做了大概2個多月人臉檢測,從查最近三年的論文到確定技術路線,再到實現其中的幾篇,遇到了不少坑。這些坑都是一些小細節,這種小細節在論文里面基本都是一筆帶過或者根本就沒說,而國內外的一些博客和問答網站上,又基本沒有給出過太好的答案。在此總結一下,同時也希望給在做或者准備做這方面的人一些有用的提示。
1.怎樣選擇合適的算法做人臉檢測
最近人臉檢測算法的流派包括三類以及這三類之間的組合:viola-jones(后面簡稱vj)框架,dpm和cnn。如果准備搞學術刷性能,建議搞dpm和cnn這兩個,尤其是cnn目前比較有搞頭。如果是為了在服務器上搭一個性能不錯但是也要兼顧一些速度的人臉檢測服務,建議搞cnn和vj這兩個,因為dpm實在是太慢了,如果是dpm+cnn更是慢上加慢。相比之下,單獨使用cnn就可以獲得很不錯的性能,同時如果有gpu服務器的話,速度也尚可。vj的話,性能一般但是速度是沒問題的。如果准備在移動端or嵌入式上使用(就是計算能力,內存,甚至代碼和模型大小都有限制),而且還想實時的話,基本上就只能選vj了,當然特征不一定要用haar。hog,lbp,pixel-difference以及一系列手工設計的特征都可以一戰。其實如果待檢測的人臉沒有特別大幅度的姿態變化,傳統的vj就夠了,新的方法主要還是在表達能力(說白了就是能檢測到更多千奇百怪的人臉)上增強了很多。如果你的人臉沒那么奇怪(例如:嚴重遮擋、旋轉的特別厲害、特別不清晰等等),用vj也就夠了。
2.怎樣收集負樣本圖片
沒有人臉的那種風景照片基本是不行的。說白了人臉檢測器就是把一張圖片中的人臉和背景區分開,所以負樣本也應該是從帶有人臉的圖片中收集。例如像aflw這種公開數據集中,人臉比較小,背景變化也比較大,就可以用一些確定沒有人臉的圖片遮擋住一張圖片中的人臉位置,這張圖片就可以當作負樣本圖片了。當然,大多數公開數據集並沒有把一張圖片中的所有人臉標注出來,所以多數情況下還需要自己把上述方法處理過的圖片再過一下。
3.在每個stage怎樣生成負樣本
我目前在搞基於vj和cnn的兩套結構。這個問題主要存在於vj框架中,因為vj框架在每個stage開始訓練之前,都需要利用當前的人臉檢測器,把正樣本過一遍,留下true positive,然后再從負樣本圖片中選取一些被錯分的區域(patch),即false positive,用作補充,和之前stage被錯分的負樣本放在一起,作為當前stage的負樣本。我查閱了一些論文和網上的資料,再加上我個人分析,一般來講,合理的負樣本生成策略如下:
(1)隨機選取一張負樣本圖片
(2)從負樣本圖片中隨機選取一個區域(left,top,size)
(3)將(2)中的區域送入當前的分類器,如果被錯分成正樣本,就保留下來,用於接下來stage的訓練。
(4)重復(1)(2)(3)直到收集到足夠的用於接下來stage訓練的負樣本。一般來講,新生成的負樣本+上個stage剩下的負樣本=當前stage剩下的正樣本。例如在第6個stage,剩下10000正樣本,5000負樣本,那么需要收集的負樣本數量是5000。
再次申明,上面是我的個人理解,如果有問題歡迎指正or討論。乍一看上面的策略好像沒啥問題,但是當stage越來越多,當前分類器的fpr越來越小時,被錯分的負樣本就會越來越少,生成負樣本也越來越難。例如:當前fpr=1e-5,如果要生成10000負樣本,就需要測試10e4/1e-5=10e9(10億)個區域。所以訓練到最后,大部分的耗時都在生成負樣本,而不是訓練weak learner了。對於這種情況,我目前找的策略如下:
(1)盡量多收集負樣本圖片,增加負樣本的多樣性。
(2)如果內存允許,盡量多的把負樣本圖片讀取進內存,這樣在讀取圖片的時候會快很多,和運行分類器相比,磁盤讀取和圖片解碼就慢多了。
(3)如果cpu允許,openmp使勁開多線程。
4.訓練集、驗證集和測試集
vj的原論文和soft-cascade的論文中都提到,要把數據分成train set, validate set和test set。train set用於訓練,validate set用於在每個stage訓練完成后,測試當前分類器的tpr,fpr。test set用於最后測試分類器的實際性能。但在實際操作中,validate set這部分很少有人做,而缺少這部分訓練出的分類器性能也是可以接受的,所以可以不用拘泥於這部分,畢竟數據那么有限,還是多放一些數據訓練吧。
5.程序中的坑
(1)負樣本圖片太多太大
為了獲取更好的性能,我按照一些論文中的說法,逐漸從aflw中生成負樣本圖片。在負樣本只有幾百到幾千張的時候,圖片解碼后保存在內存中,大概只占用1-2G,沒什么問題。但隨着負樣本圖片越來越多,在達到上萬張后,內存的占用量提升到了幾十G。雖然我是在服務器上跑的,但考慮到一台服務器有多個人同時使用/跑多個訓練程序,這個占用量也很驚人了。為此,我考慮了一些解決方法,都是利用時間換空間。基本上想要內存占用少,只能延長訓練時間了。最極端的做法是,當某個負樣本被需要時,才從硬盤中讀取到內存,該方法因為存在大量磁盤io,使訓練過程變得極慢。好一點的方法是,維護一定的內存大小,例如10G,然后只有10G的樣本被載入內存,當那些沒有被載入的樣本需要使用時,就把目前內存中的一些樣本干掉,這樣就有空間載入新樣本了。該方法對訓練速度的影響要小一些(取決於你允許的內存使用量和所有樣本量的比例),但是程序設計起來較復雜,如果使用第三方庫來實現,也會增加額外的依賴。這個問題我目前也沒有想到即省空間又不太影響時間的方案,如果哪位大神知道,還望不吝賜教。
(2)圖像越界
對於取像素值的時候,最好還是做一些越界保護,例如:
int x=max(min(x,width),0)
int y=max(min(y,height),0)
release版訓練程序跑到一半崩潰查越界也是夠酸爽的
(3)如果需要生成隨機數,盡量不要用C自帶的
C的偽隨機序列生成算法相對還是比較弱的(周期不夠長),而且不支持多線程。網上有很多很好用的隨機序列生成算法,我使用的是randomc。
6.如何測試程序正確性
說實話這個真的挺難,我的做法是,可以先自己偽造一些小規模數據,這樣自己就知道輸入和對應的輸出了。單步調試的時候,算法每個步驟的中間結果基本也可以手算出來,然后和程序中的結果對比。但還是有些算法,不訓練完是不知道模型是否正確的,這種情況就只能先用一些簡單參數&小訓練集搞一個比較弱但是也湊合能用的模型,在測試集上測試,如果弱爆了(預測結果跟隨機一樣),那就說明算法訓練代碼有問題。