代码源码

前情回顾:【论文笔记】R-CNN系列之论文理解

整体架构

由三部分组成

(1)提取特征的卷积网络extractor

(2)输入特征获得建议框rois的rpn网络

(3)传入rois和特征图,获得分类结果和回归结果的分类网络classifier

伪代码:

class FasterRCNN(nn.Module):
def __init__(self, ...):
super(FasterRCNN, self).__init__()
# 构建特征提取网络
self.extractor, classifier = decom_vgg16(...)
# 构建建议框网络
self.rpn = RegionProposalNetwork(...)
# 构建分类器网络
self.head = VGG16RoIHead(..., classifier=classifier) def forward(self, x, scale=1.):
# 计算输入图片的大小
img_size = x.shape[2:]
# 利用主干网络提取特征
base_feature = self.extractor.forward(x)
# 获得建议框
_, _, rois, roi_indices, _ = self.rpn.forward(base_feature, img_size, scale)
# 获得classifier的分类结果和回归结果
roi_cls_locs, roi_scores = self.head.forward(base_feature, rois, roi_indices, img_size)
return roi_cls_locs, roi_scores, rois, roi_indices def freeze_bn(self):
for m in self.modules():
if isinstance(m, nn.BatchNorm2d):
m.eval()

1.卷积网络提取特征

1.1 关于VGG16

代码

import torch
import torch.nn as nn cfg = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M']
'''
假设输入图像为(600, 600, 3),随着cfg的循环,特征层变化如下:
600,600,3 -> 600,600,64 -> 600,600,64 -> 300,300,64 -> 300,300,128 -> 300,300,128 -> 150,150,128 -> 150,150,256 -> 150,150,256 -> 150,150,256
-> 75,75,256 -> 75,75,512 -> 75,75,512 -> 75,75,512 -> 37,37,512 -> 37,37,512 -> 37,37,512 -> 37,37,512
到cfg结束,我们获得了一个37,37,512的特征层
'''
class VGG16(nn.Module):
def __init__(self):
super(VGG16, self).__init__() self.feature = make_layers(cfg, batch_norm=False)
self.avgpool = nn.AdaptiveAvgPool2d((7, 7)) #不管cfg結束后特征层大小是多少,统一变成7*7
self.classifier = nn.Sequential(
nn.Linear(7 * 7 * 512, out_features=4096, bias=False),
nn.ReLU(inplace=True), # 原地操作更加节省内存
nn.Linear(in_features=4096, out_features=4096, bias=False),
nn.ReLU(inplace=True),
nn.Linear(in_features=4096, out_features=1000, bias=False),
) def forward(self, x):
x = self.feature(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x def make_layers(cfg, batch_norm=False):
input_channels = 3
layers = []
for v in cfg:
if v == 'M':
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
else:
conv2d = nn.Conv2d(in_channels=input_channels, out_channels=v, kernel_size=3, stride=1, padding=1)
if batch_norm:
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers += [conv2d, nn.ReLU(inplace=True)] # layers.append()不能加list
input_channels = v
return nn.Sequential(*layers) input = torch.randn(1, 3, 600, 600)
model = VGG16()
output = model(input)
print(output.shape)

1.2 调整VGG16

  • 利用VGG16的特征提取部分作为Faster R-CNN的特征提取网络
  • 利用后半部分的全连接网络作为Faster R-CNN的分类网络
def decom_vgg16(pretrained = False):
model = VGG16()
feature = list(model.feature) #转化为list
classifier = list(model.classifier)
del classifier[-1]           # 删去最后的全连接层,如果有dropout层也删掉
feature = nn.Sequential(*feature) # 再转为Sequential
classifier = nn.Sequential(*classifier)
return feature,classifier

打印一下classifier :

2. RPN生成区域提议

图中的intermediate layer可以用3*3的卷积实现,cls layer和reg layer都可以用1*1的卷积实现。

self.conv1  = nn.Conv2d(in_channels, mid_channels, 3, 1, 1)
self.score = nn.Conv2d(mid_channels, n_anchor * 2, 1, 1, 0)
self.loc = nn.Conv2d(mid_channels, n_anchor * 4, 1, 1, 0)

cls layer后接softmax层

reg layer后接筛选层

2.1 anchor

(1)计算出3*3滑动窗口中心点对应原始图像上的中心点

特征图上所有的点,对应原图上固定间隔的点

import matplotlib.pyplot as plt
import numpy as np if __name__ == "__main__":
width = 38
height = 38
feat_stride = 16
x = np.arange(0, width * feat_stride, feat_stride)
y = np.arange(0, height * feat_stride, feat_stride)
X, Y = np.meshgrid(x, y)
plt.plot(X, Y,
color='limegreen', # 设置颜色为limegreen
marker='.', # 设置点类型为圆点
linestyle='') # 设置线型为空,也即没有线连接点
plt.grid(True)
plt.show()

原图                                                                                特征图

(2)生成anchor box

首先在原图(0,0)位置生成一组(9个)anchor box,然后在原图上滑动(stride=16),得到(38*38*9个)anchor box

#   生成基础的9个先验框
def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2], anchor_scales=[8, 16, 32]):
anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4), dtype=np.float32)
for i in range(len(ratios)):
for j in range(len(anchor_scales)):
h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i]) index = i * len(anchor_scales) + j
anchor_base[index, 0] = - h / 2. #框的四个参数分别是左上的(x,y)右下的(x,y)
anchor_base[index, 1] = - w / 2.
anchor_base[index, 2] = h / 2.
anchor_base[index, 3] = w / 2.
return anchor_base # 对基础先验框移动对应到所有特征点上(特征点在特征图上是相连的,但对应到原图上间隔feat_stride)
def _enumerate_shifted_anchor(anchor_base, feat_stride, height, width):
# 计算网格中心点
shift_x = np.arange(0, width * feat_stride, feat_stride)
shift_y = np.arange(0, height * feat_stride, feat_stride)
shift_x, shift_y =np.meshgrid(shift_x, shift_y)
shift = np.stack((shift_x.ravel(), shift_y.ravel(), shift_x.ravel(), shift_y.ravel(),), axis=1)
# 每个网格点上的9个先验框
A = anchor_base.shape[0]
K = shift.shape[0]
anchor = anchor_base.reshape((1, A, 4)) + shift.reshape((K, 1, 4))#应为anchor_base的四个参数都是坐标,所以都要平移
anchor = anchor.reshape((K * A, 4)).astype(np.float32)# 所有的先验框
return anchor # 38*38*9个

