四个步骤:
Fast RCNN相比于RCNN改进了3点:
作为 torchvision 中目标检测基类,GeneralizedRCNN 继承了 torch.nn.Module,后续 FasterRCNN 、MaskRCNN 都继承 GeneralizedRCNN。
4个步骤:
将 transform 后的图像输入到 backbone 模块提取特征图
#GeneralizedRCNN.forward(…)
features = self.backbone(images.tensors)
backbone 一般为 VGG、ResNet、MobileNet 等网络。
然后经过 rpn 模块生成 proposals 和 proposal_losses
#GeneralizedRCNN.forward(…)
proposals, proposal_losses = self.rpn(images, features, targets)
接着进入 roi_heads 模块(即 roi_pooling + 分类)
#GeneralizedRCNN.forward(…)
detections, detector_losses = self.roi_heads(features, proposals, images.image_sizes, targets)
最后经 postprocess 模块(进行 NMS,同时将 box 通过 original_images_size映射回原图)
#GeneralizedRCNN.forward(…)
detections = self.transform.postprocess(detections, images.image_sizes, original_image_sizes)
对于普通的 FasterRCNN 只需要将 feature_map 输入到 rpn 网络生成 proposals 即可。但是由于加入 FPN,需要将多个 feature_map 逐个输入到 rpn 网络。
class AnchorGenerator(nn.Module):
def generate_anchors(self, scales, aspect_ratios, dtype=torch.float32, device="cpu"):
def set_cell_anchors(self, dtype, device):
首先,每个位置有 5 种 anchor_size 和 3 种 aspect_ratios,所以每个位置生成 15 个 base_anchors:
[ -23., -11., 23., 11.] # w = h = 32, ratio = 2
[ -16., -16., 16., 16.] # w = h = 32, ratio = 1
[ -11., -23., 11., 23.] # w = h = 32, ratio = 0.5
[ -45., -23., 45., 23.] # w = h = 64, ratio = 2
[ -32., -32., 32., 32.] # w = h = 64, ratio = 1
[ -23., -45., 23., 45.] # w = h = 64, ratio = 0.5
[ -91., -45., 91., 45.] # w = h = 128, ratio = 2
[ -64., -64., 64., 64.] # w = h = 128, ratio = 1
[ -45., -91., 45., 91.] # w = h = 128, ratio = 0.5
[-181., -91., 181., 91.] # w = h = 256, ratio = 2
[-128., -128., 128., 128.] # w = h = 256, ratio = 1
[ -91., -181., 91., 181.] # w = h = 256, ratio = 0.5
[-362., -181., 362., 181.] # w = h = 512, ratio = 2
[-256., -256., 256., 256.] # w = h = 512, ratio = 1
[-181., -362., 181., 362.] # w = h = 512, ratio = 0.5
由于有 FPN 网络,所以输入 rpn 的是多个特征。为了方便介绍,以下都是以某一个特征进行描述,其他特征类似。
假设有h * w的特征,首先会计算这个特征相对于输入图像的下采样倍数 stride:
比如原图800*800大小的图片,输出特征图50*50,stride则为800/50=16
#AnchorGenerator.grid_anchors(...)
shifts_x = torch.arange(0, grid_width, dtype=torch.float32, device=device) * stride_width
shifts_y = torch.arange(0, grid_height, dtype=torch.float32, device=device) * stride_height
shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
然后将 base_anchors 的中心从(0,0)移动到网格的点,且在网格的每个点都放置一组 base_anchors。这样就在当前 feature_map 上有了很多的 anchors。
stride 代表网络的感受野,网络不可能检测到比 feature_map 更密集的框了!所以才只会在网格中每个点设置 anchors(反过来说,如果在网格的两个点之间设置 anchors,那么就对应 feature_map 中半个点,显然不合理)。
#AnchorGenerator.grid_anchors(...)
anchors.append((shifts.view(-1, 1, 4) + base_anchors.view(1, -1, 4)).reshape(-1, 4))
#RPN
如果输入3*600*800的图像,经过Backbone网络,降采样率为16,输出512*37*50(600/16=37.5,800/16=50)的特征图feature map.
经过RPNhead的3*3卷积神经网络维度还是512*37*50,对于分类网络分支,1x1卷积输出18*37*50的特征,对于回归网络分支,1x1卷积输出36*37*50的数据。
18是因为在RPN中的分类网络部分只是判断有无物体,即正负样本,不进行物体类别分类,所以分类结果只有两种(0和1),若每个特征点生成9个锚框Anchor,2*9=18.
回归网络中每个anchor对应四个数据,tx、ty、tw、th,因此9x4=36。
RPN中的AnchorGenerator 生成37*50*9=16650个锚框Anchors,有一些锚框超出了边界,因此保留边框内部部分。
RPN中分类网络部分:通过计算Anchor与标签的IOU来判断是正样本还是负样本,具体实现时,需要计算每一个Anchor与每一个标签的IOU,因此会得到一个IOU矩阵。判断标准如下:
(1) 对于任何一个Anchor,与所有标签的最大IOU小于0.3,则视为负样本。
(2) 对于任何一个标签,与其有最大IOU的Anchor视为正样本。
(3) 对于任何一个Anchor,与所有标签的最大IOU大于0.7,则视为正样本。
由于Anchor的数量接近2万,并且大部分时背景,因此需要筛选一部分,默认选择256个Anchor。
RPN的回归偏移真值:得到anchor相对于target框的偏移量后,保存在bbox_tarets中,损失函数通常使用smooth函数,smooth函数结合了1阶与2阶损失函数,原因在于,当预测偏移量与真值差距较大时,使用2阶函数时导数太大,模型容易发散而不容易收敛,因此大于1时采用导数较小的1阶段损失函数。
RPN的非极大值抑制(NMS)将重叠的框去掉,最后再次根据RPN的预测得分选择前2000个,作为最终的Proposal,输出到下一阶段。这里得到2000个候选框还是太多,继续Select Sample,筛选出的正负样本总数为256,筛选标准如下:
(1)对于任何一个proposal,其与所有标签的最大IOU如果大于等于0.5,则视为正样本。
(2)对于任何一个proposal,其与所有标签的最大IOU如果大于等于0且小于0.5,则视为负样本。
为了控制正负样本比例基本满足1:3,在此正样本数量不超过64,超过了则随机选取64个。
网络输出
2. RCNn部分的损失函数计算方法与RPN相似,只不过分类类别有2变为21,回归则是至多有64个正样本参与回归计算,负样本不参与回归计算。
[外链图片转存中…(img-ujOc2x2C-1699491243408)]