(附代碼)CenterNet:繼YOLO之后的高效目標(biāo)檢測算法
點(diǎn)擊左上方藍(lán)字關(guān)注我們

轉(zhuǎn)載自 | 機(jī)器學(xué)習(xí)算法工程師
作者 | OpenMMLab @知乎
鏈接 | https://zhuanlan.zhihu.com/p/374891478
摘要
先附上鏈接:https://github.com/open-mmlab/mmdetection
在大家的千呼萬喚中,MMDetection 支持 CenterNet 了!從官方倉庫 xingyizhou/CenterNet 的 5.5k star 可見其受歡迎程度。既然叫做 CenterNet,那么其最大亮點(diǎn)就是提出了一種強(qiáng)任務(wù)擴(kuò)展的框架,可以將大部分任務(wù)都?xì)w納為預(yù)測中心+基于中心點(diǎn)的偏移屬性,例如目標(biāo)檢測是中心點(diǎn)+基于中心點(diǎn)的寬高屬性偏移;關(guān)鍵點(diǎn)檢測是中心點(diǎn)+基于中心點(diǎn)的人體關(guān)鍵點(diǎn)偏移預(yù)測等等。

除了上述所提通用做法,其還是一個(gè)速度和精度平衡,anchor-free 算法,由于其簡單的設(shè)計(jì)思想、無需NMS、無需復(fù)雜的FPN結(jié)構(gòu)、超參少的特點(diǎn),在很多對速度有要求或者比賽中都有采用,也比較容易部署,應(yīng)用非常廣泛的。
1 算法核心實(shí)現(xiàn)
由于 CenterNet 比較出名,而且大部分源碼都是基于 CornerNet,故本文不進(jìn)行詳細(xì)分析。對于目標(biāo)檢測而言,其輸出主要包括兩條分支,一個(gè)是中心點(diǎn) heatmap 回歸分支;一個(gè)是基于中心點(diǎn)的寬高屬性預(yù)測分支,為了提高中心點(diǎn)的預(yù)測精度,還引入了額外的 offset 回歸分支,回歸用于量化誤差導(dǎo)致的中心點(diǎn)偏移,heatmap 和 offset 回歸的做法參考自 CornerNet。
由于 MMDetection 中已經(jīng)實(shí)現(xiàn)了 CornerNet,為了方便代碼復(fù)用,在 CenterNet 復(fù)現(xiàn)中大量復(fù)用了相關(guān)代碼,例如數(shù)據(jù)增強(qiáng)、后處理等等。
1.1 Backbone
考慮到 Hourglass-104 和 ResNet-101 等大模型的訓(xùn)練時(shí)間以及 DLANet 網(wǎng)絡(luò)的復(fù)雜性,我們優(yōu)先考慮采用 ResNet-18 作為復(fù)現(xiàn)的 base 模型(DLANet 由于結(jié)構(gòu)的復(fù)雜性以及代碼易讀性,我們計(jì)劃單獨(dú)提一個(gè)新的 PR 進(jìn)行復(fù)現(xiàn),后續(xù)會發(fā)布),其配置為:
backbone=dict(
type='ResNet', depth=18, norm_eval=False, norm_cfg=dict(type='BN'))需要特別注意:由于 ResNet-18 模型比較小,而且 CenterNet 訓(xùn)練 epoch 非常長為140,所以最好是所有 BN 層都參與訓(xùn)練,故修改更改默認(rèn)設(shè)置為 norm_eval=False。
1.2 Neck
為了代碼解耦,我們將 CenterNet 模型也切分出了 Neck 模塊,對應(yīng)模型是 `CTResNetNeck`,主要完成上采樣操作。
neck=dict(
type='CTResNetNeck',
in_channel=512,
num_deconv_filters=(256, 128, 64),
num_deconv_kernels=(4, 4, 4),
use_dcn=True), 由于輸入和輸出特征圖是相差 4 倍,ResNet-18 輸出特征圖最大是下采樣 32 倍,故需要上采樣 3 次。

