《IoUNet(6)_源码_RoIAlign(1)》

IoUNet(6)_源码_RoIAlign(1)

IoUNet(6)_源码_RoIAlign(1)

 

0 引言

IoUNet提出了全新的PrRoIPooling,本来是想专门学习下其源码就算了,但发现既然都走到这一步了,顺便把Fast RCNN中的RoIPooling、Mask RCNN中的RoIAlign一起做一下对比得了,后来就花时间慢慢在学习、回顾,不过又发现如果想说清楚RoIPooling,最起码得了解输入RoIPooling层的anchor是如何生成的,Fast RCNN是通过Selective Seatch获得的proposals,Faster RCNN通过RPN;如果真想一步步理清楚思路,那这条线不知道得回溯到什么程度了;

那就此打住了,就以Faster RCNN通过RPN生成proposals,再输入RoIPooling层开始讲,如果不了解RPN等概念的,就只能去参照Faster RCNN的论文了;---- Faster RCNN确实是太经典了,如果写笔记,讲十篇笔记也讲不完,所以我一直没敢下手记录;

 

1 IoUNet回顾

本笔记介绍IoUNet,IoUNet的结构如fig 5,PrRoIPooling的功能与RoIPooling类似,都是从尺度不一的proposals上,通过PrRoIPooling / RoIPooling层的操作,得到固定尺度(如7 x 7 x 256维度)的特征,PrRoIPooling相比RoIPooling,能在feature map上对RoI提取更精准的特征;

RoI Pooling、RoI Align、PrRoI Pooling可视化操作如fig 6:

RoI Pooling:RoI的连续坐标首先要做一遍离散的量化操作,也即分别求得(x1, y1)、(x2, y2)的下、上确界,再在修正后的bin内(fig 6中红色实线框),累加操作计算F上离散的特征值,做的最近邻采样操作;

RoI Align:为了消除RoI Pooling离散的量化错误,bin内的每个连续点(fig 6中红色点(ai, bi))都需要计算该点至N = 4的上、下、左、右四个离散点范围内的双线性插值操作,也即将公式(3)对每个连续点(ai, bi)都操作一次;从中可知RoI Align中参数N是预定义好的,表示连续点(ai, bi)附近的4个离散点,无法根据bin的尺度做自适应调整;

PrRoI Pooling:基于公式(2),直接在连续的feature map上通过公式(3)的二阶积分方式计算bin的特征值;此外,PrPool(Bin, F)对于bin的坐标是连续可微 + 可导的;

PrPool(B, F)对x1的偏导数可计算如公式(4):

其他坐标的偏导计算方式PrPool(bin,F)与公式(4)类似,PrPool避免了做bin坐标的量化,因此PrPool的计算过程是连续可微的,进而可以得到更精准的特征提取结果;

 

2 RoI Align源码学习

2.1 RoI Pooling中存在的问题

先看看RoI Pooling中存在哪些不足:

在2-stage的目标检测算法,如Fast RCNN、Faster-RCNN中,ROI Pooling 的作用是根据proposals的位置坐标在feature map中将相应RoI区域pooling为固定尺寸的feature,以便进行后续的cls和bbox reg操作;由于proposals的位置通常是由模型回归得到的,一般来讲是float型数据,而池化后的feature得到的是尺寸固定特征(可以理解为int型数据,如6 x 6 x 256),故ROI Pooling操作过程中存在两次离散性的量化过程:

1 将proposals的边界量化为整数点坐标值;

2 将量化后的边界区域平均分割成 k x k 个bin,对每一个bin的边界进行量化;

 

经过上述两次离散性量化,此时的proposals已经和最开始回归出来的位置有一定的偏差,这个偏差会影响目标检测或者分割的准确度,在Mask RCNN论文里,作者把它总结为:不匹配问题(misalignment);结合roi_pooling_layer.cpp中的源码,举个栗子:

1 输入一张800 x 800的图片,图片上有一个350.2 x 350.2 pix的proposals(经过bbox reg后得到的float bbox),图片经过主干网络提取特征后,feature map的stride = 16,因此,图像和proposals的边长都是输入时的1/16:800正好可以被16整除变为50,但350.2 / 16后得到21.89,带有小数,于是ROI Pooling的第一次量化操作,结合对bbox坐标的round函数,将bbox的坐标量化为整数int型;----- 通过.cpp代码的Forward_cpu函数可知,对每个RoI的坐标做round量化后,得到的proposals尺度自然会变为int型,至于是21,还是22,需要结合proposals在feature map中的坐标而定(因为是按照坐标来做round操作的,而非按照RoI本身的尺度);----- 对应第一次量化;

