有什么问题可以加作者微信讨论,cyx645016617 上千人的粉丝群已经成立,氛围超好。为大家提供一个遇到问题有可能得到答案的平台。

0 概述

这一篇文论在我看来,是CVPR 2015年 HED网络(holistically-nested edge detection)的一个改进,RCF的论文中也基本上和HED网络处处对比

在上一篇文章中,我们依稀记得HED模型有这样一个图:

其中有HED的五个side output的特征图,下图是RCF论文中的图:

我们从这两个图的区别中来认识RCF相比HED的改进,大家可以看一看图。

揭晓答案:

  • HED是豹子的图片,但是RCF是两只小鸟的图片(手动狗头)
  • HED中的是side output的输出的特征图,而RCF中是conv3_1,conv3_2,这意味着RCF似乎把每一个卷积之后的输出的特征图都作为了一个side output

没错,HED选取了5个side output,每一个side output都是池化层之前的卷积层输出的特征图;而RCF则对每一次卷积的输出特征图都作为side output,换句话说 最终的side output中,同一尺寸的输出可能不止一个

如果还没有理解,请看下面章节,模型结构。

1 模型结构

RCF的backbone是VGG模型:

从图中可以看到:

  • 主干网络上分成state1到5,stage1有两个卷积层,stage2有两个卷积层,总共有13个卷积层,每一次卷积输出的图像,再额外接入一个1x1的卷积,来降低通道数,所以可以看到,图中有大量的21通道的卷积层。
  • 同一个stage的21通道的特征图经过通道拼接,变成42通道或者是63通道的特征图,然后再经过一个1x1的卷积层,来把通道数降低成1,再进过sigmoid层,输出的结果就是一个RCF模型中的side output了

2 损失函数

这里的损失函数其实和HED来说类似:

首先整体来看,损失函数依然使用二值交叉熵

其中\(|Y^-|\) 表示 negative的像素值,\(|Y^+|\)表示positive的像素值。一般来说轮廓检测任务中,positive的样本应该是较少的,因此\(\alpha\)的值较小,因此损失函数中第一行,y=0也就是计算非轮廓部分的损失的时候,就会增加一个较小的权重,来避免类别不均衡的问题。

损失函数中有两个常数,一个是\(\lambda\),这个就是权重常数,默认为1.1;另外一个是\(\eta\)。论文中的描述为:

Edge datasets in this community are usually labeled by several annotators using their knowledge about the presences of objects and object parts. Though humans vary in cognition, these human-labeled edges for the same image share high consistency. For each image, we average all the ground truth to generate an edge probability map, which ranges from 0 to 1. Here, 0 means no annotator labeled at this pixel, and 1 means all annotators have labeled at this pixel. We consider the pixels with edge probability higher than η as positive samples and the pixels with edge probability equal to 0 as negative samples. Otherwise, if a pixel is marked by fewer than η of the annotators, this pixel may be semantically controversial to be an edge point. Thus, whether regarding it as positive or negative samples may confuse networks. So we ignore pixels in this category.

大意就是:一般对数据集进行标注,是有多个人来完成的。不同的人虽然有不同的意识,但是他们对于同一个图片的轮廓标注往往是具有一致性。RCF网络最后的输出,是由5个side output融合产生的,因此你这个RCF的输出也应该把大于\(\eta\)的考虑为positive,然后小于\(\eta\)的考虑为negative。 其实这一点我自己在复现的时候并没有考虑,我看网上的github和官方的代码中,都没有考虑这个,都是直接交叉熵。。。我这就也就多此一举的讲解一下论文中的这个\(\eta\)的含义

3 pytorch部分代码

对于这个RCF论文来说,关键就是一个模型的构建,另外一个就是损失函数的构建,这里放出这两部分的代码,来帮助大家更好的理解上面的内容。

3.1 模型部分

下面的代码在上采样部分的写法比较老旧,因为这个网上找来的pytorch版本估计比较老,当时还没有Conv2DTrans这样的函数封装,但是不妨碍大家通过代码来学习RCF。