(b) 即為以 ResNet 為 backbone 的網(wǎng)絡(luò)結(jié)構(gòu),(d) 是以 DLANet 為 backbone 的 CenterNet 網(wǎng)絡(luò)結(jié)構(gòu)(實(shí)際上 d 圖和代碼是對不上的,OUT 輸出來自倒數(shù)第二層上采樣輸出,而不是上圖中的倒數(shù)第一層)
為了提高性能,作者在上采樣模塊中引入了可變形卷積,實(shí)現(xiàn)結(jié)果表明提升了較多性能(從 26.0 提升到29.5),而且上采樣模塊是可學(xué)習(xí)的轉(zhuǎn)置卷積,而非常用的雙線性上采樣模塊。
layers = []
for i in range(len(num_deconv_filters)):
feat_channel = num_deconv_filters[i]
conv_module = ConvModule(
self.in_channel,
feat_channel,
3,
padding=1,
conv_cfg=dict(type='DCNv2') if self.use_dcn else None,
norm_cfg=dict(type='BN'))
layers.append(conv_module)
upsample_module = ConvModule(
feat_channel,
feat_channel,
num_deconv_kernels[i],
stride=2,
padding=1,
conv_cfg=dict(type='deconv'), # 轉(zhuǎn)置卷積
norm_cfg=dict(type='BN'))
layers.append(upsample_module)
self.in_channel = feat_channel
return nn.Sequential(*layers)需要注意一個(gè)細(xì)節(jié):
if isinstance(m, nn.ConvTranspose2d):
# 采用 ConvTranspose2d 默認(rèn)初始化方法
m.reset_parameters()
# 模擬雙線性上采樣 kernel 初始化
w = m.weight.data
f = math.ceil(w.size(2) / 2)
c = (2 * f - 1 - f % 2) / (2. * f)
for i in range(w.size(2)):
for j in range(w.size(3)):
w[0, 0, i, j] = \
(1 - math.fabs(i / f - c)) * (
1 - math.fabs(j / f - c))
for c in range(1, w.size(0)):
w[c, 0, :, :] = w[0, 0, :, :]上述初始化過程非常復(fù)雜,實(shí)際上作者是希望 ConvTranspose2d 初始化時(shí)候能夠提供類似雙線性上采樣層功能,故其初始化參數(shù)設(shè)置為了雙線性上采樣核,有助于收斂。而 `m.reset_parameters()` 存在的原因是 MMDetection 中的 `ConvModule` 會修改掉原始 ConvTranspose2d 層的初始化方式。
1.3 Head
拆解出 Neck 后,Head 模塊就非常簡單了,輸出三個(gè)特征圖,分別是高斯熱圖 (h/4, w/4, cls_nums),每個(gè)通道代表一個(gè)類別;寬高輸出圖 (h/4, w/4, 2),代表中心點(diǎn)距離左上邊距值;中心點(diǎn)量化偏移圖(h/4, w/4, 2):
def _build_head(self, in_channel, feat_channel, out_channel):
"""Build head for each branch."""
layer = nn.Sequential(
nn.Conv2d(in_channel, feat_channel, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(feat_channel, out_channel, kernel_size=1))
return layer高斯熱圖和 offset 圖分支的 target 和 loss 計(jì)算方式都是完全采用了 CornerNet 算法,故本文不再贅述,而寬高輸出圖和 offset 一樣,采用常規(guī)的 L1Loss,并且寬高輸出圖和 offset 分支僅僅在 heatmap 中心點(diǎn)處才會計(jì)算 loss,其余地方全部忽略。
CenterNet 后處理流程和 CornerNet 幾乎一致,除了有額外的寬高預(yù)測圖分支外。以上就是 CenterNet 全部流程,下面重點(diǎn)分析在復(fù)現(xiàn)過程中發(fā)現(xiàn)的一些細(xì)節(jié)。
2 復(fù)現(xiàn)細(xì)節(jié)
2.1 收斂速度
CenterNet 雖然很好,但是收斂速度比較慢,和 RetinaNet 等模型相比收斂速度慢的太多了,常規(guī)算法都僅僅需要 12 epoch 就能得到比較好的結(jié)果,而 CenterNet 需要 140。這主要是因?yàn)槠湔龢颖咎倭耍瑢捀哳A(yù)測和 offset 預(yù)測分支僅僅在 gt bbox 中心才算 loss。在 COCO 數(shù)據(jù)集上,為了能夠達(dá)到還不錯(cuò)的性能,作者采用了非常多的數(shù)據(jù)增強(qiáng)手段。
2.2 超參設(shè)置
因?yàn)?CenterNet 算法發(fā)布比較早且很實(shí)用,故基于源碼也有很多更好的第三方復(fù)現(xiàn),在閱讀源碼過程中以及參考第三方復(fù)現(xiàn) github.com/FateScript/C,我們相應(yīng)的對 CenterNet 超參進(jìn)行了調(diào)整,細(xì)節(jié)如下:
修復(fù)了源碼中預(yù)訓(xùn)練模型均值和方差錯(cuò)誤問題。torchvision 發(fā)布的模型均值和方差實(shí)際上和源碼發(fā)布的不一樣
因?yàn)槌L的訓(xùn)練時(shí)間以及參考現(xiàn)代目標(biāo)檢測優(yōu)化器設(shè)置,我們直接采用了 SGD+Momentum+Warmup 優(yōu)化器,而沒有采用原始的 Adam,結(jié)果表明在 ResNet18,且含 DCNv2 模型上,SGD 跑出的性能是 29.7,而 Adam 是 29.1
分布式訓(xùn)練中采用了 DDP 模式,而非源碼中的 DP
當(dāng)訓(xùn)練過程中某個(gè) batch 內(nèi)沒有 gt bbox,那么 heatmap 分支會輸出比較大的 Loss,導(dǎo)致梯度激增,后續(xù)難以得到比較好的性能,為了穩(wěn)定訓(xùn)練過程,我們額外引入了梯度裁剪
經(jīng)過上述改進(jìn),最終訓(xùn)練出來的 CenterNet 性能會比源碼高大概 1.7 個(gè)點(diǎn)。如果想知道更多性能對比結(jié)果,可以參考 github.com/open-mmlab/m 。
在小模型 ResNet18-DCNv2 上多次訓(xùn)練,我們發(fā)現(xiàn)性能其實(shí)還是不太穩(wěn)定,最低出現(xiàn)過 29.4 mAP,最高出現(xiàn)過 29.9 mAP,波動如此大的原因可能有多方面:
小模型在出現(xiàn) loss 波動大的情況下,后續(xù)很難穩(wěn)定,特別是當(dāng) loss 波動出現(xiàn)在后期
CenterNet 數(shù)據(jù)增強(qiáng)比較多,可能會出現(xiàn)極端場景而影響最終性能
2.3 其他細(xì)節(jié)
以 ResNet18-DCNv2 為例,在作者所提的 flip 多尺度測試中,和常規(guī)的做法不同,其是在特征圖上面進(jìn)行平均,而不是單圖得到 bbox 后,統(tǒng)一進(jìn)行 nms 操作。為此,我們在 `mmdet/models/detectors/centernet.py` 中特意重寫了這部分邏輯。
而且比較奇怪的是,三個(gè)輸出分支中,正常來說應(yīng)該是都要進(jìn)行特征圖平均,但是實(shí)際上 offset 分支是沒有進(jìn)行平均操作的。
center_hm = (center_hm[0:1] + self.flip_tensor(center_hm[1:2])) / 2
wh_hm = (wh_hm[0:1] + self.flip_tensor(wh_hm[1:2])) / 2
offset_hm = offset_hm[0:1]實(shí)際測試發(fā)現(xiàn),如果 offset 也進(jìn)行特征圖平均操作,mAP 會降低 0.2。
不管是單尺度測試還是 flip 測試,都沒有采用 nms,只在多尺度測試時(shí)候才用了 nms。實(shí)際上發(fā)現(xiàn)如果對 flip 也進(jìn)行 nms 操作,最終性能可以由 31.0 提升到31.6。
END
整理不易,點(diǎn)贊三連↓
