Repulsion Loss 遮擋場景下的目標檢測

極市導(dǎo)讀
?在行人檢測工作中,機器常因檢測對象相互遮擋而受到干擾,導(dǎo)致識別錯誤。這篇論文提出的Repulsion Loss能較好地解決這一問題,值得一讀。>>加入極市CV技術(shù)交流群,走在計算機視覺的最前沿
介紹
本文是曠視研究院CVPR2018上的一篇工作,在檢測行人任務(wù)中,由于行人之間互相遮擋,導(dǎo)致傳統(tǒng)的檢測器容易受遮擋的干擾,給出錯誤的預(yù)測框。
研究人員先是從數(shù)據(jù)集上進行分析,定量描述了遮擋對行人檢測帶來的影響。后面受吸引,排斥的啟發(fā),提出了Repulsion Loss來盡可能讓預(yù)測框貼近真實框的同時,又能與同類排斥,進而避免誤檢。
問題引入
常見的遮擋問題可以再被細分為主要兩類:
類間遮擋,即目標被其他類遮擋住。舉個例子,一個行人遛狗,人體下半部分就可能被狗狗遮住 類內(nèi)遮擋,目標物體被同類遮擋住,在我們問題里面也就是行人遮擋。
我們思考一下行人遮擋會對檢測器造成什么影響。假設(shè)我們目標行人是T,旁邊被另外一個行人B所遮擋。那么B的真實框會導(dǎo)致我們對T的預(yù)測框P,往B去移動(shift),造成類似下圖的情況

另外我們再考慮下目標檢測常用的后處理NMS,非極大值抑制。NMS操作是為了抑制去除掉多余的框。但是在行人檢測中,NMS操作會帶來更糟糕的檢測結(jié)果。還是剛剛的例子,我對T有一個預(yù)測框P,但因為距離B靠的太近,我可能會被B的預(yù)測框給抑制,導(dǎo)致行人檢測中出現(xiàn)漏檢。這也從另外一個側(cè)面反映出行人檢測對NMS閾值的敏感性,閾值太低了會帶來漏檢,閾值太高了會帶來假正例(即標出錯誤的目標)
因此如何穩(wěn)定的檢測出群體中個體行人是行人檢測器的關(guān)鍵。
現(xiàn)有的方法僅僅要求預(yù)測框盡可能靠近目標框,而沒有考慮周圍附近的物體。受磁鐵同性相斥,異性相吸的原理,我們提出了一種RepLoss新的損失函數(shù)
該損失函數(shù)在要求預(yù)測框P靠近目標框T(吸引)的同時,也要求預(yù)測框P遠離其他不屬于目標T的真實框(排斥) 該損失函數(shù)很好的提升了行人檢測模型的性能,并且降低了NMS對閾值的敏感性

人群遮擋的影響
數(shù)據(jù)集
我們采用了CityPersons數(shù)據(jù)集,該數(shù)據(jù)集有共約35000個行人。我們的實驗都基于這個數(shù)據(jù)集進行,在評價當中,我們采用log miss rate的MR?2指標來進行衡量(也就是每張圖片的漏檢率上取平均值,再進行l(wèi)og計算,該值越低越好)
檢測器
我們的基線檢測器沿用了Faster RCNN,將骨干網(wǎng)絡(luò)換成resnet。由于行人檢測算是小目標檢測任務(wù),因此我們給resnet增加了空洞卷積,并將下采樣改為8倍(原始224->7下采樣是32倍)
簡單改進后的目標檢測器的MR指標由15.4下降到14.6,稍微提升了點。
小目標難檢測原因(補充)
傳統(tǒng)的分類網(wǎng)絡(luò)為了減少計算量,都使用到了下采樣,而下采樣過多,會導(dǎo)致小目標的信息在最后的特征圖上只有幾個像素(甚至更少),信息損失較多 下采樣擴張的感受野比較利于大目標檢測,而對于小目標,感受野可能會大于小目標本身,導(dǎo)致效果較差
對失敗案例的分析
我們在CityPerson數(shù)據(jù)集中,由于該數(shù)據(jù)集是從分割數(shù)據(jù)集得來的,因此我們有每個行人的可見區(qū)域,即BBox_visible
為了更好分析,我們定義了一個遮擋率,如下公式
由公式可知,當行人可見區(qū)域越小,遮擋率occ越大
我們設(shè)定occ >= 0.1即為一個遮擋的案例
而occ >=0.1 并且與其他行人的IoU >=0.1,我們定義為人群遮擋案例
基于這兩類設(shè)定,我們又在原數(shù)據(jù)集上劃分出兩個子集,分別是reasonable-occ,reasonable-crowd 很顯然,reasonable-crowd也是resonable-occ的子集

藍色,橙色,灰色分別代表Reasonable-crowd子集,Reasonable-occ子集,Reasonable集合。可以看到crowd子集在occ子集中,占據(jù)了接近60%。這也從側(cè)面說明了人群遮擋是遮擋中一個主要問題。
假正例分析
我們同時也分析了有多少假正例是由人群遮擋造成的 我們具體分為了三類,background,localization,crowd
background是預(yù)測框與真實框的IoU<0.1 localization是預(yù)測框僅與一個真實框的IoU>=0.1 Crowd是預(yù)測框與多于兩個真實框的IoU>=0.1

