本文主要是介绍Facebook,Kaiming He的PointRend我的阅读笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Point Rend笔记
PointRend模块的提出是为了解决Mask预测时候对于原图或feature map上的像素的过采样和欠采样问题。这个问题的核心在于,由于2d图像本身是以栅格化的形式保存图像,那么网络在预测Mask的时候,不可避免地会将所有像素都有同等地位地进行预测,而实际上我们更希望的是网络集中预测在物体边缘部分的地方,而物体内部则不需要过多地预测,那么前者就是欠采样,后者则是过采样。
PointRend方法的思路借鉴于计算机图形学中理论,将图像分割任务当做是一个渲染问题,并提出了PointRend神经网络模块。该模块能够通过迭代细分算法,自适应选取位置进行基于点分割预测。这一模块可以应用在已有的实例分割和语义分割的模型上,从而增强预测Mask的精细度和降低,并能让分割输出更高的分辨率。
原有问题
- 1 常规的网格处理图形会导致在平滑区域的不必要的过采样,同时在物体边缘却造成欠采样。这导致在平滑区不必要的计算和生成模糊的边缘轮廓。
- 2 之前为了平衡上述的欠采样和过采样问题,往往会输出比原有图形更小的,例如语义分割为原图 1 / 8 1/8 1/8等。
创新点
- 1 借鉴CG中的经典思想,设计出PointRend神经网络模块,使用了细分策略,自适应地选择非统一采样的点(像素)子集来进行标签预测。
- 2 细分策略的使用能够让预测Mask分辨率更高,降低同等尺寸下的运算成本。
- 3 PointRend模块可以输入一个或多个标准网格的CNN的feature map,输出一个定义在一个更好的网格、更高分辨率的结果。
- 4 PointRend模块只会在选取过的点上进行预测。为了在这些点上进行预测,PointRend模块会在选取的点上,通过插值函数 f f f方式提取逐点特征表示,然后使用叫做point head的小型子网络在得到的逐点特征表示上进行标签预测。
方法
渲染
计算机图形学中渲染是讲一个三维的模型显示到一个二维的均匀网格的像素图上。虽然输出是均匀网格的数据,但是其背后的物理模型是连续表示的,通过一定的方法,可以计算出这个物体上的各种物理遮挡、属性表现出来的颜色、性状等。
具体做法
将一个图像的分割任务的真实分割Mask,当做是某个连续模型(即渲染中的实际三维模型)的遮挡图(在这里是假设也是连续的),而输出的Mask则是渲染的以二维均匀网格表示的图形。上述的连续模型就是网络中输出的feature map,而feature map的连续性可以通过插值的方式(文中使用二次线性插值)近似的到各个位置的值来近似。在这里则使用一个小型的参数化函数进行得到的feature map上(插值后得到的)逐像素的计算预测值,相当于渲染中对每个像素点值的物理或几何计算方法(如光线追踪).总体上,Mask在整个过程中如同渲染一样,从粗糙到精细的一个过程。
根据上面思路,PointRend模块组成:
- 输入:CNN的feature map, f ∈ R C × H × W f \in \mathbb{R}^{C\times H\times W} f∈RC×H×W(均匀网格像素,网格大小比原图大4到16倍)
- 输出:预测map, p ∈ R K × H ′ × W ′ p \in \mathbb{R}^{K\times H^{'}\times W^{'}} p∈RK×H′×W′(一般分辨率比输入高)
- 1 选取点:点选择阶段,选择一部分 f f f中的像素并提取其值进行下一步处理。
- 2 特征提取:通道间逐点(point-wise)特征提取,并通过双线性插值 f f f得到特征值,这样可以提取 f f f通道间的信息用于后序预测。
- 3 预测:point head网络处理,point head是一个小型的神经网络,训练后用于预测该点的label(使用的是2中提取的特征,并且每个点上label都是单独预测的)。
接下来将会详细叙述上面三个主要组成部分的工作方法。
自适应选取点
PointRend的核心在于如何选取点,处于物体边缘的点,高频域的点需要重点关注。
-
预测过程中的选取点:在输出达到目标分辨率前,重复以下循环:
- 1 使用双线性插值法,升采样(upsample)输入map,然后这个map中选择N个预测中最不确定的点(例如二值Mask中置信度接近0.5的点)
- 2 point-wise特征表示计算,并预测其label
-
训练过程中的选取点:如果按照预测过程中的方式为训练point head选取点,其会导致引入了迭代环节,从而影响BP算法的实现,所以训练时选取的是非迭代方式的随机采样选取点。这一随机取样遵循:偏向选取不确定的部分,同时在较为确定的部分有一定采样。假设需要选取 N N N个点给point head进行训练。具体步骤为:
- 从均匀分布中生成 k N kN kN个点( k k k为可调参数)
- 从 k N kN kN个点,通过计算每个点的不确定性估计值(根据任务设计这个值的计算方式),挑选其中前 β N \beta N βN个点( β ∈ [ 0 , 1 ] \beta \in [0,1] β∈[0,1])。在本文实现中,不确定性估计值计算的是,0.5与真实类别标签(GT)的粗略预测map(输入或从上一次迭代中得到的)插值而来的值的距离,即 ∣ 0.5 − B i l i n e a r ( p ( k ) ) [ i , j ] ∣ \vert {0.5 - Bilinear(p(k))[i,j]} \vert ∣0.5−Bilinear(p(k))[i,j]∣, i , j i,j i,j为插值后像素点位置, p ( k ) p(k) p(k)为上一轮预测后的,作为输入的第k个feature map。
- 剩余的 ( 1 − β ) N (1-\beta) N (1−β)N个点从均匀分布中采样生成。
下图是展示了不同 k k k和 β \beta β值下的采样例子。
Point-wise表示(提取特征)
选取点后,通过双线性插值方式从featrue map得到feature vector,其中feature map可以是单张,也可以是多张,或FPN对应的位置,然后concatenate一起。
但是,这样操作存在两个问题:
- 1 因为这些特征不会包含区域特定信息,所以如果有两个重叠的目标,这时候在重叠位置点上,两个都会返回同一值,这样不利于预测重叠目标的标签(需要同时有两标签)
- 2 由于特征是为了更好地处理细节部分而提取的,所以自然只会和低级信息关联,而如果包含更多上下信息和语义信息会更有帮助
基于上述的考虑,可以从网络中加入粗糙的分割预测,如果通道传递的是不同类别的语义信息,那么粗糙分割预测则提供了全局性的上下信息。这样的粗糙的分割预测可以由如轻量化的Mask RCNN网络得到。
在Facebook的detectron2实现中,这一过程在detectron2/projects/PointRend/point_rend/point_featrues.py的point-sample函数中实现,使用的的是(除开加入粗糙的分割预测部分)torch.nn.functional.grid_sample(input, grid, mode=‘bilinear’, padding_mode=‘zeros’, align_corners=None)函数,源码如下所示:
def point_sample(input, point_coords, **kwargs):"""A wrapper around :function:`torch.nn.functional.grid_sample` to support 3D point_coords tensors.Unlike :function:`torch.nn.functional.grid_sample` it assumes `point_coords` to lie inside[0, 1] x [0, 1] square.Args:input (Tensor): A tensor of shape (N, C, H, W) that contains features map on a H x W grid.point_coords (Tensor): A tensor of shape (N, P, 2) or (N, Hgrid, Wgrid, 2) that contains[0, 1] x [0, 1] normalized point coordinates.Returns:output (Tensor): A tensor of shape (N, C, P) or (N, C, Hgrid, Wgrid) that containsfeatures for points in `point_coords`. The features are obtained via bilinearinterplation from `input` the same way as :function:`torch.nn.functional.grid_sample`."""add_dim = Falseif point_coords.dim() == 3:add_dim = Truepoint_coords = point_coords.unsqueeze(2)output = F.grid_sample(input, 2.0 * point_coords - 1.0, **kwargs)#hereif add_dim:output = output.squeeze(3)return output
在pytorch文档中,torch.nn.functional.grid_sample(input, grid, mode=‘bilinear’, padding_mode=‘zeros’, align_corners=None)的作用是根据给定的input的值,使用grid中提供的位置计算output。具体而言到4D的数据,有
输入
- i n p u t : ( N , C , H i n , W i n ) input:(N,C,H_{in},W_{in}) input:(N,C,Hin,Win)
- g r i d : ( N , H o u t , W o u t , 2 ) grid:(N,H_{out},W_{out},2) grid:(N,Hout,Wout,2)
输出 - o u t p u t : ( N , C , H o u t , W o u t ) output:(N,C,H_{out},W_{out}) output:(N,C,Hout,Wout)
对于每一个输出位置 o u t p u t [ n , : , h , w ] output[n,:,h,w] output[n,:,h,w]由输入的二维向量 g r i d [ n , h , w ] grid[n,h,w] grid[n,h,w]决定 i n p u t input input的位于 ( x , y ) (x,y) (x,y)的像素,这个像素将会使用某种插值方式得到输出 o u t p u t [ n , : , h , w ] output[n,:,h,w] output[n,:,h,w],而插值方式取决于函数参数中的mode选项,默认为双线性插值方式。
值得注意的是, g r i d grid grid中的需要采样像素的位置表示方式是使用 i n p u t input input中空间维度的归一化后得到的值,也就是说它 x , y x,y x,y的范围为 [ − 1 , 1 ] [-1,1] [−1,1],例如如果 x = − 1 , y = − 1 x=-1,y=-1 x=−1,y=−1,那么这个采样点应该是 i n p u t input input的最左上角的像素。假如超出了范围 [ − 1 , 1 ] [-1,1] [−1,1]的话,由函数参数padding_mode决定其值。
但pytorch没有说明是怎么计算的方法,其实,这一操作是来自论文Spatial Transformer Networks(https://arxiv.org/abs/1506.02025, Max Jaderberg. from Google)。接下来叙说是如何进行计算的。
Spatial Transformer Networks
Spatial Transformer Networks提出了一种让各种神经网络结构得到处理空间变换的的能力的方法,而这种变换是可以根据每个样本设计,而且不需要增添任何标记,能够动态地将图片或feature map变换,让有了这一空间变换模块的网络不仅能从图片中选取最相关区域,而且能将这些区域变换为能让后继网络更简单处理的输出。效果如下图所示:
spatial transformer是一个可微的模块,用于在前馈时给feature map作空间变换,而变换是根据特定输入而定的。输入可以是单张或多张map,而输出为单张map。spatial transfomer可分为三部分,根据计算顺序分别为:
- 1 localisation network根据输入的feature map得到输出spatial transformer中所需参数;
- 2 然后通过预测出来的这些参数来产生sampling grid(一组点集,是输入map的一些需要采样的点),从sampling grid中来产生后续变换后的输出(由grid generator 产生);
- 3 最后,feature map和sampling grid作为sampler的输入,sampler通过在输入中被取样的点上产生输出map。
可以其中变换写作矩阵形式(具体查看论文),或者是下式
V i c = ∑ n H ∑ m W U n m c k ( x i s − m ; Φ x ) k ( y i s − n ; Φ y ) ∀ i ∈ [ 1 … H ′ W ′ ] ∀ c ∈ [ 1 … C ] V_{i}^{c}=\sum_{n}^{H} \sum_{m}^{W} U_{n m}^{c} k\left(x_{i}^{s}-m ; \Phi_{x}\right) k\left(y_{i}^{s}-n ; \Phi_{y}\right) \forall i \in\left[1 \ldots H^{\prime} W^{\prime}\right] \forall c \in[1 \ldots C] Vic=n∑Hm∑WUnmck(xis−m;Φx)k(yis−n;Φy)∀i∈[1…H′W′]∀c∈[1…C]
其中 Φ x \Phi_{x} Φx和 Φ y \Phi_{y} Φy为generic sampling kernel k ( . ) k(.) k(.)的参数,这个 k ( . ) k(.) k(.)定义了图形插值方式(如双线性插值方法), U n m c U_{n m}^{c} Unmc为输入通道c位置 ( n , m ) (n,m) (n,m)的值, V i c V_{i}^{c} Vic为输出通道c上序列号i的像素值。下面的图可以比较直观地看出变换进行的操作,说明看原文的图注。
回到PointRend中,可以看出,Spatial Transformer的第一步已经被自适应取点取代,而后续中步骤则由pytorch中的torch.nn.functional.grid_sample所计算,而grid_sample中的参数mode则决定了Spatial Transformer第三步中generic sampling kernel,在PointRend中也就是双线性插值。最终产生了根据PointRend中自适应选点后变换的feature map。
Point head
得到了Point-wise表示的特征后,将其输入到简单的MLP(Multi-layer Perceptron)中进行label预测。MLP的权重是各个点都共享的。在本文实现中使用的是和R-CNN相似的两层1024宽的隐藏层,预测每一类的输出。
如果有理解错误,请多多包涵。
这篇关于Facebook,Kaiming He的PointRend我的阅读笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!