文章目錄
- 前言
- 1、解決的問題
- 2、模型結構
- 2.1.ReCNN
- 2.2. RiRoiAlign
- 總結
前言
?本篇解讀2021CVPR旋轉目標檢測論文:ReDet:A Rotation-equivariant Detector for Aerial Object Detection,附上地址和原始碼鏈接:
論文下載地址
原始碼地址
1、解決的問題

?這是本人組會上做的ppt,簡單說創新點有兩個:
?1)利用NIPS2019的e2cnn思想重寫了ResNet50并命名為ReCNN,使得CNN具有旋轉等變性,即當輸入影像發生旋轉時,CNN提取到的特征一樣,
?2)在經過e2cnn提取到影像的特征向量F(K*N,H,W)后,在通道維度上,可以理解為劃分成N個組(N=4/8)代表4個方向或8個方向,而每組的子通道數為K,但RRoIAlign模塊僅僅是對于不同朝向的物體在空間維度上進行了校正,但在通道維度上并不對齊,故作者設計了RiROIAlign模塊在通道維度上和空間維度上均進行了對齊,從而得到了旋轉不變性的特征,
?總的來說:本文就是設計了一個非常強的特征提取器,
2、模型結構

2.1.ReCNN
?這塊我也不理解,e2cnn太硬核了,只是說下:作者在寫好ReCNN后,在ImageNet上重新訓練并在測驗資料集上微調,(羨慕有能力訓練Backbone的人),
2.2. RiRoiAlign
?在模型結構圖中的意思是首先使用RRoiAlign模塊進行了空間對齊,之后在回圈交換各個通道,比如r=2,將Cn2通道值賦給Cn1,Cn1的值賦給Cnn…并在前后兩個通道間執行雙線性插值來計算當前通道像素值,(我在這里比較懵逼,所以去看了看原始碼),原始碼位置:ReDet-master\mmdet\ops\riroi_align\src\riroi_align_kernel.cu,我盡量做到詳細注釋,不懂歡迎評論交流,
#include <ATen/ATen.h>
#include <THC/THCAtomics.cuh>
#include <math.h>
#define PI 3.141592653
//CUDA是并行計算,即多執行緒計算,每個執行緒對應池化后一個ROI的一個像素點的計算,
//i代表為每個執行緒的id,n代表CUDA當前分配的總的執行緒數,
#define CUDA_1D_KERNEL_LOOP(i, n) \
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \
i += blockDim.x * gridDim.x)
//塊大小為1024.
#define THREADS_PER_BLOCK 1024
//根據塊大小得到網格大小,
inline int GET_BLOCKS(const int N) {
int optimal_block_num = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;
int max_block_num = 65000;
return min(optimal_block_num, max_block_num);
}
//雙線性插值部分不貼了,網上注釋挺多的,
template <typename scalar_t>
__device__ scalar_t bilinear_interpolate(const scalar_t *bottom_data,
const int height, const int width,
scalar_t y, scalar_t x)
/
}
template <typename scalar_t>
__global__ void RiROIAlignForward(const int nthreads, const scalar_t *bottom_data,
const scalar_t *bottom_rois,
const scalar_t spatial_scale,
const int sample_num, const int channels,
const int height, const int width,
const int pooled_height, const int pooled_width,
const int nOrientation,
scalar_t *top_data)
//介紹下各個引數的含義:
//*bottom_data: 是輸入特征向量圖(K,N,H,W)的展成一維陣列后的指標,
//*bottom_rois:就是RPN建議出來的rois(cx,cy,w,h,theta)的一維陣列指標;
//nOrientation: 代表將通道劃分成4/8組
//*top_data:池化后特征圖的指標,
// index:就是當前執行緒id,即池化后*top_data所對應的下標,
CUDA_1D_KERNEL_LOOP(index, nthreads) {
// (n, c, ph, pw) is an element in the pooled output
// 由于index是一維陣列,為了計算方便,計算出一維陣列對應的輸出特征圖的位置(n,c,o,ph,pw):即當前
//index對應第n張影像的第o組通道上的(ph,pw)位置,
int pw = index % pooled_width;
int ph = (index / pooled_width) % pooled_height;
int o = (index / pooled_width / pooled_height) % nOrientation;
int c = (index / pooled_width / pooled_height / nOrientation) % channels;
int n = index / pooled_width / pooled_height / nOrientation / channels;
// 取出roi框的下標,
const scalar_t* offset_bottom_rois = bottom_rois + n * 6;
int roi_batch_ind = offset_bottom_rois[0];
// 得到roi的(cx,cy,w,h,theta)
scalar_t roi_center_w = offset_bottom_rois[1] * spatial_scale;
scalar_t roi_center_h = offset_bottom_rois[2] * spatial_scale;
scalar_t roi_width = offset_bottom_rois[3] * spatial_scale;
scalar_t roi_height = offset_bottom_rois[4] * spatial_scale;
scalar_t theta = offset_bottom_rois[5];
// 得到roi的寬和高
roi_width = max(roi_width, (scalar_t)1.);
roi_height = max(roi_height, (scalar_t)1.);
// 得到在h方向需要插值的點的個數,比如池化為7*7大小:則77/7=10就是每個子塊高為10; w方向同理,
scalar_t bin_size_h = static_cast<scalar_t>(roi_height) / static_cast<scalar_t>(pooled_height);
scalar_t bin_size_w = static_cast<scalar_t>(roi_width) / static_cast<scalar_t>(pooled_width);
// 對應論文 r = theta*N/(2*pi)公式,即得到當前roi在哪組通道
scalar_t ind_float = theta * nOrientation / (2 * PI);
// 將ind_float取整
int ind = floor(ind_float);
// 得到論文中公式9中的系數alpha值,
scalar_t l_var = ind_float - (scalar_t)ind;
scalar_t r_var = 1.0 - l_var;
// 得到ind開始旋轉通道值(就是排除theta>2*pi情況,超出一圈取余數):
ind = (ind + nOrientation) % nOrientation;
// 得到需要調整通道的index,
// 比如 ind = 0, o = 0,則ind=0.此時 ind_rot = 0; ind_rot_plus = 1;
==含義就是 ind = 0朝向的物體 對于0號輸出通道的計算需要 借助輸入特征向量的0和1號通道的像素值,==
int ind_rot = (o - ind + nOrientation) % nOrientation;
int ind_rot_plus = (ind_rot + 1 + nOrientation) % nOrientation;
// 取出ind_rot和ind_rot_plus所對應像素值
const scalar_t* offset_bottom_data =
bottom_data + (roi_batch_ind * channels * nOrientation + c * nOrientation + ind_rot) * height * width;
const scalar_t* offset_bottom_data_plus =
bottom_data + (roi_batch_ind * channels * nOrientation + c * nOrientation + ind_rot_plus) * height * width;
// 雙線性插值采樣的數目,通常為2
int roi_bin_grid_h = (sample_num > 0)
? sample_num
: ceil(roi_height / pooled_height); // e.g., = 2
int roi_bin_grid_w =
(sample_num > 0) ? sample_num : ceil(roi_width / pooled_width);
// 將roi變成[xmin,ymin,theta]格式
scalar_t roi_start_h = -roi_height / 2.0;
scalar_t roi_start_w = -roi_width / 2.0;
scalar_t cosscalar_theta = cos(theta);
scalar_t sinscalar_theta = sin(theta);
// 確定采樣點總數,最終取均值,
const scalar_t count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
scalar_t output_val = 0.;
// 回圈遍歷每個子塊內的像素值,比如roi_w = 77, roi_h = 777, pooled_w=pooed_h=7.
//則每個子塊為(77/7, 777/7)大小,即下面代碼表示遍歷每個子塊內像素值的位置,
for (int iy = 0; iy < roi_bin_grid_h; iy++) { // e.g., iy = 0, 1
const scalar_t yy = roi_start_h + ph * bin_size_h +
static_cast<scalar_t>(iy + .5f) * bin_size_h /
static_cast<scalar_t>(roi_bin_grid_h); // e.g., 0.5, 1.5
for (int ix = 0; ix < roi_bin_grid_w; ix++) {
const scalar_t xx = roi_start_w + pw * bin_size_w +
static_cast<scalar_t>(ix + .5f) * bin_size_w /
static_cast<scalar_t>(roi_bin_grid_w);
// 將每個位置執行放射變換,得到旋轉后位置
scalar_t x = xx * cosscalar_theta - yy * sinscalar_theta + roi_center_w;
scalar_t y = xx * sinscalar_theta + yy * cosscalar_theta + roi_center_h;
// 有了旋轉位置(y,x)后,執行雙線性插值得到 當前組通道的位置的像素值,
scalar_t val = bilinear_interpolate<scalar_t>(
offset_bottom_data, height, width, y, x);
scalar_t val_plus = bilinear_interpolate<scalar_t>(
offset_bottom_data_plus, height, width, y, x);
// 執行論文公式9中雙線性插值,
output_val += r_var * val + l_var * val_plus;
}
}
// 取均值
output_val /= count;
// 將值放到對應輸出特征圖中index的像素值,
top_data[index] = output_val;
}
}
?從代碼可看出,并不是作者論文中所說的先 空間對齊在通道對齊, 作者在實作上將二者結合起來,即確定通道位置的像素值之后順便執行了RRoIAlign,
?如果還是覺得蒙,我這里給出一個示例,自己手推了一下運行程序,也是ppt,

? 假設N=4,即劃分成4組通道,對應代碼中的nOrientation,r即ind,o代表池化后特征向量的通道下標,以表格為例,當r=1時,o=1時候,即將輸入通道的0和1號通道像素值拿去做RiRoiAlign,并將計算得到像素值放到o=1號位置,即實作了通道對齊,就是回圈編碼程序,
總結
? 感覺RiRoiAlign本質上是讓不同朝向的物體在通道維度上放到了一個相對的參考系下,讓不同朝向的物體從自身角度看,通道位置和自身朝向始終對齊,從而實作了真正意義上旋轉不變性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/401497.html
標籤:其他
