RPNHeadRPNHead的主要功能是给定图片的各个特征预测层,通过conv3x3滑动窗口,预测每个锚框的前景概率和回归参数__init__通过滑动窗口预测目标概率和bbox regression参数​forward​ 在多个特征层预测,把3x3conv作为滑动窗口生成预测结果.AnchorsGeneratorAnchorsGenerator类的主要功能是给定图片的各个特征层,在每个特征层上都生成特定大小和尺寸的锚框__init__​ 处理anchor的sizes和ratiosforward​ 首先生成anchor模板,再获取每个特征图上的anchor在原图上的坐标信息.随后遍历每张图像,将所有anchor存入anchors中,anchors是list of list,第一个维度是不同图片,第二个维度是不同特征层.最后通过cat操作消除第二个维度,即把一个图片的不同特征图的anchor全部汇总起来.至此AnchorGenerator生成anchor的工作结束.generate_anchors​ 生成anchors模板,首先拓展ratios和scales的维度并相乘(广播机制),获得不同尺度不同比例的anchor对应的宽和高,再将坐标摞起来,得到anchor模板的左上角(右下角)坐标.cache_grid_anchors​ 对于anchor在原图上的位置信息做了一个cache缓存操作grid_anchors​ 得到anchor模板在原图上的坐标信息,这里主要用到了meshgrid函数生成网格坐标,先生成特征图的每个像素中心点在原图上的坐标信息,再和anchor模板坐标相加即可(因为anchor模板默认中心点是(0,0))BoxCoderBoxCoder类主要工作是已知两个Box计算regression回归参数已知一个Box和regression计算回归后的Boxencode​ 根据anchor和对应的GTBox计算regression参数。这里先把所有图片的reference_boxes和proposals拼接在一起，最后返回的时候再根据每张图片的anchor个数分离，加快运算效率。encode_single/encode_boxes​ 根据proposal坐标得到其宽高(ex_widths/ex_heights)和中心点坐标(ex_ctr_x/ex_ctr_y).同理计算GTBox的宽高和中心点.​ 最后计算四个回归参数dx/dy/dw/dh.参照以下公式.decode​ 思路和encode相反,参数为proposal和回归参数regresssion,计算最终的proposal坐标.decode_single​ 思路与encode_single相反.​ 计算公式如下.MatcherMatcher的主要功能是将真实框分配给锚框,并划分正负样本_call_​ 根据IoU矩阵为每个proposal分配真实框,并根据IoU大小划分正负样本.BalancedPositiveNegativeSampleBalancedPositiveNegativeSample的主要功能：随机采样正负样本，挑选指定比例的样本参与损失计算__call__​ call函数的参数是matched_idxs,数据类型是List of Tensor,List代表不同图像,Tensor代表每幅图像中每个Proposals的正负样本类型,为-1时代表样本被忽略;为0时代表负样本;>0时代表正样本​ 首先通过条件过滤出正负样本的下标,再根据正样本的比例(positive_fraction)选取正负样本的数量.注意如果正/负样本不足时,需要采样所有正/负样本.​ 正负样本的随机采样使用randperm函数,​ 最后根据选出的正负样本下标制作掩码并返回.RegionProposalNetworkRegionProposalNetwork类是RPN网络的主体,调用了Matcher/BoxCoder等类来完成RPN的功能__init__传入上文提到的两个类(anchor_generator和rpn_head).fg_iou_thresh和bg_iou_thresh是划分正负样本的阈值.positive_fraction是计算损失时正负样本比例pre_nms_top_n是每个特征层最后NMS留下的候选框个数,post_nms_top_n是所有特征层汇总后留下的候选框个数BoxCoder /Matcher/BalancedPositiveNegativeSample见前文forward​ 首先将特征图丢入head中获得预测类别和边界框偏移的结果,再通过anchor_generator生成anchors.​ 接下来计算每个特征层上的anchors的数量,用于之后的过滤操作.​ 利用Box_coder的decode操作将pred_bbox_deltas应用在生成的anchors上,获得propoals的坐标信息,在这里做NMS操作.​ 最后为预测的proposal分配GTBox,并计算损失.concat_box_prediction_layers​ 对box_cls和box_regression两个list中的每个预测特征层的预测信息的tensor排列顺序以及shape进行调整 -> [N, -1, C]C是num_classes or 4(bbox regression)permute_and_flatten​ 主要工作是交换维度,把C维度换到最后.利于之后计算损失.filter_proposals​ 此方法调用topk方法过滤每个特征层的proposals.​ 首先用level变量存储不同特征层上的anchors索引信息(因为这个时候不同特征层的anchor在之前的处理中存储在一起了,这里需要用level来区分不同的特征层anchor)​ 随后调用_get_top_n_idx获取每层特征图中预测概率排前pre_nms_top_n的anchor的索引值,存入top_n_idx.利用双重索引获得这些anchor的objectness和proposal.最后过滤得到的proposal对应的objectness记得做一次sigmod~​ 最后对于过滤的proposals进行边缘的裁剪,并去除小概率的proposal.最后再按照不同次执行一次batch_nms.batch_nms是按照某个类别/预测层分别执行nms,当然底层实现更加高效.具体见下文clip_boxes_to_image​ 利用clamp函数将边界框限制在图片范围内remove_small_boxes​ 过滤宽高不满足条件的proposalbatch_nms​ 为了高效实现不同类别分别做NMS,首先为不同的类别生成一个足够大的offset,保证不同类别之间的boxes不会重叠,从而保证了在各自类别内做NMS的效果.assign_targets_to_anchors​ 将真实框分配给锚框,并划分正负样本.主要使用proposal_matcher来实现匹配.具体方法见Matcher类实现.compute_loss​ 首先通过BalancedPositiveNegativeSampler类选择正负样本,并将正负样本的索引拼接在一起,用来计算目标预测损失.​ 随后计算正样本的边界框回归损失(smooth_L1_Loss)和所有样本的物体预测损失(BCE_Loss)
整体思路​ RoI-Header共由三部分组成：box_roi_pool:Multi-scale RoIAlign poolingbox_head:TwoMLPHeadbox_predictor:FastRCNNPredictorMultiScaleRoIAlign​ 该类与之前所述的RoIPooling不同，RoIAlign的定位能力更强。RoIPooling在计算过程中存在取整操作，从而引入了更多的定位误差，而Align不会进行取整操作。具体以后再展开~TwoMLPHead​ TwoMLPHead其实就是RoIPooling之后跟着的两个全连接层(还有一个Flatten层).FastRCNNPredictor​ FastRCNNPredictor也就是两个全连接层，分别预测每个proposal的类别和bbox的回归参数。注意输入的num_classes应该是实际类型+1,因为第0类是backgroundROIHeadsinit​ 保存一些需要用到的工具.box_similarity负责计算box_iouproposal_matcher负责正负样本的分配fg_bg_sampler负责正负样本的采样其他参数就是刚刚提到的类以及一些阈值参数了forward​ 训练模式下,首先会对proposal进一步采样,得到proposal样本和对应的label.​ 其次将proposal和features特征层送入roi_pool得到每个proposal的box_features.box_features的形状应该是[num_proposals,channel,7,7]​ 随后将box_features送入box_header提取出特征向量​ 最后将这些向量送入box_predictor得到类别和回归参数预测结果​ 最后一部分代码如下.如果是训练模式下将通过fastrcnn_loss计算损失;如果是预测模式则会对proposals进行预处理postprocess_detections.最后返回相应的结果select_training_samples该函数的功能是将RPN网络提供的Proposal进行采样,并计算这些Proposal的标签和regression参数(分配gtbox并计算,跟之前RPN网络内的操作类似)​ 如下图所示,源码将gt_boxes也拼到了proposal后面,这里可能考虑到了PRN训练初始无法提供有效的proposal,所以加入gt_boxes来训练FastRCNN网络部分.​ 接下来将调用assign_targets_to_proposals函数将proposals分配给gt_boxes.这个函数在之前的RPN网络提到过,这里不再赘述.​ 之后调用了subsample进行采样.得到一定比例的正负样本.​ 最后一步是遍历每张图片,首先找到正负样本(因为回归参数正负样本都参与计算)对应proposal的类别和proposal分配到的gt_box,再计算gt_box和proposal之间的回归参数(通过box_coder的encode方法,之前在RPN网络中有提到).注意这里负样本对应的gt_box是第0个gt_box,按道理来说负样本不参与边界回归参数损失的计算.但是为了防止matched_idxs下标越界,所以在计算match_idxs时将-1都置为了0,导致现在"负样本有对应的gt_box,且计算了回归参数",不过这个问题不大,因为label记录了负样本的位置,在计算损失时忽略这部分即可~subsample​ 该函数其实只是调用了fg_bg_sampler这个类对象,得到了每张图片里的正负样本索引,随后将每张图片的正负样本索引丢到sampled_inds列表里.fastrcnn_loss​ 刚开始将label和regression cat起来是把不同图片的labels和回归参数都摞起来,一起处理.​ 正负样本都会计算类别损失.​ 而回归参数损失只计算正样本的,所以这里需要用sampled_pos_inds_subset记录正样本的位置.同时还需要对box_regression进行reshape处理,因为regression参数针对每个类别都会有四个参数. 最后使用smoothL1Loss进行正样本的回归参数损失计算.postprocess_detections 在预测模式下，将通过此函数得到最后的预测结果。具体流程见下图；具体操作见源码(带注释)
整体思路​ 创建DataSet首先需要继承torch.utils.data.Dataset这个类，然后再init函数中完成数据的一些预处理，比如xml文件的解析/类与序号的映射/图片路径的存储等。​ 接下来需要重载__len__和__getitem__两个方法,分别返回数据长度和某个序号对应的图片(包括图片本身和标注)如果用到多GPU训练,按照Pytorch官方的建议,最好再实现get_height_and_wight这个方法,节约内存.(因为这样可以避免pytorch将所有图片读入计算宽高)源码细节1. xml解析​ 在init方法中调用了parse_xml_to_dict方法解析xml文件,获取其中的object信息.(物体的类别/位置/边界框)​ 而parse_xml_to_dict具体使用递归的方法遍历标签信息,返回字典类型的数据2.__getitem__方法​ 首先通过上述的给出的xml解析方法解析图片对应的xml文件,将结果存入data变量.图片也通过Image.open打开​ 接下来将data中的边界框和类别数据进行读取,丢到boxes和labels列表中.之后注意将这些数据转换成Tensor类型​ 最后将信息都整理到target中,作为整体的标签返回.最后还需要判断是否对图片进行data augmentation3.Transform​ transform有很多类型,这里简单介绍一下水平翻转的实现.需要注意的是图片翻转之后,边界框的标注位置也需要翻转.​ 对于水平翻转: y坐标不需要改变,xmax变为width-xmin,xmin变为width-xmax4.collate_fn​ 为了之后实现dataloaer,这里需要实现collate_fn函数.​ 不同于分类网络中dataset只返回一张图片和一个label(形式比较固定),目标识别网络中需要返回图片加标注,而标注是不等长的,使用默认的stack有可能出现问题.所以需要手动用collate_fn方法进行堆叠.下图是dataloader的实现,这里传入了collate_fn.不传入这个参数默认使用torch.stack()对__getitem__的每个返回值进行堆叠​
源码阅读|Faster RCNN(三)——GeneralizedRCNNTransform
整体思路​ GeneralizedRCNNTransform主要用在图像进入backbone网络前的预处理以及预测结果输出时的后处理两个阶段.主要工作是图像的标准化处理以及resize操作.函数细节__init__​ __init__函数主要输入图像的均值和方差,以及resize时图片的最小(大)边长范围normalize​ 最后一行通过添加None这个维度可以增加一维维度,再利用广播机制对image的每个像素都进行操作.resize​ 这个方法首先调用_resize_image使用双线性插值调整图片大小,再通过resize_boxes调整对应的box大小._resize_image​ 根据宽高限制来确定缩放比例,调用interpolate对图像进行双线性插值,这里在image又添加一个维度,是因为interpolate方法输入需要是4D图像resize_boxes​ 按照缩放比例调整box坐标即可.这里torch.stack()会在tensor最后新增一个维度,这里就是在最后一个维度摞起来batch_images​ 这个方法是将一个batch图像中再次resize到统一尺寸,加速训练.这个统一尺寸被调整为size_divisible的整数倍​ 具体实现时寻找一个batch中图片的高宽最大值,以此作为最大图像.其他图像跟该图像做左上角对齐,空余位置填充零.​ 这种方法的好处是保证了图像的比例.forward​ 对于每张图片,以此调用normalize和resize方法进行标准化和缩放.而在进行batch_images前,需要记录当前图像尺寸,存入image_sizes_list,最后与image打包成一个list,跟target标注一起返回.​ 之所以要这么做是因为经过batch_images后,图像变成统一尺寸,但是图像有效区域在原先的图片大小范围内,所以需要保存batch_resize前的图像大小.postprocess​ 这个方法是预测模式下最后的后处理操作.
Faster RCNN框架图图源： deep-learning-for-image-processing/pytorch_object_detection/faster_rcnn at master · WZMIAOMIAO/deep-learning-for-image-processing (github.com)源码主要内容​ Faster R-CNN源码阅读将从以下几个方面展开，详见其他文档DataSet网络框架GeneralizedRCNNTransformRPNPredict Header正负样本划分与采样Loss函数PostProcessChange Backbone(with FPN)环境配置Python 3.6/3.7/3.8Pytorch>=1.6.0pycocotoolsUbuntu or CentosUse Gpu to train modelmore details see requirements.txt文件结构 ├── backbone: 特征提取网络，可以根据自己的要求选择 ├── network_files: Faster R-CNN网络（包括Fast R-CNN以及RPN等模块） ├── train_utils: 训练验证相关模块（包括cocotools） ├── my_dataset.py: 自定义dataset用于读取VOC数据集 ├── train_mobilenet.py: 以MobileNetV2做为backbone进行训练 ├── train_resnet50_fpn.py: 以resnet50+FPN做为backbone进行训练 ├── train_multi_GPU.py: 针对使用多GPU的用户使用 ├── predict.py: 简易的预测脚本，使用训练好的权重进行预测测试 ├── validation.py: 利用训练好的权重验证/测试数据的COCO指标，并生成record_mAP.txt文件 ├── coco.json: coco数据集标签文件 └── pascal_voc_classes.json: pascal_voc标签文件预训练权重MobileNetV2 weights(下载后重命名为mobilenet_v2.pth，然后放到bakcbone文件夹下): https://download.pytorch.org/models/mobilenet_v2-b0353104.pthResnet50 weights(下载后重命名为resnet50.pth，然后放到bakcbone文件夹下): https://download.pytorch.org/models/resnet50-0676ba61.pthResNet50+FPN weights: https://download.pytorch.org/models/fasterrcnn_resnet50_fpn_coco-258fb6c6.pth注意在源码中修改对应模型的路径与名称数据集(以PASCAL VOC2012为例)Pascal VOC2012 train/val数据集下载地址：http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar如果不了解数据集或者想使用自己的数据集进行训练，请参考:https://b23.tv/F1kSCK使用ResNet50+FPN以及迁移学习在VOC2012数据集上得到的权重: 链接:https://pan.baidu.com/s/1ifilndFRtAV5RDZINSHj5w 提取码:dsz8训练确保提前准备好数据集确保提前下载好对应预训练模型权重若要训练mobilenetv2+fasterrcnn，直接使用train_mobilenet.py训练脚本若要训练resnet50+fpn+fasterrcnn，直接使用train_resnet50_fpn.py训练脚本若要使用多GPU训练，使用python -m torch.distributed.launch --nproc_per_node=8 --use_env train_multi_GPU.py指令,nproc_per_node参数为使用GPU数量如果想指定使用哪些GPU设备可在指令前加上CUDA_VISIBLE_DEVICES=0,3(例如我只要使用设备中的第1块和第4块GPU设备)CUDA_VISIBLE_DEVICES=0,3 python -m torch.distributed.launch --nproc_per_node=2 --use_env train_multi_GPU.py注意事项在使用训练脚本时，注意要将--data-path(VOC_root)设置为自己存放VOCdevkit文件夹所在的根目录由于带有FPN结构的Faster RCNN很吃显存，如果GPU的显存不够(如果batch_size小于8的话)建议在create_model函数中使用默认的norm_layer， 即不传递norm_layer变量，默认去使用FrozenBatchNorm2d(即不会去更新参数的bn层),使用中发现效果也很好。训练过程中保存的results.txt是每个epoch在验证集上的COCO指标，前12个值是COCO指标，后面两个值是训练平均损失以及学习率在使用预测脚本时，要将train_weights设置为你自己生成的权重路径。使用validation文件时，注意确保你的验证集或者测试集中必须包含每个类别的目标，并且使用时只需要修改--num-classes、--data-path和--weights-path即可，其他代码尽量不要改动
摘要​ Faster RCNN在Fast RCNN的基础上增加了RPN网络来代替比较耗时的Selective Search。RPN网络和检测网络共享同一张卷积特征图，他同时预测每个位置的前景（背景）概率和每一类相对于锚框的偏移，得到的预测框将送入Fast RCNN的检测头进行进一步的分类和BBOX回归。​ 对于非常深的VGG-16模型，Faster RCNN在GPU上的帧速率为5fps（包括所有步骤），同时在PASCAL VOC 2007、2012和MS COCO数据集上实现了最先进的对象检测精度，每张图像只有300个建议框。在ILSVRC和COCO 2015竞赛中，Faster R-CNN和RPN是多个赛道第一名获奖作品的基础。算法流程RPN网络结构正向传播对于特征图上的每个3x3的滑动窗口(实际实现就是用conv3x3 p1 s1)，首先计算窗口中心点在原图上的位置，并计算出k个anchor box​ 对于anchor的选择，共有三种比例，三种尺度，即每个位置都有9个anchor感受野​ 对于特征图上的感受野，骨干网络为ZFNet时，感受野为171；骨干网络为VGG16时，感受野为228(具体计算如下)有个问题是，无论是VGG/ZF骨干网络，一个位置的感受野最多也就是228x228，那为什么anchor box的尺寸可以设置到256x256甚至512x512？ 在论文中解释是说，通过物体的局部来预测物体是有可能的，实际上这么设置表现也确实有所提升（anchor->proposal正负样本选择​ 训练时，每个mini-batch，即每张图片随机选择256个anchor，这些anchor中正负样本比例大约是1:1。如果正样本数目不足128，那就用负样本补足到256.​ 正负样本定义如下：​ 正样本：①和某个GT Box的IoU大于0.7​ ②是和某个GT Box的IoU最大的anchor(就是对第一条规则的补充，避免出现某个GT Box没有分配anchor)​ 负样本：和所有的GT Box的IoU都小于0.3RPN 损失​ RPN网络的损失分为两部分，分类损失和边界框损失。分类主要是衡量预测框中是否含有物体(类似于YOLO系列中的objectness)，边界框损失就是简单的回归损失。​ 分类损失有两种实现：​ 如果每个anchor只有一个预测值，那么就使用BCE二元交叉熵损失；​ 如果每个anchor有两个预测值，那么就使用Softmax Cross Entropy。​ 边界框回归损失和Fast RCNN一样，都是用SmoothL1 Loss.标注值t*的计算参考RCNN中的实现。Detect Loss​ 检测网络的损失和Faster-RCNN网络的损失一摸一样。这里不再赘述。网络训练现在可以直接采用RPN loss+Fast R-CNN Loss联合训练网络自己的一点理解​ 从损失函数来看，Faster R-CNN和YOLO挺像的(应该是YOLO借鉴的ROI思想)。​ 在YOLO系列中，对于每个grid cell会预测k个anchor(since v2)，对于每个anchor会预测一个置信度(objectness)，边界框偏移和类别预测。这里的类别预测同v1一样，是条件概率，即在置信度基础上的概率。​ 而在Faster R-CNN中，置信度和类别预测被拆开成了两个网络，RPN网络负责预测置信度和边界框偏移；Fast R-CNN网络负责预测类别和边界框偏移。​ RPN网络提供Proposal，其实就是在置信度的基础上筛选出一些质量高的锚框(当然还需要根据边界偏移调整获得最后的region proposal)。​ Fast R-CNN接到Proposal后，根据它在特征图上的映射获得特征矩阵，再通过RoI pooling层获得获得固定大小的矩阵，送入全连接层预测类别和bbox回归。那么这里的类别同样可以理解成条件概率，因为得到的候选框是经过RPN网络筛选过的，即在置信度满足一定条件时预测出的概率。​ 所以YOLO系列其实就是将RPN网络和Fast R-CNN网络进行了合并。至于锚框生成，区别于RPN网络在特征图上通过滑动窗口生成，YOLO更加简单粗暴，直接将原图分割并在每个单元上生成锚框。