博客專欄

        EEPW首頁 > 博客 > YOLOv5在無人機/遙感場景下做旋轉(zhuǎn)目標(biāo)檢測時進(jìn)行的適應(yīng)性改建詳解(踩坑記錄)

        YOLOv5在無人機/遙感場景下做旋轉(zhuǎn)目標(biāo)檢測時進(jìn)行的適應(yīng)性改建詳解(踩坑記錄)

        發(fā)布人:計算機視覺工坊 時間:2022-05-15 來源:工程師 發(fā)布文章
        作者丨略略略@知乎(已授權(quán))

        來源丨h(huán)ttps://zhuanlan.zhihu.com/p/358441134編輯丨極市平臺

        文章開頭直接放上我自己的項目代碼:

        https://github.com/hukaixuan19970627/YOLOv5_DOTA_OBBgithub.com/hukaixuan19970627/YOLOv5_DOTA_OBB

        (以下為最初版本代碼,最新代碼以GitHub為準(zhǔn))

        star?還請多多益善。

        前言:以下改建基于2020.10.11日上傳的YOLOv5項目

        現(xiàn)成的YOLOv5代碼真的很香,不管口碑怎么樣,我用著反正是挺爽的,畢竟一個開源項目學(xué)術(shù)價值和工程應(yīng)用價值只要占其一就值得稱贊,而且v5確實在項目上手這一塊非常友好,建議大家自己上手體會一下。

        本文默認(rèn)讀者對YOLOv5的原理和代碼結(jié)構(gòu)已經(jīng)有了基礎(chǔ)了解,如果從未接觸過,可以參考這篇文章:

        深度眸:進(jìn)擊的后浪yolov5深度可視化解析:https://zhuanlan.zhihu.com/p/183838757

        目標(biāo)檢測方法所采取的邊框標(biāo)注方式要按照被檢測物體本身的形狀特征進(jìn)行改變。原始YOLOv5項目的應(yīng)用場景為自然場景下的目標(biāo),目標(biāo)檢測邊框為水平矩形框(Horizontal Bounding Box,HBB),畢竟我們的視角就是水平視角。

        而當(dāng)視角發(fā)生改變時,物體呈現(xiàn)在二維圖像中的形狀特征就會發(fā)生改變,為了更好的匹配圖像特征,人們想出了多種邊框的標(biāo)記方法,比如交通監(jiān)控(鳥瞰)視角下的物體可以采取橢圓邊框進(jìn)行標(biāo)注

        圖片

        視角繼續(xù)上升來到無人機/衛(wèi)星的高度,俯視視角下的物體形狀特征繼續(xù)發(fā)生改變,此時邊框標(biāo)記方式就有了更多的選擇:

        圖片

        至于你問選擇適當(dāng)?shù)倪吙驑?biāo)注方式有什么作用,我個人的理解有以下兩點:

        1. 標(biāo)注方式越精準(zhǔn),提供給網(wǎng)絡(luò)訓(xùn)練時的冗余信息就越少;先驗越充分,網(wǎng)絡(luò)的可學(xué)習(xí)方案就越少,有利于約束網(wǎng)絡(luò)的訓(xùn)練方向和減少網(wǎng)絡(luò)的收斂時間;
        2. 當(dāng)目標(biāo)物體過于緊密時,精準(zhǔn)的標(biāo)注方式可以避免被NMS”錯殺“已經(jīng)檢出的目標(biāo)。
        圖片

        以本圖為例,精準(zhǔn)的標(biāo)注方式可以確保緊密的物體之間的IOU為0;如果標(biāo)注方式改為水平目標(biāo)邊框檢測效果將慘不忍睹。

        那么純俯視角度(無人機/遙感視角)下的物體有哪些常見的標(biāo)注方式呢?可以參考下面這篇文章,且yangxue作者提出的Circular Smooth Label也是YOLOv5改建的關(guān)鍵之處:

        旋轉(zhuǎn)目標(biāo)檢測方法解讀(DCL, CVPR2021)

        上面那篇文章的主要思想就是緩解旋轉(zhuǎn)目標(biāo)標(biāo)注方式在網(wǎng)絡(luò)訓(xùn)練時產(chǎn)生的邊界問題, 這種邊界問題其實可以一句話概括:由于學(xué)習(xí)的目標(biāo)參數(shù)具有周期性,在周期變化的邊界處會導(dǎo)致?lián)p失值突增,因此增大網(wǎng)絡(luò)的學(xué)習(xí)難度。 這句話可以參考下圖進(jìn)行理解:

        圖片

        以180度回歸的長邊定義法中的θ為例,θ ∈[-90,90);正常訓(xùn)練情況下,網(wǎng)絡(luò)預(yù)測的θ值為88,目標(biāo)真實θ值為89,網(wǎng)絡(luò)學(xué)習(xí)到的角度距離為1,真實情況下的兩者差值為1;邊界情況下,網(wǎng)絡(luò)預(yù)測的θ值為89,目標(biāo)真實θ值為-90,網(wǎng)絡(luò)學(xué)習(xí)到的角度距離為179,真實情況下的兩者差值為1.

        那么如何處理邊界問題呢:(以θ的邊界問題為例)

        1. 尋找一種新的旋轉(zhuǎn)目標(biāo)定義方式,定義方式中不含具有周期變化性的參數(shù),卻又能表示周期旋轉(zhuǎn)的目標(biāo)物體,根本上杜絕邊界問題的產(chǎn)生;(Anchor free/mask的思路,PolarDet、P-RSDet基于極坐標(biāo)系表示一個任意四邊形物體,BBA-Vectors、O^2-DNet基于向量來表示一個有向矩形,ROPDet、Beyond Bounding Box、Oriented Reppoints基于點集來表示一個任意形狀的物體,)
        2. 從損失函數(shù)上入手,使用Smooth L1單獨考慮每個參數(shù)時,賦予損失函數(shù)和角度同樣的周期性,使得邊界處θ之間差值可以很大,但loss變化實際很小;或者綜合考慮所有回歸參數(shù)的影響,使用旋轉(zhuǎn)IoU損失函數(shù)也可以規(guī)避邊界問題,不過RIoU不可導(dǎo),近似可導(dǎo)的相關(guān)工作可以參考KLD、GWD,工程上實現(xiàn)RIoU可導(dǎo)的工作可以參考:https://github.com/csuhan/s2anet/blob/master/configs/rotated_iou/README.mdθ由回歸問題轉(zhuǎn)為分類問題。(把連續(xù)問題直接離散化,避開邊界情況)

        其中2,3yangxue大佬都有過相應(yīng)的解決方案,大家可以去他的主頁參考。CSL就是3的思想體現(xiàn),只不過CSL考慮的更多,因為當(dāng)θ變?yōu)榉诸悊栴}后,網(wǎng)絡(luò)就無法學(xué)習(xí)到角度距離信息了,比如真實角度為-90,網(wǎng)絡(luò)預(yù)測成89和-89產(chǎn)生的損失值我們期望是一樣的,因為角度距離實際上都是1。

        所以CSL實際上是一個用分類實現(xiàn)回歸思想的解決方案, 具體細(xì)節(jié)大家移步去上面的文章。我們直接用成果,基于180度回歸的長邊定義法中的參數(shù)只有θ存在邊界問題,而CSL剛好又能處理θ的邊界問題,那么我們”暫且認(rèn)為“CSL+長邊定義法的組合是比較優(yōu)的。之所以說是”暫且“是因為yangxue大佬又在最新的文章里面又提出了這種方式的缺點:

        圖片

        當(dāng)時我的心情如下,那還是方法1的anchor free方案比較好,一勞永逸;

        圖片

        但是這篇文章有部分我還沒有理解透徹,我們還是只用CSL+長邊定義法就行了,后期的升級工作交給各位了。

        標(biāo)注方案確定之后,就可以開始一系列的改建工作了。

        正文:

        基本所有基于深度學(xué)習(xí)的目標(biāo)檢測器項目的結(jié)構(gòu)都分為:

        數(shù)據(jù)加載器(圖像預(yù)處理)--> BackBone(提取目標(biāo)特征) --> Neck(收集組合目標(biāo)特征) --> Head(預(yù)測部分) --> 損失函數(shù)部分

        圖片YOLOv4論文截的圖一、數(shù)據(jù)加載部分

        首先我們必須熟知自己的數(shù)據(jù)在進(jìn)入網(wǎng)絡(luò)之前的數(shù)據(jù)形式是什么樣的,因為我們采用的是長邊定義法,所以我們的注釋文件格式為:

        [  classid    x_c   y_c   longside   shortside    Θ  ]  Θ∈[0, 180)


        * longside: 旋轉(zhuǎn)矩形框的最長邊

        * shortside: 與最長邊對應(yīng)的另一邊

        * Θ: x軸順時針旋轉(zhuǎn)遇到最長邊所經(jīng)過的角度

        至于數(shù)據(jù)形式如何轉(zhuǎn)換,利用好cv2.minAreaRect()函數(shù)+總結(jié)規(guī)律就可以,我的另一篇文章里講的比較清楚,大家可以移步:

        略略略:DOTA數(shù)據(jù)格式轉(zhuǎn)YOLO數(shù)據(jù)格式工具(cv2.minAreaRect踩坑記錄):https://zhuanlan.zhihu.com/p/356416158)

        注意opencv4.1.2版本cv2.minAreaRect()函數(shù)生成的最小外接矩形框(x,y,w,h,θ)的幾個大坑:

        (1) 在絕大數(shù)情況下 Θ∈[-90, 0);

        (2) 部分水平或垂直的目標(biāo)邊框,其θ值為0;

        (3) width或height有時輸出0, 與此同時Θ = 90;

        (4) 輸出的width或height有時會超過圖片本身的寬高,即歸一化時數(shù)據(jù)>1。

        接下來圖像數(shù)據(jù)與label數(shù)據(jù)進(jìn)入到程序中,我們必須熟知在進(jìn)入backbone之前,數(shù)據(jù)加載器流程中l(wèi)abels數(shù)據(jù)的維度變化。

        圖片

        原始yolov5中,labels數(shù)據(jù)維度一直以(X_LT, Y_LT, X_RB, Y_RB)左上角右下角兩點坐標(biāo)表示水平矩形框的形式存在,并一直在做歸一化和反歸一化操作

        由于我們采用的邊框定義法是[x_c y_c longside shortside Θ],邊框的角度信息只存在于θ中,我們完全可以將 [x_c y_c longside shortside] 視為水平目標(biāo)邊框,因此在數(shù)據(jù)加載部分我們只需要在labels原始數(shù)據(jù)的基礎(chǔ)上添加一個θ維度,只要不是涉及到會引起labels角度變化的代碼都不需要更改其處理邏輯。

        注意: 數(shù)據(jù)加載器中存在大量的歸一化和反歸一化的操作,以及大量涉及到圖像寬高度的數(shù)據(jù)變化,因此網(wǎng)絡(luò)輸入的圖像size:HEIGHT 必須= WIDTH,因為長邊定義法中的longside和shorside與圖像的寬高沒有嚴(yán)格的對應(yīng)關(guān)系。

        數(shù)據(jù)加載器中涉及三類數(shù)據(jù)增強方式:Mosaic,random_perspective(仿射矩陣增強),普通數(shù)據(jù)增強方式

        其中Mosaic,仿射矩陣增強都是針對(X_LT, Y_LT, X_RB, Y_RB)數(shù)據(jù)格式進(jìn)行增強,修改時添加θ維度就可以,不過仿射矩陣增強函數(shù)內(nèi)共有 Translation、Shear、Rotation、Scale、Perspective、Center 6種數(shù)據(jù)增強方式,其中旋轉(zhuǎn)與形變仿射的變換會引起目標(biāo)角度上的改變

        所以只要超參數(shù)中的 ['perspective']=0,['degrees']=0 ,這塊函數(shù)代碼就不需要修改邏輯部分,為了方便我們直接把涉及到角度的增強放在最后的普通數(shù)據(jù)增強方式中。

        圖片random_perspective函數(shù)的部分代碼(仿射矩陣增強)圖片在普通數(shù)據(jù)增強代碼塊中添加角度方面的數(shù)據(jù)增強

        注意:Mosaic操作中會同時觸發(fā)MixUp數(shù)據(jù)增強操作,但是在遙感/無人機應(yīng)用場景中我個人認(rèn)為并不適用,首先背景復(fù)雜就是該場景中的普遍難題,MixUp會融合兩張圖像,圖像中的小目標(biāo)會摻雜另一張圖的背景信息(包含形似物或噪聲),從而影響小目標(biāo)的特征提取。(不過一切以實驗結(jié)果為準(zhǔn))

        圖片本人的在數(shù)據(jù)加載器部分使用的超參數(shù)列表二、Backbone部分、Neck部分

        提取圖像特征層的結(jié)構(gòu)都不需要改動。

        三、Head部分

        head部分也就是yolo.py文件中的Detect類,由于我們將θ轉(zhuǎn)為分類問題,因此每個anchor負(fù)責(zé)預(yù)測的參數(shù)數(shù)量為 (x_c y_c longside shortside score)+num_classes+angle_classes。修改Detect類的構(gòu)造函數(shù)即可。

        圖片Detect類構(gòu)造函數(shù)修改:增加180個角度分類通道四、損失函數(shù)部分

        損失函數(shù)共有四個部分:置信度損失、class分類損失、θ角度分類損失、bbox邊框回歸損失。

        (1)計算損失前的準(zhǔn)備工作

        損失的計算需要 targets 與 predicts,每個數(shù)據(jù)的維度都要有所對應(yīng),因此需要general.py文件中的build_targets函數(shù)生成目標(biāo)真實GT的類別信息列表、邊框參數(shù)信息列表、Anchor索引列表、Anchor尺寸信息列表、角度類別信息列表。

        圖片

        其中Anchor索引列表用于檢索網(wǎng)絡(luò)預(yù)測結(jié)果中對應(yīng)的anchor,從而將其標(biāo)記為正樣本。yolov5為了保證正樣本的數(shù)量,在正樣本標(biāo)記策略中采用了比較暴力的策略:原本yolov3僅僅采用當(dāng)前GT中心所在的網(wǎng)格中的anchor進(jìn)行正樣本標(biāo)記,而yolov5不僅采用當(dāng)前網(wǎng)格中的anchor標(biāo)記為正樣本,同時還會標(biāo)記相鄰兩個網(wǎng)格的anchor為正樣本。

        這種處理邏輯個人暫不評價好壞,但是yolov5源碼在代碼實現(xiàn)上顯然考慮不夠周全,目標(biāo)中心所屬網(wǎng)格如果剛好在圖像的邊界位置,yolov5的源碼有時會輸出超過featuremap尺寸的索引。這種bug表現(xiàn)在訓(xùn)練中就是某個時刻yolov5的訓(xùn)練就會中斷:

        Traceback (most recent call last):
        File "train.py", line 457, in <module>
        train(hyp, opt, device, tb_writer)
        File "train.py", line 270, in train
        loss, loss_items = compute_loss(pred, targets.to(device), model) # loss scaled by batch_size
        File "/mnt/G/1125/rotation-yolov5-master/utils/general.py", line 530, in compute_loss
        tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * iou.detach().clamp(0).type(tobj.dtype) # iou ratio
        RuntimeError: CUDA error: device-side assert triggered

        上述報錯顯然是索引時超出數(shù)組取值范圍的問題,解決方法也很簡單,先查詢是哪些參數(shù)超出了索引范圍,當(dāng)運行出錯時,進(jìn)入pdb調(diào)試,打印當(dāng)前所有索引參數(shù):

        圖片

        然后就發(fā)現(xiàn)網(wǎng)格索引gj,gi偶爾會超出當(dāng)前featuremap的索引范圍。(舉例:featuremap大小為32×32,網(wǎng)格索引范圍為0-31,但是build_targets函數(shù)偶爾會輸出索引值32,此時出現(xiàn)bug,訓(xùn)練中斷)

        然而我當(dāng)時在yolov5項目源碼的Issues中卻發(fā)現(xiàn)沒人提交這種問題,原因也很簡單,自然場景的下的目標(biāo)很難標(biāo)注在圖片的邊界位置,但是遙感/無人機圖像顯然相反,由于會經(jīng)過裁剪,極其容易出現(xiàn)目標(biāo)標(biāo)注在邊界位置的情況, 如下圖所示:

        圖片經(jīng)過裁剪的DOTA圖像目標(biāo)所屬網(wǎng)格很容易出現(xiàn)在圖像的邊界位置

        這個BUG屬于yolov5源碼build_targets函數(shù)生成anchor索引時考慮不周全導(dǎo)致的,解決辦法也很簡單,在生成的索引處加上數(shù)值范圍限制(壞處就是可能出現(xiàn)網(wǎng)格重復(fù)利用的情況,比較浪費):

        圖片general.py文件中的build_targets函數(shù)中的bug位置修改

        2021.04.25更新:

        重復(fù)利用就重復(fù)利用唄(~破罐破摔!~),本來yolov5的跨網(wǎng)格正負(fù)樣本標(biāo)記方式就會產(chǎn)生同一個anchor與不同gt進(jìn)行l(wèi)oss計算的問題,這個地方感覺還有很多地方可以優(yōu)化,但就是想不明白這樣子回歸明明會產(chǎn)生二義性問題為什么效果還是很好?

        之后的改建部分也比較機械,在compute_loss函數(shù)和build_targets函數(shù)中添加θ角度信息的處理即可,主要注意數(shù)據(jù)索引的代碼塊就可以,由于添加了‘θ’ 180個通道,所以函數(shù)中所有的索引部分都要更改。

        圖片更改索引部分(以類別損失代碼塊為例)補充:

        今天(2021年3月21日) 我又去yolov5的issues上看了看,似乎20年11月份修復(fù)了這個問題。我這個是基于20年10月11日的代碼改建的,要是晚下載幾天就好了,興許能避開這個坑。

        圖片

        又看了下新的yolov5源碼,很多地方大換血...... 改建的速度還沒人家更新的速度快。

        (2)計算損失
        • class分類損失:

        無需更改,注意數(shù)據(jù)索引部分即可。

        • θ角度分類損失:

        由于我們添加的θ是分類任務(wù),照葫蘆畫瓢,添加分類損失就可以了,值得注意的是θ部分的損失我們有兩種方案:

        1. 一種就是正常的分類損失,同類別損失一樣:BCEWithLogitsLoss;
        2. 先將GT的θ label經(jīng)CSL處理后,再計算類別損失:BCEWithLogitsLoss。

        項目代碼中同時實現(xiàn)了兩種方案,由csl_label_flag進(jìn)行控制,csl_label_flag為True則進(jìn)行CSL處理,否則計算正常分類損失,方便大家查看CSL在自己數(shù)據(jù)集上的提升效果:

        圖片θ角度分類損失的兩種方案
        • bbox邊框回歸損失:

        yolov5源碼中邊框損失函數(shù)采用的是IOU/GIOU/CIOU/DIOU,適用于水平矩形邊框之間計算IOU,原本是不適用于旋轉(zhuǎn)框之間計算IOU的。由于框會旋轉(zhuǎn)等原因,計算兩個旋轉(zhuǎn)框之間的IOU公式通常都不可導(dǎo),如果θ為回歸任務(wù),勢必要通過旋轉(zhuǎn)IOU損失函數(shù)進(jìn)行反向傳播從而調(diào)整自身參數(shù),大多數(shù)旋轉(zhuǎn)檢測器的處理辦法都是將不可導(dǎo)的旋轉(zhuǎn)IOU損失函數(shù)進(jìn)行近似,使得網(wǎng)絡(luò)可以正常進(jìn)行訓(xùn)練。

        不過因為我們將θ視為分類任務(wù)來處理,相當(dāng)于將角度信息與邊框參數(shù)信息解耦,所以旋轉(zhuǎn)框的損失計算部分也分為角度損失和水平邊框損失兩個部分,因此源碼部分可以不進(jìn)行改動,邊框回歸損失部分依舊采用IOU/GIOU/CIOU/DIOU損失函數(shù)。

        • 置信度損失:

        這一部分我們需要考慮清楚,yolov5源碼是將GT水平邊框與預(yù)測水平邊框的IOU/GIOU/CIOU/DIOU值作為該預(yù)測框的置信度分支的權(quán)重系數(shù),由于改建的情況特殊(水平邊框+角度),我們有兩種選擇:

        1. 置信度分支的權(quán)重系數(shù)依然選擇水平邊框的之間的IOU/GIOU/CIOU/DIOU;
        2. 置信度分支的權(quán)重系數(shù)為旋轉(zhuǎn)框IOU。

        方案1相當(dāng)于完全解耦預(yù)測角度與預(yù)測置信度之間的關(guān)聯(lián),置信度只與邊框參數(shù)有關(guān)聯(lián),但事實上角度的一點偏差對旋轉(zhuǎn)框IOU的影響是很大的,這種做法可能會影響網(wǎng)絡(luò)最后對目標(biāo)的score預(yù)測,導(dǎo)致部分明明角度預(yù)測錯誤但是邊框參數(shù)預(yù)測正確的冗余框有過大的score,從而NMS無法濾除,最終影響檢測精度

        2021.04.22更新:

        方案1速度比方案2訓(xùn)練快很多,gpu利用率也更穩(wěn)定,而且預(yù)測出來的框的置信度相比來說會更高,就是可能錯檢的情況會多一點(θloss收斂正常,置信度loss收斂正常的話該情況會得到明顯緩解)

        方案2除了錯檢情況少一點以外,其余都是缺點,大家可以自行對比嘗試。不過缺點后期可以通過cuda加速來改善,畢竟DOTA_devkit提供的C++庫計算效率確實不高。再加上代碼不是自己寫的,想直接套用別的旋轉(zhuǎn)IoU代碼就只能用時間效率賊低的for循環(huán)來做。

        圖片旋轉(zhuǎn)IOU隨角度偏移量的變化曲線

        方案2自然是為了避免上述情況的產(chǎn)生,此外也是對將角度解耦出去的一種”補償“。(至于網(wǎng)絡(luò)能否學(xué)到這一層補償那就不得而知,畢竟conf分支的權(quán)重系數(shù)不會通過反向傳播的方式進(jìn)行更新——detach的參數(shù)不會參與網(wǎng)絡(luò)訓(xùn)練)

        不會計算旋轉(zhuǎn)IOU也沒關(guān)系,DOTA數(shù)據(jù)集的作者額外提供了一個DOTA_devkit工具,里面有現(xiàn)成的C++庫,我們直接調(diào)用即可。

        圖片通過計算預(yù)測框與GT框之間的旋轉(zhuǎn)IOU來作為conf分支的權(quán)重系數(shù)五、其他修改部分

        數(shù)據(jù)加載器(圖像預(yù)處理)--> BackBone(提取目標(biāo)特征) --> Neck(收集組合目標(biāo)特征) --> Head(預(yù)測部分) --> 損失函數(shù)部分

        以上部分基本修改完畢,接下來就是可視化的部分,利用好Opencv的三個函數(shù)即可:

        # rect = cv2.minAreaRect(poly)   # 得到poly最小外接矩形的(中心(x,y), (寬,高), 旋轉(zhuǎn)角度)
        # box = np.float32(cv2.boxPoints(rect))  # 返回最小外接矩形rect的四個點的坐標(biāo)
        # cv2.drawContours(image=img, contours=[poly], contourIdx=-1, color=color, thickness=2*tl)

        大家可以參考我上傳的項目代碼,里面基本每段代碼都會有我的注解(主要是當(dāng)時自己剛開始看yolov5源碼,每句話都有注釋)。

        圖片訓(xùn)練:一個batch的圖像數(shù)據(jù)可視化圖片檢測:預(yù)測結(jié)果可視化圖片若目標(biāo)數(shù)量太多,可以選擇不顯示label(label=None)

        改建部分完結(jié)撒花,歡迎討論!

        本文僅做學(xué)術(shù)分享,如有侵權(quán),請聯(lián)系刪文。


        *博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。



        關(guān)鍵詞: AI

        相關(guān)推薦

        技術(shù)專區(qū)

        關(guān)閉
        主站蜘蛛池模板: 治多县| 邻水| 临夏县| 如皋市| 黎城县| 子长县| 萍乡市| 色达县| 汾西县| 十堰市| 湛江市| 东城区| 宿州市| 酒泉市| 抚顺县| 三河市| 时尚| 青浦区| 阳原县| 宽城| 墨竹工卡县| 甘洛县| 云阳县| 梨树县| 区。| 垦利县| 抚宁县| 海晏县| 谷城县| 南汇区| 宜宾县| 崇仁县| 龙江县| 洛宁县| 河津市| 北宁市| 烟台市| 揭阳市| 南岸区| 木里| 图木舒克市|