class RCF(nn.Module):
def __init__(self):
super(RCF, self).__init__()
#lr 1 2 decay 1 0
self.conv1_1 = nn.Conv2d(3, 64, 3, padding=1)
self.conv1_2 = nn.Conv2d(64, 64, 3, padding=1) self.conv2_1 = nn.Conv2d(64, 128, 3, padding=1)
self.conv2_2 = nn.Conv2d(128, 128, 3, padding=1) self.conv3_1 = nn.Conv2d(128, 256, 3, padding=1)
self.conv3_2 = nn.Conv2d(256, 256, 3, padding=1)
self.conv3_3 = nn.Conv2d(256, 256, 3, padding=1) self.conv4_1 = nn.Conv2d(256, 512, 3, padding=1)
self.conv4_2 = nn.Conv2d(512, 512, 3, padding=1)
self.conv4_3 = nn.Conv2d(512, 512, 3, padding=1) self.conv5_1 = nn.Conv2d(512, 512, kernel_size=3,
stride=1, padding=2, dilation=2)
self.conv5_2 = nn.Conv2d(512, 512, kernel_size=3,
stride=1, padding=2, dilation=2)
self.conv5_3 = nn.Conv2d(512, 512, kernel_size=3,
stride=1, padding=2, dilation=2)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool2d(2, stride=2, ceil_mode=True)
self.maxpool4 = nn.MaxPool2d(2, stride=1, ceil_mode=True) #lr 0.1 0.2 decay 1 0
self.conv1_1_down = nn.Conv2d(64, 21, 1, padding=0)
self.conv1_2_down = nn.Conv2d(64, 21, 1, padding=0) self.conv2_1_down = nn.Conv2d(128, 21, 1, padding=0)
self.conv2_2_down = nn.Conv2d(128, 21, 1, padding=0) self.conv3_1_down = nn.Conv2d(256, 21, 1, padding=0)
self.conv3_2_down = nn.Conv2d(256, 21, 1, padding=0)
self.conv3_3_down = nn.Conv2d(256, 21, 1, padding=0) self.conv4_1_down = nn.Conv2d(512, 21, 1, padding=0)
self.conv4_2_down = nn.Conv2d(512, 21, 1, padding=0)
self.conv4_3_down = nn.Conv2d(512, 21, 1, padding=0) self.conv5_1_down = nn.Conv2d(512, 21, 1, padding=0)
self.conv5_2_down = nn.Conv2d(512, 21, 1, padding=0)
self.conv5_3_down = nn.Conv2d(512, 21, 1, padding=0) #lr 0.01 0.02 decay 1 0
self.score_dsn1 = nn.Conv2d(21, 1, 1)
self.score_dsn2 = nn.Conv2d(21, 1, 1)
self.score_dsn3 = nn.Conv2d(21, 1, 1)
self.score_dsn4 = nn.Conv2d(21, 1, 1)
self.score_dsn5 = nn.Conv2d(21, 1, 1)
#lr 0.001 0.002 decay 1 0
self.score_final = nn.Conv2d(5, 1, 1) def forward(self, x):
# VGG
img_H, img_W = x.shape[2], x.shape[3]
conv1_1 = self.relu(self.conv1_1(x))
conv1_2 = self.relu(self.conv1_2(conv1_1))
pool1 = self.maxpool(conv1_2) conv2_1 = self.relu(self.conv2_1(pool1))
conv2_2 = self.relu(self.conv2_2(conv2_1))
pool2 = self.maxpool(conv2_2) conv3_1 = self.relu(self.conv3_1(pool2))
conv3_2 = self.relu(self.conv3_2(conv3_1))
conv3_3 = self.relu(self.conv3_3(conv3_2))
pool3 = self.maxpool(conv3_3) conv4_1 = self.relu(self.conv4_1(pool3))
conv4_2 = self.relu(self.conv4_2(conv4_1))
conv4_3 = self.relu(self.conv4_3(conv4_2))
pool4 = self.maxpool4(conv4_3) conv5_1 = self.relu(self.conv5_1(pool4))
conv5_2 = self.relu(self.conv5_2(conv5_1))
conv5_3 = self.relu(self.conv5_3(conv5_2)) conv1_1_down = self.conv1_1_down(conv1_1)
conv1_2_down = self.conv1_2_down(conv1_2)
conv2_1_down = self.conv2_1_down(conv2_1)
conv2_2_down = self.conv2_2_down(conv2_2)
conv3_1_down = self.conv3_1_down(conv3_1)
conv3_2_down = self.conv3_2_down(conv3_2)
conv3_3_down = self.conv3_3_down(conv3_3)
conv4_1_down = self.conv4_1_down(conv4_1)
conv4_2_down = self.conv4_2_down(conv4_2)
conv4_3_down = self.conv4_3_down(conv4_3)
conv5_1_down = self.conv5_1_down(conv5_1)
conv5_2_down = self.conv5_2_down(conv5_2)
conv5_3_down = self.conv5_3_down(conv5_3) so1_out = self.score_dsn1(conv1_1_down + conv1_2_down)
so2_out = self.score_dsn2(conv2_1_down + conv2_2_down)
so3_out = self.score_dsn3(conv3_1_down + conv3_2_down + conv3_3_down)
so4_out = self.score_dsn4(conv4_1_down + conv4_2_down + conv4_3_down)
so5_out = self.score_dsn5(conv5_1_down + conv5_2_down + conv5_3_down)
## transpose and crop way
weight_deconv2 = make_bilinear_weights(4, 1).cuda()
weight_deconv3 = make_bilinear_weights(8, 1).cuda()
weight_deconv4 = make_bilinear_weights(16, 1).cuda()
weight_deconv5 = make_bilinear_weights(32, 1).cuda() upsample2 = torch.nn.functional.conv_transpose2d(so2_out, weight_deconv2, stride=2)
upsample3 = torch.nn.functional.conv_transpose2d(so3_out, weight_deconv3, stride=4)
upsample4 = torch.nn.functional.conv_transpose2d(so4_out, weight_deconv4, stride=8)
upsample5 = torch.nn.functional.conv_transpose2d(so5_out, weight_deconv5, stride=8)
### center crop
so1 = crop(so1_out, img_H, img_W)
so2 = crop(upsample2, img_H, img_W)
so3 = crop(upsample3, img_H, img_W)
so4 = crop(upsample4, img_H, img_W)
so5 = crop(upsample5, img_H, img_W) fusecat = torch.cat((so1, so2, so3, so4, so5), dim=1)
fuse = self.score_final(fusecat)
results = [so1, so2, so3, so4, so5, fuse]
results = [torch.sigmoid(r) for r in results]
return results