(3)获得预测框

根据rpn得到的k个(dx,dy,dw,dh),以及上一步得到的k个anchor box的位置(Px,Py,Pw,Ph),可以计算得到预测框的G(Gx,Gy,Gw,Gh)

def loc2bbox(src_bbox, loc):
if src_bbox.size()[0] == 0:
return torch.zeros((0, 4), dtype=loc.dtype)
  # src_bbox就是anchor,四个参数为左上角的坐标和右下角的,所以要转换得到(Px,Py,Pw,Ph)
src_width = torch.unsqueeze(src_bbox[:, 2] - src_bbox[:, 0], -1)
src_height = torch.unsqueeze(src_bbox[:, 3] - src_bbox[:, 1], -1)
src_ctr_x = torch.unsqueeze(src_bbox[:, 0], -1) + 0.5 * src_width
src_ctr_y = torch.unsqueeze(src_bbox[:, 1], -1) + 0.5 * src_height
  # rpn得到的回归参数
dx = loc[:, 0::4]
dy = loc[:, 1::4]
dw = loc[:, 2::4]
dh = loc[:, 3::4]
  # 计算得到预测框的G(Gx,Gy,Gw,Gh)
ctr_x = dx * src_width + src_ctr_x
ctr_y = dy * src_height + src_ctr_y
w = torch.exp(dw) * src_width
h = torch.exp(dh) * src_height
  # dst_bbox又是左上角右下角格式
