pytorch-faster-rcnn 代码实践

转载请注明出处!

目标检测在CV里面占很大一席之地了,而目标检测当红网络肯定少不了RCNN家族。在自己的数据集上使用了Faster RCNN,效果确实不错。

理解Faster RCNN还需要相应地看Fast RCNN和RCNN,因为有的公式在前2篇出现过的会略过,就不太明白。

ok进入正题~

1 Faster RCNN论文详解

Faster RCNN网络就是:

1)  卷积网络去掉全连接层的Feature Map;

2)  Feature Map——>RPN生成region proposal;

3)  region proposal+Feature Map——>ROI pooling 生成proposal feature maps;

4)   proposal feature maps经过全连接层后分类+bounding box regression获得最终的检测框的精确定位以及类别。

这可能会有疑问(1)为什么要生成Feature Map而不是直接输入原始图像?(2) RPN是什么,生成的region proposal和proposal feature maps有什么区别? (3)为什么还要再经过bounding box regression?

问题(1),回想曾经行人检测是hog+SVM,选定一些窗口,通过滑窗来提取区域,并提取区域的HOG特征,输入到SVM判断是否行人.而Faster RCNN就是利用神经网络提特征.

1.1 RPN(region proposal network)

RPN的作用是生成region proposal,受到空间金字塔的启发。再回到行人检测的问题。人因为远近在图像中表现大小不一样,如果用同一个(w,h)的窗口去滑动,结果可能会漏掉小的人。所以,之后人们会用多个(w,h)的窗口去检测,并且选取响应最大的那个(w,h)。多个(w,h)具体多少个?具体w和h是多少?RPN确定了具体多少个,具体w和h是多少。

âè¡äººæ£æµâçå¾çæç´¢ç»æ

RPN取代了滑窗的形式(也可以理解为窗口的步长是1个像素吧),借鉴了FCN的思想,变成了pixel-2pixel预测,只不过FCN对于每个像素输出是N个类别的概率值,而RPN输出的就是region proposal。上述说RPN确定了具体多少个窗口和具体w和h是多少。这个窗口呢,他们叫Anchors。文献中这个Anchors的数量k=9,分别是3种尺寸size、w和h比分别为{1:1  1:2  2:1}。顺便说一句,不管你输入的图像大小是多少,最后输入进网络都会reshape为统一尺寸,比如800x600。

所以RPN就相当于,用9个窗口,步长为1个像素地去检测行人。每个像素呢,会有9个判断(同时进行,所以很节约时间),以它为中心的9个anchors最有可能存在的位置(每个点初始化的9个anchors是一样的,经过学习,每个点的9个anchors位置都会改变)。

RPN它不仅输出了region proposal,还要分个类,不过是二分类,背景还是目标。也就是它额外加了个预测的分支。

比如使用VGG网络后最后一层卷积层的输出是256个224x224的feature maps,输入RPN后变成了 6*k个224x224的通道。(region proposal是4k,分类是2k,因为anchors需要4个变量表示,左上角x、y和w,h,分类需要2个输出,分别是是背景的概率和是目标的概率)

再看看reg和cls不要想复杂了,这两个是并行的,reg使用的是bounding box回归,cls就是二分类!结合图和代码,可以看一下reg和cls具体如何操作的。

feature_maps --> 3x3 卷积+relu  后,

输入到cls:

 -->1x1卷积+relu-->reshape -->softmax-->reshpe-->output  (label prediction)

第一个reshape :[batch,channels,w,h]-->[batch,2,w*channels/2,h]。这里channels等于2(bg/fg) * 9 (anchors)

第二个reshape:[batch,channels,w,h]-->[batch,18,w*channels/18,h]。

为什么要reshape:要预测的话,输入channels表示你要预测的类别有channels个,比如二分类,输入到softmax里面必须channels等于2。所以要把channels = 2*9变成 2.

再看看bounding box reg,feature_maps --> 3x3 卷积后,

输入到reg:

-->1x1卷积+relu-->bounding box reg->output  (region proposals)

1.2 bounding box regression

写在前面:BB回归是特征图的函数,而不是位置的函数!即通过前面的卷积提取特征,将这些特征与偏移量(表示anchor到GT的所要经历的表示平移+缩放的4个参数)的映射关系就是BB回归要学习的东西。

这个问题在RCNN文章中详细介绍了。比如图中,绿色的框框是groundtruth,而红色的框框为rpn中某个anchor。如何让anchor逼近groundtruth呢?