3.2 损失函数部分

def cross_entropy_loss_RCF(prediction, label):
label = label.long()
mask = label.float()
num_positive = torch.sum((mask==1).float()).float()
num_negative = torch.sum((mask==0).float()).float() mask[mask == 1] = 1.0 * num_negative / (num_positive + num_negative)
mask[mask == 0] = 1.1 * num_positive / (num_positive + num_negative)
mask[mask == 2] = 0
cost = torch.nn.functional.binary_cross_entropy(
prediction.float(),label.float(), weight=mask, reduce=False)
return torch.sum(cost)

参考文章:

  1. https://blog.csdn.net/a8039974/article/details/85696282
  2. https://gitee.com/HEART1/RCF-pytorch/blob/master/functions.py
  3. https://openaccess.thecvf.com/content_cvpr_2017/papers/Liu_Richer_Convolutional_Features_CVPR_2017_paper.pdf

轮廓检测论文解读 | Richer Convolutional Features for Edge Detection | CVPR | 2017的更多相关文章

  1. 轮廓检测论文解读 | 整体嵌套边缘检测HED | CVPR | 2015

    主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, gr ...

  2. AAAI2019 | 基于区域分解集成的目标检测 论文解读

    Object Detection based on Region Decomposition and Assembly AAAI2019 | 基于区域分解集成的目标检测 论文解读 作者 | 文永亮 学 ...

  3. CVPR 2019|PoolNet:基于池化技术的显著性检测 论文解读

    作者 | 文永亮 研究方向 | 目标检测.GAN 研究动机 ​ 这是一篇发表于CVPR2019的关于显著性目标检测的paper,在U型结构的特征网络中,高层富含语义特征捕获的位置信息在自底向上的传播过 ...

  4. 目标检测论文解读13——FPN

    引言 对于小目标通常需要用到多尺度检测,作者提出的FPN是一种快速且效果好的多尺度检测方法. 方法 a,b,c是之前的方法,其中a,c用到了多尺度检测的思想,但他们都存在明显的缺点. a方法:把每图片 ...

  5. 目标检测论文解读9——R-FCN

    背景 基于ResNet 101的Faster RCNN速度很慢,本文通过提出Position-sensitive score maps(位置敏感分值图)来给模型加速. 方法 首先分析一下,为什么基于R ...

  6. 目标检测论文解读5——YOLO v1

    背景 之前热门的目标检测方法都是two stage的,即分为region proposal和classification两个阶段,本文是对one stage方法的初次探索. 方法 首先看一下模型的网络 ...

  7. 目标检测论文解读4——Faster R-CNN

    背景 Fast R-CNN中的region proposal阶段所采用的SS算法成为了检测网络的速度瓶颈,本文是在Fast R-CNN基础上采用RPN(Region Proposal Networks ...

  8. 目标检测论文解读1——Rich feature hierarchies for accurate object detection and semantic segmentation

    背景 在2012 Imagenet LSVRC比赛中,Alexnet以15.3%的top-5 错误率轻松拔得头筹(第二名top-5错误率为26.2%).由此,ConvNet的潜力受到广泛认可,一炮而红 ...

  9. 目标检测论文解读3——Fast R-CNN

    背景 deep ConvNet兴起,VGG16应用在图像分类任务上表现良好,本文用VGG16来解决检测任务.SPP NET存在CNN层不能fine tuning的缺点,且之前的方法训练都是分为多个阶段 ...

随机推荐

  1. java开发三年,Java中接口的使用你得知道,不然你凭什么涨薪

    接口概述: 接口是Java语言中的一种引用类型,是方法的"集合",所以接口的内部主要就是定义方法,包含常量,抽象方法(JDK 7及以前),额外增加默认方法和静态方法(JDK 8), ...

  2. 用Camtasia来快速地给视频添加水印

    在日常生活中,视频的流行度越来越高,各种短视频的软件蜂拥上市,所以越来越多的人走上了自媒体的道路,在这条路上,谁的视频更加的精致,谁才能获得更多的关注度,相应的也能增加自己的人气. 但是在制作视频的过 ...

  3. 吉他弹唱上手——使用节奏变化弹好chord谱

    本篇文章将向大家介绍如何改造来自网上的chord谱. 在各位日常的弹唱之中,应该会常常遇到朋友点歌的情况,如果点唱的这首歌我们听过,那我们尚可以靠以往的记忆来应付.如果这首歌我们只是曾经听到过听过,而 ...

  4. jQuery 第七章 实例方法 位置图形

    位置坐标图形大小相关方法: .offset() .position() .scrollTop() .scrollLeft() .width() .height() .innerWidth() inne ...

  5. window安装elasticsearch和kibana

    本次测试安装5.1.1版本 es下载地址:https://www.elastic.co/downloads/past-releases/elasticsearch-5-1-1 选择zip kibana ...

  6. Java基础教程——封装

    面向对象的三大特征 封装:encapsulation 继承:inheritance 多态:polymorphism 封装 类是一个最基本的封装 封装的好处: 数据安全:保证数据安全 方便调用:提供清晰 ...

  7. MySQL数据更新

    MySQL数据更新: 导读: 该练习是本人课程学习进行整理的,若有不对,欢迎指出! 该练习没有配套的sql文件,如果需要可以看之前的文章有student表等(MySQL查询练习); 这是最后一部分练习 ...

  8. 教你C 语言简单编程速成

    我们将所有的 C 语言要素放置到一份易读的备忘录上. 1972 年,丹尼斯·里奇Dennis Ritchie任职于贝尔实验室Bell Labs,在几年前,他和他的团队成员发明了 Unix .在创建了一 ...

  9. C# 9.0新特性详解系列之三:模块初始化器

    1 背景动机 关于模块或者程序集初始化工作一直是C#的一个痛点,微软内部外部都有大量的报告反应很多客户一直被这个问题困扰,这还不算没有统计上的客户.那么解决这个问题,还有基于什么样的考虑呢? 在库加载 ...

  10. JVM 堆中对象分配、布局和访问

    本文摘自深入理解 Java 虚拟机第三版 对象的创建 Java 是一门面向对象的语言,Java 程序运行过程中无时无刻都有对象被创建出来.从语言层面看,创建对象只是一个 new 关键字而已,而在虚拟机 ...