2 然后需要把RoI的特征池化6 x 6的大小,因此将上述RoI平均分割成6 x 6个bin,显然,每个矩形区域的边长为3.67(以 RoI尺度为22 pix为例),又含有小数,于是RoIPooling要实施第二次量化操作,具体地,每个bin的stride = 3.67,例如对grid中第一行第二列的bin,其水平方向bin的边界坐标为(coord_xs + 1 x 3.67, coord_xs + 2 x 3.67),coord_xs表示整个RoI的左上角x轴坐标,可以发现bin的左右边界x轴坐标都是float型,通过.cpp代码的Forward_cpu函数可知,需要使用到ceil、floor函数做bin坐标的量化,就对应着在IoUNet的fig 6中,RoIPooling中那个实线空色框是做了坐标的 “向外” 扩张取整;

经过这两次量化,候选区域已经出现了较明显的偏差,更重要的是,该层特征图上0.1个像素的偏差,缩放到原图就是1.6个像素,直接扩大了16倍,这样就比较麻烦了;

那么RoIAlign就是为了缓解该问题,将每个bin位置上的取值从最近邻插值转为双线性插值,一定程度上缓解该问题;

 

2.2 RoIAlign的理解

还是得结合图来对比下RoI Pooling与RoI Align:

以上是RoI Pooling的操作,配合2.1小节中笔记,应该很容易理解了:整个RoI Pooling过程中涉及的两次量化分别发生在1:对ROI坐标和2:对ROI划分为等大子区域( bin )上的量化;

 

ROI Align很好地解决了ROI Pooling操作中两次量化造成的区域不匹配( mis-alignment )的问题,Mask R-CNN论文的实验显示,在检测测任务中将 ROI Pooling 替换为 ROI Align 可以提升检测模型的准确性;

具体地,ROI Align从ROI Pooling局限性的源头上进行了改进,也即取消了两次离散量化操作,使用双线性插值获得坐标为浮点数的像素点上的图像数值,从而将整个特征聚集过程转化为一个连续的操作;值得注意的是,ROI Align并不需要对两步量化中产生的浮点数坐标的像素值都进行计算,而是设计了一套优雅的流程,ROI Align具体操作为:

1 遍历每一个RoI区域,保持浮点数边界不做量化;

2 将RoI区域分割成k x k个bin,每个bin的边界也不做量化;

3 在每个bin中计算固定四个坐标位置,用双线性插值方式计算出这四个位置的值,然后进行max-pooling操作;----- 固定位置就对应下图中每个bin内的黑色点;

ROI Align的实现如上图,虚线框表示feature map,实线框表示一个RoI,Pooling输出大小为2 x 2(也即grid为2 x 2共4个bin),每个bin中包含四个采样点;

ROI Align基于双线性插值的方法,利用feature map上距离采样点(对应上图每个bin内的黑色点)最近的四个像素(对应上图虚线框内各个虚线grid的交叉点,相当于每个pix上的像素点)得到其像素值,整个过程中没有对ROI,均分ROI产生的bins和采样点进行量化;每个bin中采样点的数目和位置存在一定的规则,即若采样点数为1,则该点位于bin的中心位置;若采样点数为4,则采样点的位置为均分该bin为4个小矩形后各自的中心点;通常这些采样点的坐标为浮点数,所以需要用到插值的方法获得其像素值;

事实上,ROI Align 在遍历取样点的数量上没有ROIPooling那么多(只需要求每个采样点附近4个点的双线性插值结果),但却可以获得更好的性能,这主要归功于解决了mis-alignment的问题;

以上图做简要说明:

1 主干网:VGG16,stride = 32,原图800 x 800,最后一层特征图feature map大小:25 x 25 pix;

2 假定原图中有bbox reg后的proposal,大小为665 x 665 pix,这样,映射到feature map中的大小:665 / 32 = 20.78,即20.78 x 20.78 pix;----- 此处取消RoI Pooling中的第一次量化操作,不做bbox坐标的取整,而是保留浮点数;