dst_bbox = torch.zeros_like(loc)
dst_bbox[:, 0::4] = ctr_x - 0.5 * w
dst_bbox[:, 1::4] = ctr_y - 0.5 * h
dst_bbox[:, 2::4] = ctr_x + 0.5 * w
dst_bbox[:, 3::4] = ctr_y + 0.5 * h return dst_bbox

(4)筛选anchor 

# 建议框筛选 (1)出界的不要
roi[:, [0, 2]] = torch.clamp(roi[:, [0, 2]], min=0, max=img_size[1])
roi[:, [1, 3]] = torch.clamp(roi[:, [1, 3]], min=0, max=img_size[0])
# 建议框筛选(2)小于16的不要
min_size = self.min_size * scale
keep = torch.where(((roi[:, 2] - roi[:, 0]) >= min_size) & ((roi[:, 3] - roi[:, 1]) >= min_size))[0]
roi = roi[keep, :]
score = score[keep]
# 建议框筛选(3)按概率选出前n_pre_nms个
order = torch.argsort(score, descending=True)
if n_pre_nms > 0:
order = order[:n_pre_nms]
roi = roi[order, :]
score = score[order]
# 建议框筛选(4)利用nms选出n_post_nms个
keep = nms(roi, score, self.nms_iou)
keep = keep[:n_post_nms]
roi = roi[keep]

3.分类网络

 分类网络主要分为三部分

(1)ROI pooling layer:将rois转换为固定大小

(2)classifier:VGG16的后半部分全连接层

(3)cls layer 和 loc layer:两个全连接层

伪代码:

class VGG16RoIHead(nn.Module):
def __init__(self, n_class, roi_size, spatial_scale, classifier):
super(VGG16RoIHead, self).__init__()
self.roi = RoIPool((roi_size, roi_size), spatial_scale)
self.classifier = classifier
self.cls_loc = nn.Linear(4096, n_class * 4)
self.score = nn.Linear(4096, n_class)
def forward(self, x, rois, roi_indices, img_size):
...
# (1) ROI pooling layer
pool = self.roi(x, indices_and_rois)
pool = pool.view(pool.size(0), -1)
# (2) classifier layer
fc7 = self.classifier(pool)
# (3) cls layer 和 loc layer
roi_cls_locs = self.cls_loc(fc7)
roi_scores = self.score(fc7)
roi_cls_locs = roi_cls_locs.view(n, -1, roi_cls_locs.size(1))
roi_scores = roi_scores.view(n, -1, roi_scores.size(1))
return roi_cls_locs, roi_scores

3. 1 ROI pooling layer

将区域提议对应的feature map转换成小的固定大小(H*W)的feature map,不限制输入map的尺寸。

ROI pooling layer的输入有两项:

(1)提取特征的网络的最后一个feature map

(2)一个表示图片中所有ROI的N*5的矩阵,其中N表示 ROI的数目,5表示图像index和坐标参数(x,y,h,w) 。坐标的参考系不是针对feature map这张图的,而是针对原图的。

...
self.roi = RoIPool((roi_size, roi_size), spatial_scale)
...
pool = self.roi(x, indices_and_rois)# 利用建议框对公用特征层进行截取

(roi_size, roi_size):执行裁剪后的输出大小(int或Tuple[int,int]),如(高度、宽度)

spatial_scale:将输入坐标映射到框坐标的比例因子。默认值:1.0。

4. 训练

分为几步:

(1)获取公用特征层

(2)利用rpn网络获得调整参数、得分、建议框、先验框

(3)计算损失

4.1 计算建议框网络rpn的回归损失和分类损失

(0)rpn后我们得到rpn_locs, rpn_scores, rois, roi_indices, anchor,其中anchor是先验框,roi是预测框。

(1)为每个先验框anchor找到对应的最大IOU的真实框

    def _calc_ious(self, anchor, bbox):