圖中紅框就是上述的crowd error,大約有20%的假正例都是由人群導(dǎo)致的 因為相鄰的兩個真實框,預(yù)測框或多或少產(chǎn)生偏移,導(dǎo)致預(yù)測錯誤
Repulsion Loss
前面分析了這么多錯誤,現(xiàn)在才是重頭戲 Repulsion Loss主要由三部分構(gòu)成
Lattr是為了預(yù)測框更接近真實框(即吸引)
Lrep則是為了讓預(yù)測框遠離周圍的真實框(即排斥)
參數(shù)α和β用于平衡兩者的權(quán)重
我們設(shè) P(lP , tP , wP , hP )為候選框 G(lG, tG, wG, hG)為真實框
P+為正候選框集合,正候選框的意思是,至少與其中一個真實框的IoU大于某個閾值,這里是0.5 g = {G} 是真實框集合
Attraction term
這一項loss在其他算法也廣泛使用,為了方便比較,我們沿用smoothL1 Loss

Smooth L1 Loss公式如下

這里我們的平滑系數(shù)取2

Repulsion Term (RepGT)
RepGT loss設(shè)計是為了遠離非目標的真實框 對于一個候選框P,其排斥對象被定義為,除去本身要回歸目標的真實框外,與其IoU最大的真實框

受IoU loss啟發(fā),我們定義了一個IoG
損失定義如下

這里沒有采用smooth l1 loss而是smooth ln loss,其公式如下


不同平滑系數(shù),最后陡峭程度不一樣。當一個候選框P與非目標的真實框重疊越多,其懲罰也越大。
Repulsion Term (RepBox)
這項損失是針對人群檢測中,NMS處理對閾值敏感的問題 我們先將P+集合劃分成互斥的g個子集(因為一共有g(shù)個目標物體)
然后從兩個不同子集隨機采樣,分別得到兩個互斥集合的預(yù)測框,即
我們希望這兩個互斥集合出來的回歸框,交叉的范圍盡可能小,于是有了RepBox loss,公式如下

其中分母的I是identity函數(shù),即
這里限制大于0,為了避免分式除0,我們這里加了個極小值 上面依舊采用Smooth ln函數(shù)來計算。
引申討論
距離函數(shù)選擇
在懲罰項中,我們分別選擇了IoG和IoU來進行度量。其原因是IoG和IoU把范圍限定在了(0, 1),與此同時 SmoothL1是無界的。如果SmoothL1用在RepGT中,它會讓預(yù)測框與非目標的gt框離的越遠越好,而我們的初衷只是想減少交叉部分,相比之下,IoG更符合我們的思想
另外在RepGT中使用IoG而不使用IoU的原因是,IoG的分母下,真實框大小area(G)是固定的,因此其優(yōu)化目標是去減少與目標框重疊,即area(B∩G)。而在IoU下,回歸器也許會盡可能讓預(yù)測框更大(即分母)來最小化loss
實驗部分
這里只簡單介紹一下 我們在CityPerson和Caltech-USA分別訓(xùn)練了80k和160k個iter

根據(jù)不同平滑系數(shù),得到的提升也不一樣 我們進一步調(diào)整兩個loss的權(quán)重,相對得到了更好的效果


實驗效果圖如下,這是未經(jīng)過NMS處理的錨框圖

