使用SSD模型檢測教學場景下的“舉手”目標
由於專案需求,最近花了約三週的時間,嘗試在我們自己的教學場景資料集上,完成SSD目標檢測模型的測試,檢測目標只有一個類別:舉手(Handraising)。實際上,專案中已經存在可以完成舉手目標檢測的方案R-FCN,所以目的是為了驗證SSD是否會有檢測效果和檢測速度的提升,這裡簡要記錄一下整個流程,儘管之後在測試資料集上,SSD的檢全率和準確率並不比R-FCN更好。
一、背景介紹:
SSD(Single Shot MultiBox Detector)是2016出來的一篇目標檢測的文章,實際比R-FCN稍早一些。在該文中,作者指出他們提出的新方法比之前的目標檢測方法都要好(準確率、檢測速度兩個方面都有優勢截止到SSD釋出,它應該是最優的目標檢測演算法,之後同年公佈的R-FCN也表現出幾乎相同的檢測準確率,但並未與SSD作比較。到了現在,物體檢測方面最優的方法應該要算YOLO v2,下面是近些年出現的一些物體檢測演算法列表:
PS:總結主要來自部落格
DPM(時間 2008)
Adiscriminatively trained, multiscale, deformable part model
OverFeat(時間 2013)
OverFeat:Integrated Recognition, Localization and Detection using Convolutional Networks
SPP-Net(時間 2015)
Spatial PyramidPooling in Deep Convolutional Networks for Visual Recognition
DeepID-Net(時間 2014)
DeepID-Net:Deformable Deep Convolutional Neural Networks for Object Detection
RCNN(時間 2014)
Rich featurehierarchies for accurate object detection and semantic segmentation
Fast RCNN(時間 2015)
Fast R-CNN
Faster RCNN(時間 2015)
Faster R-CNN towards real-time object detection with region proposalnetworks
R-FCN(時間 2016)
R-FCN Object Detection via Region-based Fully Convolutional Networks
Yolo(時間 2016)
You Only Look Once - Unified, Real-Time Object Detection
SSD(時間 2016)
SSD Single Shot MultiBox Detector
Yolo v2(時間 2016)
YOLO9000 - Better, Faster, Stronger
Mask R-CNN(時間 2017)
Mask R-CNN
……
可以看到,物體檢測演算法層出不窮,讓人應接不暇,這裡只選取SSD物體檢測演算法來進行總結,理論分析部分不再討論,詳細記述模型使用過程。
二、SSD配置及除錯步驟:
原始碼網址:https://github.com/weiliu89/caffe/tree/ssd
其實github上已經給出了詳細使用步驟,這裡再重複一遍,同時就自己遇到的一些問題給出解決辦法。
這裡按照該網址提供的步驟來記錄
1、Installation
2、Preparation
3、Train/Eval
4、Models
1、Installation
1)首先是下載原始碼並安裝,選擇將其放在自己的某個資料夾下
git clone https://github.com/weiliu89/caffe.git
cd caffe
git checkout ssd
(出現“分支”則說明copy-check成功...作者caffe目錄下有三個分支fcn/master/ssd, 利用git checkout來切換分支,否則只有master目錄下的檔案)
2)之後需要編譯原始碼
# Modify Makefile.config according to your Caffe installation.
cp Makefile.config.example Makefile.config
#這裡需要根據電腦具體的配置修改Makefile.config
make -j8
# Make sure to include $CAFFE_ROOT/python to your PYTHONPATH.
make py
make test -j8
# (Optional)
make runtest -j8
編譯過程中,只要配置好了caffe所需要的檔案,一般不會出現什麼問題
2、Preparation
1)下載已經訓練好的VGGNet16模型,fullyconvolutional reduced (atrous) VGGNet,確認將其放在$CAFFE_ROOT/models/VGGNet/ 目錄下
網址中提供的連結不可用,這裡使用網上搜到的預訓練模型
連結:http://pan.baidu.com/s/1miDE9h2
密碼:0hf2
可見SSD是基於VGGNet16的物體檢測演算法,如果該model已經掛了,就自己搜尋吧~
2)下載VOC2007、VOC2012資料集,並將其放在 $HOME/data/ 目錄下,也可以修改之,但同時記得修改之後的指令碼中出現的該目錄的引用
# Download the data.
cd $HOME/data
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
wgethttp://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
# 下載完畢
# Extract the data.
tar -xvf VOCtrainval_11-May-2012.tar
tar -xvf VOCtrainval_06-Nov-2007.tar
tar -xvf VOCtest_06-Nov-2007.tar
# 解壓完畢
3)使用資料集,建立LMDB(Lightning Memory-Mapped Database)檔案
cd $CAFFE_ROOT
# Create the trainval.txt, test.txt, and test_name_size.txt indata/VOC0712/
./data/VOC0712/create_list.sh
# 如果修改了資料集的位置,需要修改 create_list.sh 中相應的地址
執行結果:
/home/zhouhuayi/SSD/caffe$ ./data/VOC0712/create_list.sh
Create list for VOC2007 trainval...
Create list for VOC2012 trainval...
Create list for VOC2007 test...
I0122 14:30:12.952246 25110 get_image_size.cpp:61] A total of 4952 images.
I0122 14:30:14.953508 25110 get_image_size.cpp:100] Processed 1000 files.
I0122 14:30:16.875109 25110 get_image_size.cpp:100] Processed 2000 files.
I0122 14:30:18.889964 25110 get_image_size.cpp:100] Processed 3000 files.
I0122 14:30:20.848163 25110 get_image_size.cpp:100] Processed 4000 files.
I0122 14:30:22.774821 25110 get_image_size.cpp:105] Processed 4952 files.
create_list.sh指令碼將VOC2007、VOC2012中的圖片混合到了一起,得到的trainval.txt,test.txt, test_name_size.txt三個txt文字中,前兩個分別是訓練集、測試集中圖片的名稱,test_name_size.txt則是測試集圖片名稱和對應的長寬
# You can modify the parameters in create_data.sh if needed.
# It will create lmdb files for trainval and test with encoded originalimage:
# -$HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb
# -$HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb
# and make soft links at examples/VOC0712/
./data/VOC0712/create_data.sh
create_data.sh才正式生成lmdb檔案
這一步總是出現問題,報錯如下:
Traceback (most recent call last):
File"/home/zhouhuayi/caffe/scripts/create_annoset.py", line 7, in<module>
from caffe.proto importcaffe_pb2
ImportError: No module named caffe.proto
Traceback (most recent call last):
File"/home/zhouhuayi/caffe/scripts/create_annoset.py", line 7, in<module>
from caffe.proto importcaffe_pb2
ImportError: No module named caffe.proto
上網查詢解決方法,發現問題是沒有把caffe中的和python相關的內容的路徑新增到python的編譯路徑中。
第一種解決方案是修改 .bashrc 檔案,但在我這邊實際沒有用,出現類似問題的不妨嘗試一下
$ cat .bashrc
$ echo "export PYTHONPATH=/home/zhouhuayi/caffe/python">>.bashrc
#在 .bashrc 檔案中追加python相關路徑
$ source ~/.bashrc
#儲存上述修改
之後執行仍舊出現上述bug,該方案不能解決問題
第二種解決方案是,修改出現問題的 .py檔案,每一次都明確指定python的相關路徑,使用python呼叫caffe時,在相應的.py檔案的最前面加入以下四句:
caffe_root = '/home/zhouhuayi/SSD/caffe/'
import sys
sys.path.insert(0, caffe_root + 'python')
import caffe
問題解決,不過使用該方法,所有呼叫caffe框架的.py檔案中都要包含這四行,略顯麻煩。
這裡修改的是 /caffe/scripts/create_annoset.py 檔案
構建LMDB資料成功後,介面輸出的結果:
/home/zhouhuayi/caffe$ ./data/VOC0712/create_data.sh
I0118 13:58:33.651304 18763 convert_annoset.cpp:122] A total of 4952images.
I0118 13:58:33.670905 18763 db_lmdb.cpp:35] Opened lmdb/data/zhouhuayi/VOC/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb
I0118 13:58:40.737097 18763 convert_annoset.cpp:195] Processed 1000 files.
I0118 13:58:47.773864 18763 convert_annoset.cpp:195] Processed 2000 files.
I0118 13:58:54.553117 18763 convert_annoset.cpp:195] Processed 3000 files.
I0118 13:59:02.140028 18763 convert_annoset.cpp:195] Processed 4000 files.
I0118 13:59:08.677724 18763 convert_annoset.cpp:201] Processed 4952 files.
/home/zhouhuayi/caffe/build/tools/convert_annoset --anno_type=detection--label_type=xml--label_map_file=/home/zhouhuayi/caffe/data/VOC0712/../../data/VOC0712/labelmap_voc.prototxt--check_label=True --min_dim=0 --max_dim=0 --resize_height=0 --resize_width=0--backend=lmdb --shuffle=False --check_size=False --encode_type=jpg--encoded=True --gray=False /data/zhouhuayi/VOC/VOCdevkit//home/zhouhuayi/caffe/data/VOC0712/../../data/VOC0712/test.txt/data/zhouhuayi/VOC/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb
I0118 13:59:09.883656 18802 convert_annoset.cpp:122] A total of 16551images.
I0118 13:59:09.884400 18802 db_lmdb.cpp:35] Opened lmdb/data/zhouhuayi/VOC/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb
I0118 13:59:31.119462 18802 convert_annoset.cpp:195] Processed 1000 files.
I0118 13:59:52.780809 18802 convert_annoset.cpp:195] Processed 2000 files.
I0118 14:00:13.609648 18802 convert_annoset.cpp:195] Processed 3000 files.
I0118 14:00:34.121955 18802 convert_annoset.cpp:195] Processed 4000 files.
I0118 14:00:54.767715 18802 convert_annoset.cpp:195] Processed 5000 files.
I0118 14:01:15.330397 18802 convert_annoset.cpp:195] Processed 6000 files.
I0118 14:01:36.558912 18802 convert_annoset.cpp:195] Processed 7000 files.
I0118 14:01:57.138043 18802 convert_annoset.cpp:195] Processed 8000 files.
I0118 14:02:18.016297 18802 convert_annoset.cpp:195] Processed 9000 files.
I0118 14:02:39.436408 18802 convert_annoset.cpp:195] Processed 10000files.
I0118 14:03:00.065383 18802 convert_annoset.cpp:195] Processed 11000files.
I0118 14:03:21.318958 18802 convert_annoset.cpp:195] Processed 12000files.
I0118 14:03:41.989552 18802 convert_annoset.cpp:195] Processed 13000files.
I0118 14:04:03.185189 18802 convert_annoset.cpp:195] Processed 14000files.
I0118 14:04:24.233533 18802 convert_annoset.cpp:195] Processed 15000files.
I0118 14:04:45.259112 18802 convert_annoset.cpp:195] Processed 16000files.
I0118 14:04:57.101454 18802 convert_annoset.cpp:201] Processed 16551files.
/home/zhouhuayi/caffe/build/tools/convert_annoset --anno_type=detection--label_type=xml--label_map_file=/home/zhouhuayi/caffe/data/VOC0712/../../data/VOC0712/labelmap_voc.prototxt--check_label=True --min_dim=0 --max_dim=0 --resize_height=0 --resize_width=0--backend=lmdb --shuffle=False --check_size=False --encode_type=jpg--encoded=True --gray=False /data/zhouhuayi/VOC/VOCdevkit//home/zhouhuayi/caffe/data/VOC0712/../../data/VOC0712/trainval.txt/data/zhouhuayi/VOC/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb
3、Train/Eval
1)開始訓練
# It will create model definition files and save snapshot models in:
# -$CAFFE_ROOT/models/VGGNet/VOC0712/SSD_300x300/
# and job file, log file, and the python script in:
# -$CAFFE_ROOT/jobs/VGGNet/VOC0712/SSD_300x300/
# and save temporary evaluation results in:
# -$HOME/data/VOCdevkit/results/VOC2007/SSD_300x300/
# It should reach 77.* mAP at 120k iterations.
python examples/ssd/ssd_pascal_300x300.py
經過120000次的訓練之後,我們可以得到所需要的帶有超引數的訓練model
執行時出現了下面的錯誤:
Check failed: error == cudaSuccess (10 vs. 0) invalid device ordinal
這是由於GPU數量不匹配造成的,如果訓練自己的資料,那麼我們只需要將solver.prototxt檔案中的device_id項改為自己的GPU塊數,一塊就是0,兩塊就是1,以此類推。SSD配置時的例子是將訓練語句整合成一個python檔案ssd_pascal.py,所以需要改此程式碼。
解決方法:
將ssd_pascal.py檔案中第332行gpus = "0,1,2,3"的GPU選擇改為gpus = "0",後面的1,2,3都刪掉,再次訓練即可。由於我訓練時的119伺服器上有兩塊GPU,所以將gpus = "0,1,2,3"修改為gpus ="1"或gpus = "0",問題解決。
這個訓練過程十分漫長,我的GPU是K40c,相較作者文中使用的TitanX十分老舊,於是迭代120k次花費了近4~5天的時間,後來驗證得到的model確實達到了文中所說的精度。之後才覺得完全沒必要這樣做,費時又無用,還不如直接準備自己的資料集,上手訓練測試,方便快捷。
2)模型測試
# If you would like to test a model you trained, you can do:
python examples/ssd/score_ssd_pascal_300x300.py
這裡就是單純測試model在測試集上的mAP(mean Average Precision),如果訓練階段已經加入了test網路,這一步驟實際已經完成了。
也可以檢視單張圖片的檢測效果,需要修改引數,甚至可以用來測試批量圖片
python examples/ssd/ssd_detect.py
4、Models
作者訓練好的3個數據集上的模型
PASCAL VOC models
COCO models
ILSVRC models
到這裡,完成了SSD的配置和除錯工作,在此基礎上,可以開始訓練自己的資料集。
三、使用SSD訓練自己的舉手資料集:
遵循上面的步驟,這裡可以直接開始準備自己的資料集。
假設已經在某個資料夾下按照VOC資料集的格式放置了自己舉手資料集
JPEGImages資料夾下是原始圖片幀,這裡都是從視訊中擷取的圖片,尺寸均為1080x1920
Annotations-only-handraising資料夾下是xml標籤資料,是人工提前將舉手物件標記出來的矩形框,也就是GroundTruth,格式模仿VOC資料集
ImageSets中是各類的txt文字,主要包括檢測物件的訓練集(trainval.txt)、測試集(test.txt),剩下的train,txt、val.txt好像沒有單獨使用,二者之和就是trainval.txt。由於我只是用SSD檢測舉手,也就是隻有一個類,所以只有這四個txt文字
lmdb資料夾下是之後生成的lmdb檔案
準備好資料,開始修改編輯create_list.sh
以上畫線部分是需要修改的地方。前兩個和資料集的位置有關,不再解釋,後兩個是我有遇到問題的地方。
由於trainval.txt、test.txt文字中,每一行結尾都是回車符’\r’,以致生成的圖片名稱、標籤名稱一一對應資料格式出問題,從字尾名開始總是換行,所以需要加上 ’\r’將其替換掉
原語句始:sed -i "s/$/.jpg/g" $img_file
現修改為:sed -i "s/\r$/.jpg/g"$img_file
再順利執行下列命令
./data/HR119/create_list.sh
接著是生成對應的lmdb資料
依舊是隻需要修改資料集的位置。
值得一提的是,這裡還要修改labelmap_voc.prototxt檔案,VOC資料集中共有20個類,加上background,num_classes一共是21,而我只有舉手一個檢測物件,num_classes是2,labelmap_voc.prototxt修改成下列格式
再順利執行下列命令,就可以完成lmdb資料的製作
./data/HR119/create_data.sh
接著,進入訓練階段,需要修改的是examples/ssd/ssd_pascal.py檔案。
主要注意以下幾個部分:
1、各種訓練集、測試集的位置。VOC0712都要換成自己的資料集名稱,記得在對應位置提前新建好資料夾,我的都修改成HR119
2、 resize的尺寸。文中提供的有300x300和512x512兩種
如果是512,還要新增額外新增一層卷積層,如下就是需要多新增的卷積層conv6_1。
如果不確定新增的是否正確,作者提供的訓練好的SSD512模型中也有修改好的py指令碼,可自行下載
3、檢測物件類別數。這裡的數量需要+1,因為有個類別是background
4、 根據GPU的塊數和是否空閒,修改引數gpus
5、batchsize、base_lr根據實際調整,一般GPU爆存要調小塊大小,loss崩掉(增大為nan)要適當調小學習率
其中,如果訓練階段GPU爆存,可以將測試階段去掉
6、 修改設定solver.prototxt。這裡是在指令碼中調整
以上,主要就是調整迭代次數’max_iter’、暫時儲存節點’snapshot’(可以在意外停止訓練時,從儲存的最新狀態繼續恢復訓練),其他引數基本不需要修改
終於可以開始訓練自己的資料集了,執行以下命令,之後就是耐心的等待了~
python examples/ssd/ssd_HR119_512x512.py
四、問題總結和思考:
一開始,我使用的是SSD300,但是訓練之後的效果非常差,舉手物件的錯檢、漏檢非常多。
比如下面這張圖片391_0041_Student.jpg,人工數的方法GroundTruth是21,使用迭代40000次的SSD300模型檢測,並將DetectionBoxing的Threshold設定為0.25,結果正檢數量只有5,漏檢數達到16。
有嘗試過將Threshold調低一些,但是相應的錯檢率就會上升,說到底還是SSD300模型不能有效區分舉手和非舉手。
仍舊是圖片391_0041_Student.jpg,使用迭代40000次的SSD512模型檢測,並將DetectionBoxing的Threshold設定為0.25,結果正檢數量是20,漏檢數為1,錯檢數為3。
其中,錯檢中有一個是老師舉手,該干擾不可避免,另外兩個誤檢和一個漏檢就是模型自身的缺陷。之後在測試資料集上,發現SSD512的漏檢率勉強能接受,但錯檢率太高,這是致命缺陷,將Threshold設定為0.3,在犧牲檢全率的情況下,正檢率提升仍舊不明顯。
圖中,正檢數量是17,漏檢數為4,錯檢數為2。
下圖是R-FCN的檢測結果:
雖然單張的檢測結果優勢不明顯,但全方位統計之後,在我們檢測舉手的場景下,R-FCN較SSD有很大優勢。
總結起來就是,在我們的應用場景下,檢測效果SSD300 < SSD512< R-FCN
再將各階段的嘗試總結如下:
第一階段
沒有改變resize的大小,保持預設的300x300,設定迭代次數為40000次,lr經過調整,比原文稍小loss才會收斂,結果訓練得到的模型效果十分差勁,這樣也就再次浪費了2到3天的時間。經過分析,VOC資料集中的圖片,尺寸大都是300x500左右,且清晰度較高,要檢測的物體相對整個圖片所佔比例也較大(大物體檢測),而我們自己的資料集圖片,是從視訊檔案中擷取的幀,大小均為1080x1920,清晰度較低,舉手作為檢測物件,符合小物體檢測特徵,所以猜測是以上的資料集缺陷,導致檢測效果不理想。於是修改網路結構,在原始的網路結構上,新增新的卷積層,訓練階段使用較大的resize值。
第二階段
將resize的調大為512x512,依舊設定迭代次數為40000次,同樣lr經過調整比300x300的還要小一些才能正常訓練。其中還出現了GPU視訊記憶體不足的問題,自然是因為resize增大,輸入的圖片更大,所以需要將batchsize相應調小些,必要時,可以設定成1。期間,在test階段又出現了GPU爆存的情況,所以test_batch_size也應調小一些,必要時可以設定訓練階段不進行test。這樣,又經過2到3天的時間的訓練,得到的新模型效果大大改善,和RFCN的檢測效果相對比,表現甚至基本接近,但後來統計舉手檢全率、準確率,效果還是有差距的。
且SSD512情況,依舊存在一些問題:
1、test的準確率(mAP)非常低。可能是因為每個DetectionBounding的置信度偏低,導致計算出的AveragePrecision偏低
2、模型的Threshold需要設定的非常低才能使用。由於每個DetectionBounding的置信度偏低,也可以說舉手沒有被有效區分出來,所以實際設定中Threshold調整為0.25左右才能有較好效果,這與RFCN中0.8的閾值相去甚遠
3、對於拍手、打手勢等一些易與舉手混淆的物件的剔除不太理想。這個問題也出現在RFCN的模型中,實際上這個問題也是影響檢測準確率的關鍵因素
後來,通過增加迭代次數至80000,檢測效果依舊沒有得到提升。
第三階段
繼續探索將resize的調大為1024x1024,訓練效果是否會進一步提升。由於圖片實在過大,此次batchsize必須設定為1,且不能在訓練階段進行測試,為了儘可能準確,迭代次數增大為80000次,lr同樣變得更小,訓練完成耗費了3到4天的時間。在訓練過程中生成的中間結果(40000迭代),可以暫時用來檢測效果是否有提升。結果顯示,相比512x512的三個問題,有以下改變:
1、test的準確率(mAP)無法測試。儘管將test_batch_size的大小設定成1,GPU卻還總是爆存,原因尚不明確
2、模型的Threshold顯著提升。這裡檢測到的降序排名靠前的DetectionBounding的置信度很高,甚至將threshold設定為0.8,仍舊能區分得到大部分舉手物件,但結果不比512x512的模型更好,可能是訓練次數還不夠的原因
3、對於拍手、打手勢等一些易與舉手混淆的物件,這裡表現的也更差勁了,不確定是否也是因為訓練次數不夠
可惜的是,之後80000次迭代完成後,模型反而更差勁了,可能是發生了過擬合,至此才放棄了SSD替代R-FCN來檢測舉手的想法。
以上,儘管嘗試SSD沒有得到更好的結果,但整個過程還是學到了很多,也為之後的實驗積累了經驗,遂記錄之。
2018年2月9日18點03分
PS:不要是用Microsoft Edge瀏覽器線上編輯部落格,儘量使用Google Chorme!!!