# 获得的ious的shape为[num_anchors, num_gt]
ious = bbox_iou(anchor, bbox)
if len(bbox)==0:
return np.zeros(len(anchor), np.int32), np.zeros(len(anchor)), np.zeros(len(bbox))
# 获得每一个先验框最对应的真实框 [num_anchors, ]
argmax_ious = ious.argmax(axis=1)
# 找出每一个先验框最对应的真实框的iou [num_anchors, ]
max_ious = np.max(ious, axis=1)
# 获得每一个真实框最对应的先验框 [num_gt, ]
gt_argmax_ious = ious.argmax(axis=0)
# 保证每一个真实框都存在对应的先验框
for i in range(len(gt_argmax_ious)):
argmax_ious[gt_argmax_ious[i]] = i
return argmax_ious, max_ious, gt_argmax_ious
#   argmax_ious为每个先验框对应的最大的真实框的序号         [num_anchors, ]
# max_ious为每个真实框对应的最大的真实框的iou [num_anchors, ]
# gt_argmax_ious为每一个真实框对应的最大的先验框的序号 [num_gt, ]

(2)采样256个anchor计算损失函数,正负样本比为1:1,如果正样本少于128个,用负样本填充。每个ground truth至少对应一个先验框。

我们分配正标签前景给两类anchor:

1)与某个ground truth有最高的IoU重叠的anchor(也许不到0.7)

2)与任意ground truth有大于0.7的IoU交叠的anchor。

我们分配负标签(背景)给与所有ground truth的IoU比率都低于0.3的anchor。

(3)根据每一个先验框最对应的真实框,得到256个真实框bbox的loc  gt_rpn_loc =(dx,dy,dw,dh)

(4)由rpn网络得到的rpn_loc,rpn_score和真实框得到的gt_rpn_loc,gt_rpn_label计算损失

rpn_loc_loss = self._fast_rcnn_loc_loss(rpn_loc, gt_rpn_loc, gt_rpn_label, self.rpn_sigma)
rpn_cls_loss = F.cross_entropy(rpn_score, gt_rpn_label, ignore_index=-1)

4.2 计算Classifier网络的回归损失和分类损失

(0)rpn后我们得到rpn_locs, rpn_scores, rois, roi_indices, anchor,其中anchor是先验框,roi是预测框。

(1)获得每一个建议框roi最对应的真实框

(2)采样n_sample个建议框roi计算损失函数

满足建议框和真实框重合程度大于neg_iou_thresh_high的作为正样本
将正样本的数量限制在self.pos_roi_per_image以内

满足建议框和真实框重合程度小于neg_iou_thresh_high大于neg_iou_thresh_low作为负样本
将正样本的数量和负样本的数量的总和固定成self.n_sample

(3)得到真实框bbox的loc gt_roi_loc =(dx,dy,dw,dh)

(4)由Classifier网络得到的roi_loc,roi_score,和真实框得到的gt_roi_loc,gt_roi_label计算损失

roi_loc_loss = self._fast_rcnn_loc_loss(roi_loc, gt_roi_loc, gt_roi_label.data, self.roi_sigma)
roi_cls_loss = nn.CrossEntropyLoss(roi_score[0], gt_roi_label)

Tip:

上图为4个真实框ground truth,在rpn层中,我们将roi筛选至n_post_nms个(600个)。

在rpn计算的是先验框anchor和真实框bbox的IOU,分类层用的是建议框roi和真实框bbox的IOU

5. 评价指标

5.1计算交并比IOU

def bbox_iou(bbox_a, bbox_b):
if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:
print(bbox_a, bbox_b)
raise IndexError
tl = np.maximum(bbox_a[:, None, :2], bbox_b[:, :2])
br = np.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:])
area_i = np.prod(br - tl, axis=2) * (tl < br).all(axis=2)
area_a = np.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)
area_b = np.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1)
return area_i / (area_a[:, None] + area_b - area_i)

5.2 计算AP

(1)获得预测结果

  • 图片送入网络得到预测结果 roi_cls_locs, roi_scores, rois等
  • 对建议框进行解码,获得预测框 [ top, left, bottom, right, score, predicted_class ]
  • 预测结果写入txt

(2)获得真实框

  • "VOC2007/Annotations/"+image_id+".xml"中找到xmin,ymin,xmax,ymax
  • 写入txt

(3)按照置信度对预测框排序

假设一共有7张图片,绿色框是GT(15个),红色框是预测框(24个)并带有置信度。