可以看到加了RepBox后,明顯少了很多夾在在兩個人中間的預(yù)測框,這也減少了后續(xù)NMS處理出錯的情況。
代碼解析
這里采用的是這版repulsion loss實現(xiàn):
https://github.com/dongdonghy/repulsion-loss-faster-rcnn-pytorch/blob/master/lib/model/faster_rcnn/repulsion_loss.py
def?IoG(box_a,?box_b):?????????????????????????????????????????????????????????????????????????????????????????????
????inter_xmin?=?torch.max(box_a[0],?box_b[0])?????????????????????????????????????????????????????????????????????
????inter_ymin?=?torch.max(box_a[1],?box_b[1])?????????????????????????????????????????????????????????????????????
????inter_xmax?=?torch.min(box_a[2],?box_b[2])?????????????????????????????????????????????????????????????????????
????inter_ymax?=?torch.min(box_a[3],?box_b[3])?????????????????????????????????????????????????????????????????????
????Iw?=?torch.clamp(inter_xmax?-?inter_xmin,?min=0)???????????????????????????????????????????????????????????????
????Ih?=?torch.clamp(inter_ymax?-?inter_ymin,?min=0)???????????????????????????????????????????????????????????????
????I?=?Iw?*?Ih????????????????????????????????????????????????????????????????????????????????????????????????????
????G?=?(box_b[2]?-?box_b[0])?*?(box_b[3]?-?box_b[1])??????????????????????????????????????????????????????????????
????return?I?/?G???????????????????????????????????????????????????????????????????????????????????????????????????
?該函數(shù)用于計算IoG
def?repgt(pred_boxes,?gt_rois,?rois_inside_ws):
????sigma_repgt?=?0.9
????loss_repgt=torch.zeros(pred_boxes.shape[0]).cuda()??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
????for?i?in?range(pred_boxes.shape[0]):???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
????????boxes?=?Variable(pred_boxes[i,rois_inside_ws[i]!=0].view(int(pred_boxes[i,rois_inside_ws[i]!=0].shape[0])/4,4))?????
????????gt?=?Variable(gt_rois[i,rois_inside_ws[i]!=0].view(int(gt_rois[i,rois_inside_ws[i]!=0].shape[0])/4,4))??????????????
????????num_repgt?=?0
????????repgt_smoothln=0
????????if?boxes.shape[0]>0:
????????????overlaps?=?bbox_overlaps(boxes,?gt)
????????????for?j?in?range(overlaps.shape[0]):
????????????????for?z?in?range(overlaps.shape[1]):
????????????????????if?int(torch.sum(gt[j]==gt[z]))==4:
????????????????????????overlaps[j,z]=0
????????????max_overlaps,?argmax_overlaps?=?torch.max(overlaps,1)
????????????for?j?in?range(max_overlaps.shape[0]):
????????????????if?max_overlaps[j]>0:
????????????????????num_repgt+=1
????????????????????iog?=?IoG(boxes[j],?gt[argmax_overlaps[j]])
????????????????????if?iog>sigma_repgt:
????????????????????????repgt_smoothln+=((iog-sigma_repgt)/(1-sigma_repgt)-math.log(1-sigma_repgt))
????????????????????elif?iog<=sigma_repgt:
????????????????????????repgt_smoothln+=-math.log(1-iog)
????????if?num_repgt>0:
????????????loss_repgt[i]=repgt_smoothln/num_repgt
???
????return?loss_repgt???
這是RepGT_loss代碼,首先進入predbox的for循環(huán)
經(jīng)過一個for循環(huán)遍歷,得到除去目標真實框外,與其IoU最大的真實框
再在for循環(huán)內(nèi),通過IoG函數(shù)計算IOG值,并根據(jù)smooth ln函數(shù)(平滑系數(shù)為sigma_regpt)
最后loss總和除以repgt的個數(shù),取得平均值
def?repbox(pred_boxes,?gt_rois,?rois_inside_ws):
????sigma_repbox?=?0
????loss_repbox=torch.zeros(pred_boxes.shape[0]).cuda()
????for?i?in?range(pred_boxes.shape[0]):
????????
????????boxes?=?Variable(pred_boxes[i,rois_inside_ws[i]!=0].view(int(pred_boxes[i,rois_inside_ws[i]!=0].shape[0])/4,4))
????????gt?=?Variable(gt_rois[i,rois_inside_ws[i]!=0].view(int(gt_rois[i,rois_inside_ws[i]!=0].shape[0])/4,4))
?
????????num_repbox?=?0
????????repbox_smoothln?=?0
????????if?boxes.shape[0]>0:
????????????overlaps?=?bbox_overlaps(boxes,?boxes)
????????????for?j?in?range(overlaps.shape[0]):
????????????????for?z?in?range(overlaps.shape[1]):
????????????????????if?z>=j:
????????????????????????overlaps[j,z]=0
????????????????????elif?int(torch.sum(gt[j]==gt[z]))==4:
????????????????????????overlaps[j,z]=0
????????????iou=overlaps[overlaps>0]
????????????for?j?in?range(iou.shape[0]):
????????????????num_repbox+=1
????????????????if?iou[j]<=sigma_repbox:
????????????????????repbox_smoothln+=-math.log(1-iou[j])
????????????????elif?iou[j]>sigma_repbox:
????????????????????repbox_smoothln+=((iou[j]-sigma_repbox)/(1-sigma_repbox)-math.log(1-sigma_repbox))
????????if?num_repbox>0:
????????????loss_repbox[i]=repbox_smoothln/num_repbox
????????????
????return?loss_repbox這是RepBox loss代碼,第一個for循環(huán)也是進入到預(yù)測框。然后一個小for循環(huán)用來計算overlap,這里還設(shè)置一個if語句塊,用來排除相同的集合(因為我們要保證兩個集合是互斥的子集)。隨后與RepGT類似,計算smoothln函數(shù),最后取平均返回
總結(jié)
曠廠的這篇算法工作做的還是很扎實的,作者先是對數(shù)據(jù)集進行分析,進而根據(jù)遮擋度,拆分出兩個子集,通過直觀的統(tǒng)計來表明行人遮擋是檢測行人的一大難點。然后從預(yù)測框和NMS處理上出發(fā),找到問題所在,進而提出RepLoss,其中兩項loss分別針對兩個獨立的問題。簡單改進模型后,加上RepLoss的效果展示還是非常不錯的。
推薦閱讀