3 假定pooled_w = 7,pooled_h = 7,即pooling后固定成7 x 7 grid的特征图,将在 feature map上映射的20.78 x 20.78 pix的region proposal划分成49个同等大小的bin,每个bin的大小20.78 / 7 = 2.97 pix,即2.97 x 2.97尺度的bin;----- 此处取消RoI Pooling中的第二次量化操作,bin的坐标、尺度不做取整操作,而是保留浮点数;

4 假定采样点数为4,即表示,对于每个2.97 x 2.97的bin,平分四份小矩形,每一份取其中心点位置,而中心点位置的像素,采用双线性插值法进行计算,这样就会得到四个点的像素值(对应上图fig 3);

5 最后,取四个像素值中最大值(max-pooling)作为这个bin的像素值,如此类推,同样是49个bin得到49个像素值,组成7 x 7大小的feature map;

6 以上操作针对single-channel,扩展到multi-channel操作类似;

 

2.3 RoIAlign的反向传播

ROI Pooling的bp反向传播公式如下:

xi:池化前特征图上的像素点;

y_rj:池化后的第r个候选区域的第j个点;

i*(r,j):点y_rj像素值的来源(最大池化时候,选出的最大像素值所在点的坐标位置索引);

由上式可以看出,只有当池化后某一个点的像素值在池化过程中采用了当前点Xi的像素值(即满足i=i*(r,j)),才在xi处回传梯度;

类比于ROIPooling,ROIAlign的反向传播需要作出稍许修改:首先,在ROIAlign中,xi *(r,j)是一个浮点数的坐标位置(前向传播时计算出来的采样点),在池化前的特征图中,每一个与 xi*(r,j) 横纵坐标距离均小于1 pix的点( 即双线性插值时特征图上的四个点 )都应该接受与此对应的点yrj回传的梯度,故ROI Align 的反向传播公式如下:

d( . ):两像素点之间的距离;

Δh、Δw:xi 与 xi*(r,j) 横纵坐标的差值,这里作为双线性插值的权重系数乘在原始的梯度上;

 

2.4 RoIAlign的Forward实现

我们参照caffe2的RoIAlign官方代码学习下,看看的头文件pytorch/caffe2/operators/roi_align_op.h:

// Copyright 2004-present Facebook. All Rights Reserved.
#ifndef ROI_ALIGN_OP_H_
#define ROI_ALIGN_OP_H_

#include "caffe2/core/context.h"
#include "caffe2/core/logging.h"
#include "caffe2/core/operator.h"

namespace caffe2 {

template <typename T, class Context>
class RoIAlignOp final : public Operator<Context> {
 public:
  RoIAlignOp(const OperatorDef& operator_def, Workspace* ws)
      : Operator<Context>(operator_def, ws),
        order_(StringToStorageOrder(this->template GetSingleArgument<string>("order", "NCHW"))),
        spatial_scale_(this->template GetSingleArgument<float>("spatial_scale", 1.)),
        pooled_height_(this->template GetSingleArgument<int>("pooled_h", 1)),
        pooled_width_(this->template GetSingleArgument<int>("pooled_w", 1)),
        sampling_ratio_(this->template GetSingleArgument<int>("sampling_ratio", -1)) {
    DCHECK_GT(spatial_scale_, 0);
    DCHECK_GT(pooled_height_, 0);
    DCHECK_GT(pooled_width_, 0);
    DCHECK_GE(sampling_ratio_, 0);
    DCHECK(order_ == StorageOrder::NCHW || order_ == StorageOrder::NHWC);
  }
  USE_OPERATOR_CONTEXT_FUNCTIONS;

  bool RunOnDevice() override {
    CAFFE_NOT_IMPLEMENTED;
  }

 protected:
  StorageOrder order_;   // 数据存储格式
  float spatial_scale_;   // 采样stride,如 1 / 16;
  int pooled_height_;     // RoIAlign后的grid数,如6 x 6 grid
  int pooled_width_;      // RoIAlign后的grid数,如6 x 6 grid
  int sampling_ratio_;    // 每个bin内高和宽方向的采样率,论文中默认是2,即每个bin采样2*2=4个点
};

} // namespace caffe2

#endif // ROI_ALIGN_OP_H_
编辑于 2018-10-16
原文地址:https://www.cnblogs.com/cx2016/p/13023204.html