平移+缩放!

假设红色的框框是A,用左上角坐标和长、宽描述,则A={Ax, Ay, Aw, Ah},绿色的groundtruth是G,G={Gx, Gy, Gw, Gh}。要找到一种映射F,是的F(A)=G',而G'尽量等于G。

1 平移:

G'x = Ax + △x             G'y = Ay + △y

2 缩放

 G'w = Aw *△w             G'h = G'h*△h

△x、△y、△w 和△h可以用A的函数来表示(因为要学习这四个变量,所以必须要写成A的函数形式)

G'x = Aw \times d_{x}(A) +Ax

G'y = Ah \times d_{y}( A) +Ay

G'w = Aw \times exp(d_{w}( A))

G'h = Ah \times exp(d_{h}( A))

要学习的就是  这四个变换dx,dy,dw和dh。

为什么有个exp,这是为了让缩放因子大于0。

由于输入RPN的是feature map,在feature map每个像素都有k个anchor,这k个anchor会有相应的4对变换d(dx,dy,dw和dh),每个anchor 都有相应的偏移量作为标准:

x,y,w,h是预测的框框,xa,ya,wa,ha是第i个像素的某个anchor,x*,y*,w*和h*是groundtruth的框框。t实际中就是d,而偏移量d是feature map的特征向量的函数:

d_{*} = \omega_{*}^{T} \times \Phi (P)

\Phi (Pi)是feature maps第i个像素的第k个anchor,则\Phi (Pi):k_ancho_w*k_ancho_h*channels的值。如下图:

纠正:在RPN阶段,\Phi (Pi)是整个feature maps的输入,而在后面的才是框框里面的。

(解释一下为什么是特征向量而不是坐标,因为特征向量是高级的语义信息,它可能包含坐标,也可能包含像素值和梯度等等,这都是需要学习后才能确定的,如果直接是坐标,这就属于低级的信息了,那也就不需要用VGG网络提取特征,直接输入原始图像就好了)

最终损失函数表示形式是:

Loss = \sum _{i=1}^{N}(t_{*}^{'}-\omega _{*}^{T}\Phi (P^{i}))^{2}

feature maps经过3x3卷积后,分别输入到cls和reg中,得到的rpn_cls_prob和rpn_bbox_pred,将这二者还有im_info输入到RPN_proposal。(im_info解释:https://blog.csdn.net/hejin_some/article/details/80743635)

1.3 RPN_proposal

#为每个位置i产生一个以i为中心的anchor  A

#在像素 i 中 预测的bbox偏移量 应用在每个anchor中

#剪辑 预测框 到图像

#删除高度或宽度 小于 阈值的预测框

#根据score对所有的(proposal,score)进行排序

#在NMS之前 提取前 top pre_nms_topN的proposals  (NMS:nonmaximum suppression)

#对其余提案应用阈值为0.7的NMS

 #NMS之后,采取  after_nms_topN 个  proposals

#返回最靠前的proposals( - > RoIs top,得分排名靠前)

通过RPN_proposal后,还需要使用Fast RCNN。

1.4 Fast RCNN: ROI pooling+分类回归

Fast RCNN:ROI pooling+(分类/回归)。

为什么需要ROI pooling?我们知道,分类在全连接层后接一个softmax,但是是需要大小一样的。前面的rpn阶段输入的是整张feature map,而这里输入的是目标ROI的feature maps,如:

ROI 和anchor的区别,anchor是统一的一定size和ratio的矩形,ROI是anchor学习的偏移量所得。这一过程是:

anchor学习了4个偏移量,anchor根据4个偏移量得到的是原始的图像对应的位置,称为bounding box(如512x512resize到600x600,则对应的是这个600x600的),bounding box经过缩小变为feature maps上的ROI。

但RPN后,根据偏移量,会把这些anchors按偏移量缩放,这就不能保证每个ROI都一样了。所以ROI pooling的作用有2个:

第一就是提取ROI的特征;

第二个就是,将每个不同大小的ROI映射出相同大小size_fix的H和W,  如(7*7*channels)。方便接下来的cls 和reg。

1.5 Sharing Features for RPN and Fast R-CNN

原文中是说,RPN和Fast R-CNN独立训练的话那二者将会以不同的方式调节参数,所以需要将RPN和fast R-CNN联合起来,共同学习。

他采取的方案是交替训练——4-Step Alternating Training:

第一步:用pre-trained 的用来提取特征的model(如VGG等)初始化RPN网络,然后训练RPN,在训练后,model以及RPN会被更新。
第二步:用pre-trained 的model(和第一步一样)初始化Fast-rcnn。然后使用训练过的RPN来计算proposal,再将proposal给予Fast-rcnn网络,接着训练Fast-rcnn。训练完以后,model以及Fast-rcnn会被更新(RPN没有参与训练所以不变)。

这有个疑问,我没想明白,我觉得这个时候,Fast-rcnn和RPN是共享了的,因为RPN的结果用来训练了Fast rcnn,但是文中说At this point the two networks do not share convolutional layers.可是这一步明明是:we train a separate detection network by Fast R-CNN using the proposals generated by the step-1 RPN.

这个博主给了我解答https://blog.csdn.net/weixin_40449426/article/details/78141635  我混淆了 共享卷积的概念:model始终保持和上一步Fast-rcnn中model一致,所以就称之为着共享。

分享是美德,对我有启发的尽量po上,感谢博主的分享~~~~

第三步:使用第二步训练完成的model来初始化RPN网络,第二次训练RPN网络。但是这次要把model锁定,训练过程中,model始终保持不变,而RPN的会被改变。

第四步:仍然保持第三步的model不变,初始化Fast-rcnn,第二次训练Fast-rcnn网络。其实就是对其unique进行finetune,训练完毕,得到一个文中所说的unified network。

我觉得Faster RCNN就介绍到这,可能还需要补一些Fast rcnn,因为有个roi pooling,后续加上把~~

后面到重头戏了,用自己的数据训练网络,我使用的是pytorch,配置有点繁琐,所以第二步说一下如何配置以及最可能遇到的坑!

2 Pytorch 搭建 Faster RCNN系列——Linux环境

环境介绍:

Pytorch

GPU: l英伟达 1070 8G

CUDA:9.0(8.0也可以)

传输门: https://github.com/jwyang/faster-rcnn.pytorch/

改一下这个,比如我的是1070系列,则改成 sm_61

我觉得这个时候很多人都会遇到问题,我花了半天解决~~避免大家入坑,

RuntimeError: cuda runtime error (8) : invalid device function at /pytorch/torch/lib/THC/THCTensorCopy.cu:204

问题解决方案:

https://github.com/jwyang/faster-rcnn.pytorch/issues/110

然后就是运行例题了~~嗯,这个就略过了,bug这些东西真的看人品~~

下面进入我们的训练自己的数据的环节了.

3 Faster RCNN训练自己的数据集

3.1 准备自己的数据集

介绍一下为自己的数据,为的数据已经分割了.比如一幅图像中有3个杯子,则处理原始图像,还有3张每个杯子的分割图.

第一步是制作成txt:

xxxxx1.jpg xmin1 ymin1 xmax1 ymax1

xxxxx1.jpg xmin2 ymin2 xmax2 ymax2

第二步是将txt变成xml.

尊重他人劳动成果,传输门:

https://github.com/ChaoPei/create-pascal-voc-dataset

3.2 训练自己的网络

首先可以下载别人训练好的模型,我下载的是VGG提特征的那个.resnet效果会好一点.

上述链接有训练好的模型:https://github.com/jwyang/faster-rcnn.pytorch

Pretrained Model

We used two pretrained models in our experiments, VGG and ResNet101. You can download these two models from:

  • VGG16: Dropbox, VT Server

  • ResNet101: Dropbox, VT Server

点击VT Server.

介绍一下如何改参数把,以及调整网络:


 
   
   
   
   
  1. def parse_args():
  2. """
  3. Parse input arguments
  4. """
  5. parser = argparse.ArgumentParser(description= 'Train a Fast R-CNN network')
  6. parser.add_argument( '--dataset', dest= 'dataset',
  7. help= 'training dataset',
  8. default= 'pascal_voc', type=str) #可不改动
  9. parser.add_argument( '--net', dest= 'net',
  10. help= 'vgg16, res101', #我使用的是VGG,所以把default改了
  11. default= 'vgg16', type=str)
  12. parser.add_argument( '--start_epoch', dest= 'start_epoch',
  13. help= 'starting epoch',
  14. default= 1, type=int) #训练的时候epoch从哪开始
  15. parser.add_argument( '--epochs', dest= 'max_epochs',
  16. help= 'number of epochs to train',
  17. default= 100, type=int) #迭代多少次
  18. parser.add_argument( '--disp_interval', dest= 'disp_interval',
  19. help= 'number of iterations to display',
  20. default= 10, type=int) #在一个迭代中,间隔多少个batch显示
  21. parser.add_argument( '--checkpoint_interval', dest= 'checkpoint_interval',
  22. help= 'number of iterations to display',
  23. default= 20, type=int) #每多少个迭代显示
  24. parser.add_argument( '--save_dir', dest= 'save_dir',
  25. help= 'directory to save models', default= "/home/faster-rcnn.pytorch/models",
  26. type=str)
  27. parser.add_argument( '--nw', dest= 'num_workers',
  28. help= 'number of worker to load data',
  29. default= 0, type=int) #加载数据要多少个worker
  30. parser.add_argument( '--cuda', dest= 'cuda',
  31. help= 'whether use CUDA',default= True,
  32. action= 'store_true') #是否使用cuda
  33. parser.add_argument( '--ls', dest= 'large_scale',
  34. help= 'whether use large imag scale',
  35. action= 'store_true') #学习率
  36. parser.add_argument( '--mGPUs', dest= 'mGPUs',
  37. help= 'whether use multiple GPUs',
  38. action= 'store_true') #是否使用cuda
  39. parser.add_argument( '--bs', dest= 'batch_size',
  40. help= 'batch_size',
  41. default= 4, type=int)
  42. parser.add_argument( '--cag', dest= 'class_agnostic',
  43. help= 'whether perform class_agnostic bbox regression',
  44. action= 'store_true') #是否执行类无关的bbox回归
  45. # config optimization
  46. parser.add_argument( '--o', dest= 'optimizer',
  47. help= 'training optimizer',
  48. default= "sgd", type=str) #优化算法
  49. parser.add_argument( '--lr', dest= 'lr',
  50. help= 'starting learning rate',
  51. default= 0.001, type=float) #初始学习率
  52. parser.add_argument( '--lr_decay_step', dest= 'lr_decay_step',
  53. help= 'step to do learning rate decay, unit is epoch',
  54. default= 5, type=int)
  55. parser.add_argument( '--lr_decay_gamma', dest= 'lr_decay_gamma',
  56. help= 'learning rate decay ratio',
  57. default= 0.1, type=float) #学习率下降率
  58. # set training session
  59. parser.add_argument( '--s', dest= 'session',
  60. help= 'training session',
  61. default= 1, type=int) #训练会话,针对多gpu
  62. # resume trained model #使用训练好的模型
  63. parser.add_argument( '--r', dest= 'resume',
  64. help= 'resume checkpoint or not',
  65. default= False, type=bool)
  66. parser.add_argument( '--checksession', dest= 'checksession',
  67. help= 'checksession to load model',
  68. default= 1, type=int)
  69. parser.add_argument( '--checkepoch', dest= 'checkepoch',
  70. help= 'checkepoch to load model',
  71. default= 1, type=int)
  72. parser.add_argument( '--checkpoint', dest= 'checkpoint',
  73. help= 'checkpoint to load model',
  74. default= 0, type=int)
  75. # log and diaplay
  76. parser.add_argument( '--use_tfboard', dest= 'use_tfboard',
  77. help= 'whether use tensorflow tensorboard',
  78. default= False, type=bool)
  79. args = parser.parse_args()
  80. return args

题外话:介绍一下argparse:arguments parser参数解析器.有时需要在终端执行python文件的时候,我们需要传入参数,所以会有个argparse来传参数.

但如果本地可以直接执行,会定义个:


 
   
   
   
   
  1. if __name__ == '__main__':
  2. args = parse_args()

这个时候,应该如何修改或赋值上面定义的参数呢?


 
   
   
   
   
  1. if __name__ == '__main__':
  2. args = parse_args()
  3. args.class_agnostic = True

这个时候直接训练就可以了.训练的适合有可能出现loss会出现NaN还是-1来着,这是因为,在制作xmin ymin xmax和ymax有的是-1.python 默认是0开始,而有的坐标从1开始,具体自己谷歌吧吼吼

3.3 测试和计算mAP

我运行的是demo.py并且稍微改了一下使得能够测mAP,test_net.py有比较多的bug,这个一调试电脑就死机.放在我的

github帐号上把,需要的自取.

https://github.com/ShourenWang/Faster-RCNN-pytorch

恩,如有疑惑之处,欢迎留言~

 

 

你可能感兴趣的:(#,目标检测)