现在假设IOU=30%,按照置信度排序得到下表。

其中TP表示预测正确、FP表示预测错误、acc TP表示从头到该位置累计正确个数、precision表示从头到该位置的精确率、recall表示从头到该位置的召回率。

下图表示的就是从头到尾,依次加入新的样本时,P和R的变化情况:

代码:

  • 按照置信度排序
bounding_boxes.sort(key=lambda x:float(x['confidence']), reverse=True)
  • 遍历bounding_boxes,计算IOU,大于min_overlap为TP,不然为FP
  • 计算precision和recall
for idx, val in enumerate(tp):
rec[idx] = float(tp[idx])/np.maximum(gt_counter_per_class[class_name], 1) # rec=tp/(tp+fn)
                    # gt_counter_per_class 计算每个类有多少个gt,gt的个数=tp+fn
for idx, val in enumerate(tp):
prec[idx] = float(tp[idx]) / np.maximum((fp[idx] + tp[idx]), 1) # prec=tp/(tp+fp)

(4)使精度单调下降,计算PR曲线的面积

蓝色线就是前面那张PR图,红色虚线的纵坐标是单调减小的,每次减小到右侧蓝线的最高点。APall就是红色虚线下方的面积。

def voc_ap(rec, prec):
rec.insert(0, 0.0) # insert 0.0 at begining of list
rec.append(1.0) # insert 1.0 at end of list
mrec = rec[:]
prec.insert(0, 0.0) # insert 0.0 at begining of list
prec.append(0.0) # insert 0.0 at end of list
mpre = prec[:]
# 1. 这一部分使精度单调下降
i_list = []
for i in range(1, len(mrec)):
if mrec[i] != mrec[i-1]:
i_list.append(i) # if it was matlab would be i + 1
# 2. 平均精度(AP)是曲线下的面积
ap = 0.0
for i in i_list:
ap += ((mrec[i]-mrec[i-1])*mpre[i])
return ap, mrec, mpre

5.3 mAP指标

如果是多类别目标检测任务,就要使用mean AP(mAP),其定义为:

即,对所有的类别进行AP的计算,然后取均值。

AP50指的是IOU的值取50%,AP70同理。

AP@50:5:95指的是IOU的值从50%取到95%,步长为5%,然后算在在这些IOU下的AP的均值。

参考文献:

1. 目标检测01:常用评价指标(AP、AP50、AP@50:5:95、mAP)

2. 睿智的目标检测20——利用mAP计算目标检测精确度

3. Pytorch 搭建自己的Faster-RCNN目标检测平台(视频)

4. 深度学习小技巧-mAP精度概念详解与计算绘制(视频)

5. ROI Pool和ROI Align

 
 

