CenterNet算法详解
Objects as Points-論文鏈接-代碼鏈接
目錄
- 1、需求解讀
- 2、CenterNet算法簡介
- 3、CenterNet算法詳解
- 3.1 CenterNet網絡結構
- 3.2 CenterNet實現細節詳解
- 3.2.1 訓練階段Heatmap生成
- 3.2.2 Heatmap上應用高斯核
- 3.3 CenterNet損失函數
- 3.3.1 Heatmap損失函數
- 3.3.2 中心點偏移損失函數
- 3.3.3 目標長寬損失函數
- 3.4 CenterNet推理階段
- 4、CenterNet網絡代碼實現
- 5、CenterNet效果展示與分析
- 5.1 CenterNet客觀效果展示與分析
- 5.2 CenterNet主觀效果展示與分析
- 6、總結與分析
- 參考資料
- 注意事項
1、需求解讀
??隨著基于Anchor的目標檢測性能達到了極限,基于Anchor-free的目標檢測算法成為了當前的研究熱點,具有代表性的工作包括CornerNet、FOCS與CenterNet等。除此之外,基于Anchor的目標檢測算法存在著一些嚴重的問題,具體包括:(1)Anchros的定義在一定程度上會限制檢測算法的性能;(2)NMS等后處理操作會降低整個檢測算法的速度。為了解決這些問題,基于Anchor-free的目標檢測算法應運而生,本文對CenterNet目標檢測算法進行詳細的剖析。
2、CenterNet算法簡介
??CenterNet是一個基于Anchor-free的目標檢測算法,該算法是在CornerNet算法的基礎上改進而來的。與單階段目標檢測算法yolov3相比,該算法在保證速度的前提下,精度提升了4個百分點。與其它的單階段或者雙階段目標檢測算法相比,該算法具有以下的優勢:
- (1)該算法去除低效復雜的Anchors操作,進一步提升了檢測算法性能;
- (2)該算法直接在heatmap圖上面執行了過濾操作,去除了耗時的NMS后處理操作,進一步提升了整個算法的運行速度;
- (3)該算法不僅可以應用到2D目標檢測中,經過簡單的改變它還可以應用3D目標檢測與人體關鍵點檢測等其它的任務中,即具有很好的通用性。
3、CenterNet算法詳解
3.1 CenterNet網絡結構
??上圖展示了CenterNet網絡的整體結構,整個網絡結構比較簡單。
- (1)最左邊表示輸入圖片。輸入圖片需要裁減到512*512大小,即長邊縮放到512,短邊補0,具體的效果如下圖所示,由于原圖的W>512,因而直接將其縮放為512;由于原圖的H<512,因而對其執行補0操作;
- (2)中間表示基準網絡,論文中嘗試了Hourglass、ResNet與DLA3種網絡架構,各個網絡架構的精度及幀率為:Resnet-18 with up-convolutional layers:28.1% coco and 142 FPS、DLA-34:37.4% COCOAP and 52 FPS、Hourglass-104:45.1% COCOAP and 1.4 FPS。
??上圖展示了3中不同的網絡架構,圖(a)表示Hourglass網絡,該網絡是在ECCV2016中的Stacked hourglass networks for human pose estimation論文中提出的一種網絡,用來解決人體位姿估計問題,其思路主要通過將多個漏斗形狀的網絡堆疊起來,從而獲得多尺度信息,具體的細節請參考該博客。圖(b)表示帶有反卷積的ResNet網絡,作者在每一個上采樣層之前增加了一個3*3的膨脹卷積,即先使用反卷積來改變膨脹卷積的通道個數,然后使用反卷積來對特征映射執行上采樣操作。圖?表示用于語義分割的DLA34網絡;圖d表示改變的DLA34網絡,該網絡在原始的DLA34網絡的基礎上增加了更多的殘差連接,該網絡將Dense_Connection與FPN的思路融合起來,前者源于DenseNet,可以用來聚合語義信息,能夠提升模型推斷是“what”的能力;后者源于聚合空間信息,能夠提升模型推斷在“where”的能力,具體的細節如下圖所示。
- (3)最右邊表示預測模塊,該模塊包含3個分支,具體包括中心點heatmap圖分支、中心點offset分支、目標大小分支。heatmap圖分支包含C個通道,每一個通道包含一個類別,heatmap中白色的亮區域表示目標的中心 點位置;中心點offset分支用來彌補將池化后的低heatmap上的點映射到原圖中所帶來的像素誤差;目標大小分支用來預測目標矩形框的w與h偏差值。
3.2 CenterNet實現細節詳解
3.2.1 訓練階段Heatmap生成
??CenterNet將目標檢測問題轉換成中心點預測問題,即用目標的中心點來表示該目標,并通過預測目標中心點的偏移量與寬高來獲取目標的矩形框。Heatmap表示分類信息,每一個類別將會產生一個單獨的Heatmap圖。對于每張Heatmap圖而言,當某個坐標處包含目標的中心點時,則會在該目標處產生一個關鍵點,我們利用高斯圓來表示整個關鍵點,下圖展示了具體的細節。
??生成Heatmap圖的具體步驟如下所示:
-
步驟1-將輸入的圖片縮放成512*512大小,對該圖像執行R=4的下采樣操作之后,獲得一個128*128大小的Heatmap圖;
-
步驟2-將輸入圖片中的Box縮放到128*128大小的Heatmap圖上面,計算該Box的中心點坐標,并執行向下取整操作,并將其定義為point;
-
步驟3-根據目標Box大小來計算高斯圓的半徑R;
??關于高斯圓的半徑確定,主要還是依賴于目標box的寬高, 實際情況下通常會取IOU=0.7,即下圖中的overlap=0.7作為臨界值,然后分別計算出三種情況的半徑,取最小值作為高斯核的半徑R,具體的實現細節如下圖所示:
(1)情況1-預測框pred_bbox包含gt_bbox框,對應于下圖中的第1種情況,將整個IoU公式展開之后,成為一個二元一次方程的求解問題。
(2)情況2-gt_bbox包含預測框pred_bbox框,對應于下圖中的第2種情況,將整個IoU公式展開之后,成為一個二元一次方程的求解問題。
(3)情況3-gt_bbox與預測框pred_bbox框相互重疊,對應于下圖中的第3種情況,將整個IoU公式展開之后,成為一個二元一次方程的求解問題。
-
步驟4-在128*128大小的Heatmap圖上面,以point為中心點,半徑為R計算高斯值,point點處數值最大,隨著半徑R的增加數值不斷減小;
??上圖展示了一個樣例,左邊表示經過裁剪之后的512512大小的輸入圖片,右邊表示經過高斯操作之后生成的128128大小的Heatmap圖。由于圖中包含兩只貓,這兩只貓屬于一個類別,因此在同一個Heatmap圖上面生成了兩個高斯圓,高斯圓的大小與矩形框的大小有關。
3.2.2 Heatmap上應用高斯核
??Heatmap上的關鍵點之所以采用二維高斯核來表示,是由于對于在目標中心點附近的一些點,其預測出來的pre_box和gt_box的IOU可能會大于0.7,不能直接對這些預測值進行懲罰,需要溫和一點,所以采用高斯核。該問題在Corner算法中就已經存在,如下圖所示,我們在設置gt_bbox的heatmap時,不僅僅只在中心點的位置設置標簽1,圖中紅色的矩形框表示gt_bbox,但是綠色的矩形框其實也可以很好的包圍該目標,即我們在檢測的過程中如何獲得像綠色框這樣的矩形框時,我們也要保存它。通俗一點來講,只要預測的corner點在中心點的某一個半徑r內,而且該矩形框與gt_bbox之間的IoU大于0.7時,我們將這些點處的值設置為一個高斯分布的數值,而不是數值0。
3.3 CenterNet損失函數
??整個CenterNet的損失函數包含3個部分,LkL_{k}Lk?表示 heatmap中心點損失,LoffL_{off}Loff?表示目標中心點偏移損失,LsizeL_{size}Lsize?表示目標長寬損失函數。
3.3.1 Heatmap損失函數
??上圖展示了Heatmap損失函數,該函數是在Focal Loss的基礎上進行了改進,其中的α\alphaα與β\betaβ是兩個超參數,用來均衡難易樣本;YxycY_{xyc}Yxyc?表示GT值,Y^xyc\hat{Y} _{xyc}Y^xyc?表示預測值;N表示關鍵點的個數。
- 當Y^xyc\hat{Y} _{xyc}Y^xyc?=1時,易分類樣本的預測值接近為1,此時(1?Y^xyc)α({1-\hat{Y} _{xyc}})^{\alpha }(1?Y^xyc?)α就表示一個很小的數值,此時損失函數的數值就比較小,起到了降低該樣本權重的作用。
- 當Y^xyc\hat{Y} _{xyc}Y^xyc?=1時,難分類樣本的預測值接近為0,此時(1?Y^xyc)α({1-\hat{Y} _{xyc}})^{\alpha }(1?Y^xyc?)α就表示一個較大的數值,此時損失函數的數值就比較大,起到了增加該樣本權重的作用。
- 當Y^xyc\hat{Y} _{xyc}Y^xyc?!=1時,為了防止預測值Y^xyc\hat{Y} _{xyc}Y^xyc?過高的接近于1,利用(1?Y^xyc)α({1-\hat{Y} _{xyc}})^{\alpha }(1?Y^xyc?)α來充當懲罰項,而(1?Yxyc)β({1-{Y} _{xyc}})^{\beta }(1?Yxyc?)β這個參數距離中心點越近,其數值越小,用來進一步減輕這個懲罰力度。
3.3.2 中心點偏移損失函數
??上圖展示了LoffL_{off}Loff?損失函數,其中O^p~{ \hat{O} \tilde{p} }O^p~?表示網絡預測的偏移量數值,p表示圖像中心點坐標,R表示Heatmap的縮放因子,p~\tilde{p}p~?表示縮放后中心點的近似整數坐標,整個過程利用L1 Loss計算正樣本塊的偏移損失。由于骨干網絡輸出的 feature map 的空間分辨率是原始輸入圖像的四分之一。即輸出 feature map 上的每一個像素點對應到原始圖像的一個4x4 區域,這會帶來較大的誤差,因此引入了偏置的損失值。
??假設目標中心點p為(125, 63),由于輸入圖片大小為512*512,縮放尺度R=4,因此縮放后的128x128尺寸下中心點坐標為(31.25, 15.75), 相對于整數坐標(31, 15)的偏移值即為(0.25, 0.75)。
3.3.3 目標長寬損失函數
??上圖展示了目標長寬損失函數,其中N表示關鍵點的個數,Sk表示目標的真實尺寸,S^pk{\hat{S} pk}S^pk表示預測的尺寸,整個過程利用L1 Loss來計算正樣本塊的長寬損失。
3.4 CenterNet推理階段
??CenterNet網絡的推理階段的實現步驟如下所述:
- 步驟1-首先將輸入圖片縮到512*512大小;
- 步驟2-然后對輸入圖片執行下采樣,并對下采樣后的圖像執行預測,即在128*128大小的Heatmap上執行預測;
- 步驟3-然后在128*128大小的Heatmap圖上面采用一個3*3大小的最大池化操作來獲取Heatmap中滿足條件的關鍵點(類似于anchor-based檢測中nms的效果),并選取100個關鍵點;
- 步驟4-最后根據confidence閾值來過濾出最終的檢測結果。
4、CenterNet網絡代碼實現
1、Hourglass網絡部分代碼
class convolution(nn.Module):def __init__(self, k, inp_dim, out_dim, stride=1, with_bn=True):super(convolution, self).__init__()pad = (k - 1) // 2self.conv = nn.Conv2d(inp_dim, out_dim, (k, k), padding=(pad, pad), stride=(stride, stride), bias=not with_bn)self.bn = nn.BatchNorm2d(out_dim) if with_bn else nn.Sequential()self.relu = nn.ReLU(inplace=True)def forward(self, x):conv = self.conv(x)bn = self.bn(conv)relu = self.relu(bn)return reluclass fully_connected(nn.Module):def __init__(self, inp_dim, out_dim, with_bn=True):super(fully_connected, self).__init__()self.with_bn = with_bnself.linear = nn.Linear(inp_dim, out_dim)if self.with_bn:self.bn = nn.BatchNorm1d(out_dim)self.relu = nn.ReLU(inplace=True)def forward(self, x):linear = self.linear(x)bn = self.bn(linear) if self.with_bn else linearrelu = self.relu(bn)return reluclass residual(nn.Module):def __init__(self, k, inp_dim, out_dim, stride=1, with_bn=True):super(residual, self).__init__()self.conv1 = nn.Conv2d(inp_dim, out_dim, (3, 3), padding=(1, 1), stride=(stride, stride), bias=False)self.bn1 = nn.BatchNorm2d(out_dim)self.relu1 = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(out_dim, out_dim, (3, 3), padding=(1, 1), bias=False)self.bn2 = nn.BatchNorm2d(out_dim)self.skip = nn.Sequential(nn.Conv2d(inp_dim, out_dim, (1, 1), stride=(stride, stride), bias=False),nn.BatchNorm2d(out_dim)) if stride != 1 or inp_dim != out_dim else nn.Sequential()self.relu = nn.ReLU(inplace=True)def forward(self, x):conv1 = self.conv1(x)bn1 = self.bn1(conv1)relu1 = self.relu1(bn1)conv2 = self.conv2(relu1)bn2 = self.bn2(conv2)skip = self.skip(x)return self.relu(bn2 + skip)def make_layer(k, inp_dim, out_dim, modules, layer=convolution, **kwargs):layers = [layer(k, inp_dim, out_dim, **kwargs)]for _ in range(1, modules):layers.append(layer(k, out_dim, out_dim, **kwargs))return nn.Sequential(*layers)def make_layer_revr(k, inp_dim, out_dim, modules, layer=convolution, **kwargs):layers = []for _ in range(modules - 1):layers.append(layer(k, inp_dim, inp_dim, **kwargs))layers.append(layer(k, inp_dim, out_dim, **kwargs))return nn.Sequential(*layers)class MergeUp(nn.Module):def forward(self, up1, up2):return up1 + up2def make_merge_layer(dim):return MergeUp()# def make_pool_layer(dim): # return nn.MaxPool2d(kernel_size=2, stride=2)def make_pool_layer(dim):return nn.Sequential()def make_unpool_layer(dim):return nn.Upsample(scale_factor=2)def make_kp_layer(cnv_dim, curr_dim, out_dim):return nn.Sequential(convolution(3, cnv_dim, curr_dim, with_bn=False),nn.Conv2d(curr_dim, out_dim, (1, 1)))def make_inter_layer(dim):return residual(3, dim, dim)def make_cnv_layer(inp_dim, out_dim):return convolution(3, inp_dim, out_dim)class kp_module(nn.Module):def __init__(self, n, dims, modules, layer=residual,make_up_layer=make_layer, make_low_layer=make_layer,make_hg_layer=make_layer, make_hg_layer_revr=make_layer_revr,make_pool_layer=make_pool_layer, make_unpool_layer=make_unpool_layer,make_merge_layer=make_merge_layer, **kwargs):super(kp_module, self).__init__()self.n = ncurr_mod = modules[0]next_mod = modules[1]curr_dim = dims[0]next_dim = dims[1]self.up1 = make_up_layer(3, curr_dim, curr_dim, curr_mod, layer=layer, **kwargs) self.max1 = make_pool_layer(curr_dim)self.low1 = make_hg_layer(3, curr_dim, next_dim, curr_mod,layer=layer, **kwargs)self.low2 = kp_module(n - 1, dims[1:], modules[1:], layer=layer, make_up_layer=make_up_layer, make_low_layer=make_low_layer,make_hg_layer=make_hg_layer,make_hg_layer_revr=make_hg_layer_revr,make_pool_layer=make_pool_layer,make_unpool_layer=make_unpool_layer,make_merge_layer=make_merge_layer,**kwargs) if self.n > 1 else \make_low_layer(3, next_dim, next_dim, next_mod,layer=layer, **kwargs)self.low3 = make_hg_layer_revr(3, next_dim, curr_dim, curr_mod,layer=layer, **kwargs)self.up2 = make_unpool_layer(curr_dim)self.merge = make_merge_layer(curr_dim)def forward(self, x):up1 = self.up1(x)max1 = self.max1(x)low1 = self.low1(max1)low2 = self.low2(low1)low3 = self.low3(low2)up2 = self.up2(low3)return self.merge(up1, up2)class exkp(nn.Module):def __init__(self, n, nstack, dims, modules, heads, pre=None, cnv_dim=256, make_tl_layer=None, make_br_layer=None,make_cnv_layer=make_cnv_layer, make_heat_layer=make_kp_layer,make_tag_layer=make_kp_layer, make_regr_layer=make_kp_layer,make_up_layer=make_layer, make_low_layer=make_layer, make_hg_layer=make_layer, make_hg_layer_revr=make_layer_revr,make_pool_layer=make_pool_layer, make_unpool_layer=make_unpool_layer,make_merge_layer=make_merge_layer, make_inter_layer=make_inter_layer, kp_layer=residual):super(exkp, self).__init__()self.nstack = nstackself.heads = headscurr_dim = dims[0]self.pre = nn.Sequential(convolution(7, 3, 128, stride=2),residual(3, 128, 256, stride=2)) if pre is None else preself.kps = nn.ModuleList([kp_module(n, dims, modules, layer=kp_layer,make_up_layer=make_up_layer,make_low_layer=make_low_layer,make_hg_layer=make_hg_layer,make_hg_layer_revr=make_hg_layer_revr,make_pool_layer=make_pool_layer,make_unpool_layer=make_unpool_layer,make_merge_layer=make_merge_layer) for _ in range(nstack)])self.cnvs = nn.ModuleList([make_cnv_layer(curr_dim, cnv_dim) for _ in range(nstack)])self.inters = nn.ModuleList([make_inter_layer(curr_dim) for _ in range(nstack - 1)])self.inters_ = nn.ModuleList([nn.Sequential(nn.Conv2d(curr_dim, curr_dim, (1, 1), bias=False),nn.BatchNorm2d(curr_dim)) for _ in range(nstack - 1)])self.cnvs_ = nn.ModuleList([nn.Sequential(nn.Conv2d(cnv_dim, curr_dim, (1, 1), bias=False),nn.BatchNorm2d(curr_dim)) for _ in range(nstack - 1)])## keypoint heatmapsfor head in heads.keys():if 'hm' in head:module = nn.ModuleList([make_heat_layer(cnv_dim, curr_dim, heads[head]) for _ in range(nstack)])self.__setattr__(head, module)for heat in self.__getattr__(head):heat[-1].bias.data.fill_(-2.19)else:module = nn.ModuleList([make_regr_layer(cnv_dim, curr_dim, heads[head]) for _ in range(nstack)])self.__setattr__(head, module)self.relu = nn.ReLU(inplace=True)def forward(self, image):# print('image shape', image.shape)inter = self.pre(image)outs = []for ind in range(self.nstack):kp_, cnv_ = self.kps[ind], self.cnvs[ind]kp = kp_(inter)cnv = cnv_(kp)out = {}for head in self.heads:layer = self.__getattr__(head)[ind]y = layer(cnv)out[head] = youts.append(out)if ind < self.nstack - 1:inter = self.inters_[ind](inter) + self.cnvs_[ind](cnv)inter = self.relu(inter)inter = self.inters[ind](inter)return outsdef make_hg_layer(kernel, dim0, dim1, mod, layer=convolution, **kwargs):layers = [layer(kernel, dim0, dim1, stride=2)]layers += [layer(kernel, dim1, dim1) for _ in range(mod - 1)]return nn.Sequential(*layers)class HourglassNet(exkp):def __init__(self, heads, num_stacks=2):n = 5dims = [256, 256, 384, 384, 384, 512]modules = [2, 2, 2, 2, 2, 4]super(HourglassNet, self).__init__(n, num_stacks, dims, modules, heads,make_tl_layer=None,make_br_layer=None,make_pool_layer=make_pool_layer,make_hg_layer=make_hg_layer,kp_layer=residual, cnv_dim=256)def get_large_hourglass_net(num_layers, heads, head_conv):model = HourglassNet(heads, 2)return model5、CenterNet效果展示與分析
5.1 CenterNet客觀效果展示與分析
??上表展示了CenterNet目標檢測在COCO驗證集上面的精度與速度。第1行展示了利用Hourglass-104作為基準網絡后不僅能夠獲得40.4AP,而且可以獲得14FPS的速度;第2行展示了利用DLA-34作為基準網絡后獲得的AP與FPS;第3行與第4行分別展示了ResNet-101與ResNet-18基準網絡在COCO驗證集上面的效果。通過觀察我們可以發現,基于DLA-34的基準網絡能夠在精度與速度之間達到一個折中。
??上表展示了CenterNet算法在COCO關鍵點驗證集上面的測試效果。通過觀察我們可以得出以下的初步結論:(1)基于Hourglass的基準網絡可以獲得更高的精度,但是速度卻很難滿足實時場景的需求;(2)基于DLA-34的基準網絡不僅可以獲得更高的精度,而且可以獲得較好的精度。(3)該算法的精度接近于很多state-of-art的行人位姿估計算法。
5.2 CenterNet主觀效果展示與分析
??上圖展示了CenterNet檢測算法在一張測試圖片上面的測試結果。左邊展示的是對應的Heatmap圖,圖中的褐色點表示該算法輸出的中心點坐標,右邊表示該算法的檢測結果。
??上圖展示了CenterNet人體位姿估計算法在一張測試圖片上面的測試結果。最左邊展示的是目標中心點的Heatmap圖,中間圖表示的是輸出的人體關鍵點Heatmap圖,最右邊表示的是CenterNet人體位姿估計算法的輸出結果,該算法在這種復雜的場景下仍然獲得了較高的精度。
??上圖展示了CenterNet目標檢測算法、CenterNet人體位姿估計算法、CenterNet 3D目標檢測算法在一些復雜的測試場景上面的測試效果。通過觀察我們可以發現該算法在不同的復雜場景下仍然得到較高的精度。
6、總結與分析
??CenterNet是一個基于Anchor-free的目標檢測算法。通過觀察上圖,我們可以發現該算法的精度幾乎超過了當時所有的單階段與雙階段目標檢測算法,包括Faster-RCNN、RetinaNet和Yolov3。由于該算法去除了耗時的Anchors與NMS后處理操作,因而該算法具有較快的運行速度,適合部署在一些低性能的嵌入式設備中。除此之外,經過實際的測試我們會發現該算法在多個實際場景中都能取得較高的檢測精度。
參考資料
[1] 原始論文
[2] 博客1
[3] 博客2
注意事項
[1] 該博客是本人原創博客,如果您對該博客感興趣,想要轉載該博客,請與我聯系(qq郵箱:1575262785@qq.com),我會在第一時間回復大家,謝謝大家的關注。
[2] 由于個人能力有限,該博客可能存在很多的問題,希望大家能夠提出改進意見。
[3] 如果您在閱讀本博客時遇到不理解的地方,希望您可以聯系我,我會及時的回復您,和您交流想法和意見,謝謝。
[4] 本人業余時間承接各種本科畢設設計和各種項目,包括圖像處理(數據挖掘、機器學習、深度學習等)、matlab仿真、python算法及仿真等,有需要的請加QQ:1575262785詳聊,備注“項目”!!!
總結
以上是生活随笔為你收集整理的CenterNet算法详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vs2008激活、序列号
- 下一篇: 罗技k380无线键盘怎么连接电脑_罗技k