欢迎访问个人网络日志知行空间
论文:Oriented R-CNN for Object Detection
代码:OBBDetection
西北工业大学的Xingxing Xie等于2021年08月提交到ICCV 2021
的论文
与水平检测框不同,旋转检测框会给出对象的minAreaRect
,是带角度的物体检测框。结合水平检测框的方法,旋转框检测常用的方法有以下几种。Rotated Region Proposal Network,参考Faster RCNN
中提出的RPN
,Rotated RPN
在每个特征图上使用3种scale
,3种ratio
,6种angle
共计54
个锚框。这种方法可以提高对象检测的召回率,但是因引入过多的anchor
,譬如在80x80x54
的特征图上会生成345600
个候选框,会导致占用过多的内存和推理时间。RoI Transformer使用RPN, RoI Alignment and regression
来生成Oriented Proposals
,这种方法相比Rotated RPN
减少了参数量,但依然存在额外的计算量。Oriented RPN,本文提出的方法,使用本文提出的中点偏移Midpoint Offset
旋转框表示法,将RPN
输出Proposals
的位置回归分支由4个变量改成6个变量来生成候选框,相比前两种方法,该方法使用全卷积的RPN
,具有更少的运算量。
w w w和 h h h是旋转框的外接矩形框的宽高, x , y x,y x,y是旋转框的外接矩形框的中心的坐标, △ α \triangle \alpha △α和 △ β \triangle \beta △β是旋转矩形框顶点偏离中心的距离。旋转矩形框的4个顶点坐标为:
通过这种表示方式,巧妙的将角度问题表示成了距离,可以使用 L 1 L_1 L1 loss来直接回归,规避了多边形IoU
计算的不可导和角度表示时的边界问题。
Oriented R-CNN
的整体架构如上图,与Faster R-CNN
中的结构基本一样,除了RPN
结构位置回归分支输出的回归变量由4个 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)变成了6个 ( x , y , w , h , △ α , △ β ) (x,y,w,h,\triangle \alpha, \triangle \beta) (x,y,w,h,△α,△β),输出的是Oriented Proposals
。在检测头部分根据proposals
做RoI Alignment
时,因Oriented RPN
生成的是旋转的候选框,因此做RoI Align
时,需要先对RoI
做旋转,因此称之为RotatedRoIAlign
。
Oriented RPN
中使用的仍然是水平anchor
,每层特征图上共3个,宽高比分别为 1 : 2 , 1 : 1 , 2 : 1 1:2,1:1,2:1 1:2,1:1,2:1,在 { P 2 , P 3 , P 4 , P 5 , P 6 } \{P_2,P_3,P_4,P_5,P_6\} {P2,P3,P4,P5,P6}上锚框anchor
的面积分别为 { 3 2 2 , 6 4 2 , 12 8 2 , 25 6 2 , 12 8 2 } \{32^2,64^2,128^2,256^2,128^2\} {322,642,1282,2562,1282}个像素。
每个anchor使用四维向量表示:
a = ( a x , a y , a w , a h ) a = (a_x,a_y,a_w,a_h) a=(ax,ay,aw,ah)
其中 ( a x , a y ) (a_x,a_y) (ax,ay)表示锚框中心, ( a w , a h ) (a_w,a_h) (aw,ah)表示锚框的宽和高。
Oriented RPN
的输出是proposals
相对于anchor
的偏移量,
δ = ( δ x , δ y , δ w , δ h , δ α , δ β ) \delta = (\delta_x, \delta_y, \delta_w, \delta_h, \delta_{\alpha},\delta_{\beta}) δ=(δx,δy,δw,δh,δα,δβ)
再通过以下公式对回归得到的偏移量解码即可得到proposals
:
{ x = a w ⋅ δ x + a x , y = a h ⋅ δ y + a y w = a w ⋅ e δ w , h = a h ⋅ e δ h Δ α = δ α ⋅ w , Δ β = δ β ⋅ h \left\{\begin{matrix} x=a_w \cdot\delta _x+a_x,& y=a_h \cdot\delta _y+a_y\\ w=a_w\cdot e^{\delta _w}, & h=a_h\cdot e^{\delta _h}\\ \Delta\alpha =\delta _\alpha \cdot w, & \Delta\beta =\delta _\beta \cdot h \end{matrix}\right. ⎩ ⎨ ⎧x=aw⋅δx+ax,w=aw⋅eδw,Δα=δα⋅w,y=ah⋅δy+ayh=ah⋅eδhΔβ=δβ⋅h
如旋转框的中点偏移表示法图中所示,公式中的 ( x , y ) (x,y) (x,y)表示proposal
的中心, ( w , h ) (w,h) (w,h)表示外接矩形框的宽和高, Δ α \Delta\alpha Δα和 Δ β \Delta\beta Δβ表示旋转框的偏移量。
既然Oriented RPN
的回归分支输出的是bounding box
位置的偏移量,因此在网络的训练过程中需要先将输入对应的ground truth box
和proposal
完成匹配(可以多个proposal对应1个gt box),然后还需要将proposal
和对应的gt box
编码成偏移量的形式 t i ∗ = ( t x ∗ , t y ∗ , t w ∗ , t h ∗ , t α ∗ , t β ∗ ) t_i^*=(t_x^*,t_y^*,t_w^*,t_h^*,t_{\alpha}^*,t_{\beta}^*) ti∗=(tx∗,ty∗,tw∗,th∗,tα∗,tβ∗),作为回归分支的目标,编码公式为:
{ t α ∗ = Δ α g / w g , t β ∗ = Δ β g / h g t w ∗ = l o g ( w g / w a ) t h ∗ = l o g ( h g / h a ) t x ∗ = ( x g − x a ) / w a , t y ∗ = ( y g − y a ) / h a \left\{\begin{matrix} t_\alpha^*=\Delta \alpha_g/w_g, & t_\beta^*=\Delta \beta_g/h_g\\ t_w^*=log(w_g/w_a) & t_h^*=log(h_g/h_a)\\ t_x^*=(x_g-x_a)/w_a, & t_y^*=(y_g-y_a)/h_a \end{matrix}\right. ⎩ ⎨ ⎧tα∗=Δαg/wg,tw∗=log(wg/wa)tx∗=(xg−xa)/wa,tβ∗=Δβg/hgth∗=log(hg/ha)ty∗=(yg−ya)/ha
其中, ( x a , y a , w a , h a ) (x_a,y_a,w_a,h_a) (xa,ya,wa,ha)分别表示水平锚框的中心和宽高, ( x g , y g , w g , h g ) (x_g,y_g,w_g,h_g) (xg,yg,wg,hg)分别表示旋转矩形框外接矩形的中心和宽高, Δ α g \Delta \alpha_g Δαg和 Δ β g \Delta \beta_g Δβg分别表示旋转矩形框的偏移量。
在以上的介绍中,有两个地方值的注意。一个是label assignment
中,是先求ground truth oriented box
的外接矩形,然后计算水平anchor
和外接矩形的IoU
来实现标签匹配的,所以标签匹配依靠的仍然是水平检测框之间的IoU
。可以参考MMROTATE中的代码实现:
# mmrotate/models/dense_heads/oriented_rpn_head.py
# line 75-81
gt_hbboxes = obb2xyxy(gt_bboxes, self.version)
assign_result = self.assigner.assign(anchors, gt_hbboxes, gt_bboxes_ignore, None if self.sampling else gt_labels)
sampling_result = self.sampler.sample(assign_result, anchors, gt_hbboxes)
另外一点是,Oriented RPN
训练中使用的损失函数,分类使用的交叉熵,回归分支使用的SmoothL1Loss
。
# configs/oriented_rcnn/oriented_rcnn_r50_fpn_1x_dota_le90.py
loss_cls=dict(type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
loss_bbox=dict(type='SmoothL1Loss', beta=0.1111111111111111, loss_weight=1.0)
本篇文章另外一部分工作主要就在拿到proposals
后对RoI
的对齐池化上。
Faster R-CNN
中使用的RPN
和Mask R-CNN
中提出的RoIAlign
的介绍可以参考:(六) Region Proposal Network和(五)ROI Pooling 与 ROI Align。
而Oriented RPN
给出的proposal
是带角度的外接矩形,这和水平框的不太一样,要想对Oriented RoI
做Alignment
,需要先将Orientd RoI
做一个旋转,然后就可以按照水平的常规RoI
来处理了,这就是RotateRoIAlign
的全部,如下图所示:
这里有一点需要注意,通过回归 Δ α \Delta \alpha Δα和 Δ β \Delta \beta Δβ得出的旋转框,很有可能是平行四边形而非矩形,因此在算得 Δ α , Δ β \Delta \alpha,\Delta \beta Δα,Δβ后,还需进行一个简单的处理,即将平行四边形的短对角线拉长到和长对角线一样长,即将旋转平行四边形变换成旋转矩形。
关于RotatedRoIAlign
的实现可以参考官方仓库,
template
void pre_calc_for_bilinear_interpolate(
const int height,
const int width,
const int pooled_height,
const int pooled_width,
const int iy_upper,
const int ix_upper,
T roi_start_h,
T roi_start_w,
T bin_size_h,
T bin_size_w,
int roi_bin_grid_h,
int roi_bin_grid_w,
T roi_center_h,
T roi_center_w,
T cos_theta,
T sin_theta,
std::vector>& pre_calc) {
int pre_calc_index = 0;
for (int ph = 0; ph < pooled_height; ph++) {
for (int pw = 0; pw < pooled_width; pw++) {
for (int iy = 0; iy < iy_upper; iy++) {
const T yy = roi_start_h + ph * bin_size_h +
static_cast(iy + .5f) * bin_size_h /
static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
for (int ix = 0; ix < ix_upper; ix++) {
const T xx = roi_start_w + pw * bin_size_w +
static_cast(ix + .5f) * bin_size_w /
static_cast(roi_bin_grid_w);
// Rotate by theta around the center and translate
// In image space, (y, x) is the order for Right Handed System,
// and this is essentially multiplying the point by a rotation matrix
// to rotate it counterclockwise through angle theta.
T y = yy * cos_theta - xx * sin_theta + roi_center_h;
T x = yy * sin_theta + xx * cos_theta + roi_center_w;
// deal with: inverse elements are out of feature map boundary
if (y < -1.0 || y > height || x < -1.0 || x > width) {
// empty
PreCalc pc;
pc.pos1 = 0;
pc.pos2 = 0;
pc.pos3 = 0;
pc.pos4 = 0;
pc.w1 = 0;
pc.w2 = 0;
pc.w3 = 0;
pc.w4 = 0;
pre_calc[pre_calc_index] = pc;
pre_calc_index += 1;
continue;
}
if (y < 0) {
y = 0;
}
if (x < 0) {
x = 0;
}
int y_low = (int)y;
int x_low = (int)x;
int y_high;
int x_high;
if (y_low >= height - 1) {
y_high = y_low = height - 1;
y = (T)y_low;
} else {
y_high = y_low + 1;
}
if (x_low >= width - 1) {
x_high = x_low = width - 1;
x = (T)x_low;
} else {
x_high = x_low + 1;
}
T ly = y - y_low;
T lx = x - x_low;
T hy = 1. - ly, hx = 1. - lx;
T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
// save weights and indices
PreCalc pc;
pc.pos1 = y_low * width + x_low;
pc.pos2 = y_low * width + x_high;
pc.pos3 = y_high * width + x_low;
pc.pos4 = y_high * width + x_high;
pc.w1 = w1;
pc.w2 = w2;
pc.w3 = w3;
pc.w4 = w4;
pre_calc[pre_calc_index] = pc;
pre_calc_index += 1;
}
}
}
}
}
- 1.Oriented R-CNN for Object Detection
- 2.https://github.com/open-mmlab/mmrotate
欢迎访问个人网络日志知行空间