【论文笔记】R-CNN系列之代码实现的更多相关文章

  1. 论文笔记:CNN经典结构2(WideResNet,FractalNet,DenseNet,ResNeXt,DPN,SENet)

    前言 在论文笔记:CNN经典结构1中主要讲了2012-2015年的一些经典CNN结构.本文主要讲解2016-2017年的一些经典CNN结构. CIFAR和SVHN上,DenseNet-BC优于ResN ...

  2. 论文笔记:CNN经典结构1(AlexNet,ZFNet,OverFeat,VGG,GoogleNet,ResNet)

    前言 本文主要介绍2012-2015年的一些经典CNN结构,从AlexNet,ZFNet,OverFeat到VGG,GoogleNetv1-v4,ResNetv1-v2. 在论文笔记:CNN经典结构2 ...

  3. 【论文笔记】CNN for NLP

    什么是Convolutional Neural Network(卷积神经网络)? 最早应该是LeCun(1998)年论文提出,其结果如下:运用于手写数字识别.详细就不介绍,可参考zouxy09的专栏, ...

  4. Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现(转)

    Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现 zouxy09@qq.com http://blog.csdn.net/zouxy09          自己平时看了一些论文, ...

  5. 论文笔记系列-Auto-DeepLab:Hierarchical Neural Architecture Search for Semantic Image Segmentation

    Pytorch实现代码:https://github.com/MenghaoGuo/AutoDeeplab 创新点 cell-level and network-level search 以往的NAS ...

  6. Person Re-identification 系列论文笔记(一):Scalable Person Re-identification: A Benchmark

    打算整理一个关于Person Re-identification的系列论文笔记,主要记录近年CNN快速发展中的部分有亮点和借鉴意义的论文. 论文笔记流程采用contributions->algo ...

  7. 【论文笔记系列】AutoML:A Survey of State-of-the-art (下)

    [论文笔记系列]AutoML:A Survey of State-of-the-art (上) 上一篇文章介绍了Data preparation,Feature Engineering,Model S ...

  8. 论文笔记系列-Neural Network Search :A Survey

    论文笔记系列-Neural Network Search :A Survey 论文 笔记 NAS automl survey review reinforcement learning Bayesia ...

  9. Multimodal —— 看图说话(Image Caption)任务的论文笔记(一)评价指标和NIC模型

    看图说话(Image Caption)任务是结合CV和NLP两个领域的一种比较综合的任务,Image Caption模型的输入是一幅图像,输出是对该幅图像进行描述的一段文字.这项任务要求模型可以识别图 ...

  10. Deep Reinforcement Learning for Visual Object Tracking in Videos 论文笔记

    Deep Reinforcement Learning for Visual Object Tracking in Videos 论文笔记 arXiv 摘要:本文提出了一种 DRL 算法进行单目标跟踪 ...

随机推荐

  1. ef 查询生成语句的几种方式

    前言 整理一下ef 如何查看生成sql 语句的,现在有ef core 了,统一整理一下. 正文 方式如下: 数据库监听 这是一种推荐方式,因为调试和代码分开,不会有影响. 然后连接: 然后可以进行一些 ...

  2. JS isPrototypeOf 和hasOwnProperty 还有in的区别

    isPrototypeOf 和hasOwnProperty 的区别 isPrototypeOf 是判断原生链上是否有该对象. 1.isPrototypeOf isPrototypeOf是用来判断指定对 ...

  3. Mysql安装和远程登录--Centos7

    在Centos7中使用的包管理工具是yum,当然使用包管理工具安装也是最方便的. 本文操作内容需要在root用户下,否则有些步骤无法成功执行. 系统环境信息展示 安装 MySQL 提供的 RPM wg ...

  4. 实训篇-Html-frameset框架集

    frameset.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  5. stmp 501 5.1.3 Invalid Address 无效的邮件地址

    stmp 501 5.1.3 Invalid Address 无效的邮件地址 一般来说就是要确认邮箱地址是不是对的 还有一种可能的情况是使用的邮件服务器仅支持对内邮件,没有对外邮件的发送权限

  6. PolarDB-X 源码解读:事务的一生

    简介: 本文将主要解读 PolarDB-X 中事务部分的相关代码,着重解读事务的一生在计算节点(CN)中的关键代码:从开始.执行.到最后提交这一整个生命周期. 概述 本文将主要解读 PolarDB-X ...

  7. SchedulerX 如何帮助用户解决分布式任务调度难题?

    ​简介:本文分别对任务调度平台的资源定义.可视化管控能力.分布式批处理能力进行了简述,并基于 SchedulerX 的能力结合实际业务场景提供了一些基础参考案例.希望通过上述内容能让大家方便地熟悉任务 ...

  8. FileStream 的 FlushAsync 方法在 .NET Framework 与 .NET Core 行为的不同

    本文记录 FileStream 的 FlushAsync 方法在 .NET Framework 与 .NET Core 行为的不同 在使用 HID 设备进行 IO 通讯时,可以采用 FileStrea ...

  9. 特权同学笔记-《边练边学》-在QP里调用modelsim的步骤

    在QP里调用Modelsim需要先设置仿真参数和工具路径. 在QP调用modelsim的步骤 1. 在QP里建立工程,代码,分析综合:2. 添加testbench代码,processing-start ...

  10. vue监听watch

    export default { watch:{         showArea(val,_val){                            console.log('showAre ...