最近用到Faster RCNN進行目標檢測,前前后后兩周把RCNN,SPPNet,Fast RCNN和Faster RCNN大體調查了一遍,準備寫一個RCNNs系列,后面還要加上今年最新的Mask RCNN。
要想開個頭,知道RCNNs在目標檢測方向的優勢,那就先用用作者的代碼,跑跑自己的代碼,下面就是在Ubuntu下進行Faster RCNN配置的方法。
一、Faster RCNN環境配置及demo運行
雖然Faster RCNN中作者加入了很多新的東西,比如怎么選Anchar,怎么計算多任務的loss等等,所幸的是作者開源了代碼,讓我們很容易就能夠用他的算法實現我們自己的任務。在運行自己的任務之前,我們首先要做的就是確保我們已經配置好源代碼的運行環境。
本文是在Caffe已經配置好的條件下進行的 ,如果Caffe的需求庫,比如opencv等等還沒有弄好,建議先去把Caffe配置好。
(1)首先使用git把Faster RCNN的源碼下載到本地:
[html] view plaincopy
git?clone?--recursive?https://github.com/rbgirshick/py-faster-rcnn.git???? (2)安裝cpython和python-opencv
[html] view plaincopy
pip?install?cpython?? apt-get?install?python-opencv?? (3)下載Faster RCNN并安裝好cpython以后,進入py-faster-rcnn/lib中使用命令編譯一下
[html] view plaincopy
make??
(4)進入py-faster-rcnn/caffe-fast-rcnn,配置Makefile.config文件,目錄中有Makefile.config.example文件,可以按照這個文件進行修改,或者使用你配置caffe時的Makefile.config文件。我這里有一個配置文件,可以參考。我的配置文件用到了cuDNN,開始我使用cuDNN的時候總會在編譯時報錯,后來在在windows上配置py-faster-rcnn和調參經驗中找到了解決辦法,解決辦法如下:
把Caffe中的include/caffe/layers/cudnn_relu_layer.hpp,/include/caffe/layers/cudnn_sigmoid_layer.hpp,/include/caffe/layers/cudnn_tanh_layer.hpp和/include/caffe/util/cudnn.hpp替換py-faster-rcnn/caffe-fast-rcnn中對應的文件; 把Caffe中的src/caffe/layers/cudnn_relu_layer.cpp,src/caffe/layers/cudnn_relu_layer.cu,src/caffe/layers/cudnn_sigmoid_layer.cpp,src/caffe/layers/cudnn_sigmoid_layer.cu,src/caffe/layer/cudnn_tanh_layer.cpp,src/caffe/layers/cudnn_tanh_layer.cu替換掉py-faster-rcnn/caffe-fast-rcnn中對應的文件; 把py-faster-rcnn/caffe-fast-rcnn中src/caffe/layers/cudnn_conv_layer.cu文件中的cudnnConvolutionBackwardData_v3全部換為cudnnConvolutionBackwardData,把cudnnConvolutionBackwardFilter_v3全部換為cudnnConvolutionBackwardFilter。 如果自己的顯卡配置足夠的話,
強烈建議開啟cudnn,cudnn不僅可以起到加速的作用 ,而且我跑實驗的時候發現加入cudnn以后
可以明顯的降低顯存的使用 。如下圖所示:
開啟cudnn之前:
開啟cudnn之后:
可以看到顯存減少了將近2G,非常可觀,強烈建議開啟。
(5)配置好Makefile以后,在caffe-fast-rcnn中執行命令進行編譯
[html] view plaincopy
make?-j8?&&?make?pycaffe?? (6)這個時候,基本上faster RCNN也就配置好了,讓我們看一下是否真的能夠運行。首先我們下載幾個作者已經訓練好的caffemodel,如果現在直接運行demo.py的話會提示你沒有caffemodel文件,然后詢問是否運行過py-faster-rcnn/data/scripts/fetch_faster_rcnn_models.sh文件,我們可以找到該文件中的下載鏈接,用迅雷一會就可以下載好,如果直接運行該文件可能要不少時間。把下載好的文件中VGG16_faster_rcnn_final.caffemodel文件復制到py-faster-rcnn/data/faster_rcnn_models中
(7)運行py-faster-rcnn/experiments/tools/demo.py文件,不出意外的話,會出現如下錯誤:
修改py-faster-rcnn/lib/fast_rcnn/train.py文件,在文件中引入text_format
[python] view plaincopy
import ?google.protobuf.text_format?? 再次運行demo.py文件,這次應該可以運行成功!
二、Faster RCNN訓練自己的數據
既然已經把Faster RCNN配置好,下一步我們來訓練自己的數據吧。Faster RCNN論文中采用的訓練方法分為幾個階段,訓練起來比較麻煩,我們這里采用源碼中的end to end的訓練方式,更簡便一些。
2.1 建立數據集
為了讓我們的訓練更簡單些,我們不去改動源碼中讀寫數據的方式,而是把我們的數據集改成Pascal VOC的數據集格式,Pascal VOC數據集主要分為三個部分:Annotations,JPEGImages和ImageSets。其中JPEGImages中存放的是訓練和測試時需要的圖像;Annotations存放的是每個圖像中所有目標的bounding box信息,每個圖像對應一個xml文件;ImageSet文件中存放的Main目錄,而Main目錄中就是訓練和測試時需要的文件列表,主要分為train.txt, test.txt, trainval.txt, val.txt可以根據文件名就知道哪些是訓練數據列表,哪些是測試數據列表。
這里如果我們有這樣結構的數據集當然最好了,如果沒有這樣的數據集的話就需要自己建立。建立數據集的話可以參考:將數據集做成VOC2007格式用于Faster-RCNN訓練——小咸魚_的博客。
這里解說一下我的獲取方法,因為我的數據比較特殊,是由在線數據轉換過來的,所以我很容易的就獲得了各個目標的bounding box信息,所以沒有使用參考博客的方法。我最終獲得的bounding box信息文件如下所示:
[html] view plaincopy
Train_IMG\000001.jpg?97?6?174?202?305?? Train_IMG\000001.jpg?8?56?198?282?162?? ......省略若干行?? Train_IMG\000001.jpg?90?537?194?699?314?? 其中每個圖像對應一個bounding box文件,每個文件中的每一行表示一個目標的bounding box,每一行由6列數據組成,第1列數據為圖像的路徑,第2列為該目標的分類,第3列為bounding box的左上角的x(對應圖像的列,即mincol),第4列為bounding box的左上角的y(對應圖像的行,即minrow),第5列為bounding box的右下角的x(對應圖像的列,即maxcol),第6列為bounding box右下角的y(對應圖像的行,即maxrow)。
然后我們接下來要做的就是把這些bounding box文件轉換為VOC數據集格式的xml文件,我這里是從github上找到的一個Python開源代碼(但是作者的原址找不到了,所以這里沒能給出參考鏈接,如果有人有原址歡迎告知,我會把原址貼上)上進行的改動,源碼如下:
[python] view plaincopy
?? ?? __author__?=?"peic" ?? ?? import ?xml.dom??import ?xml.dom.minidom??import ?os???? from ?PIL?import ?Image???? '' ?? ?? ?? ?? _INDENT?=?'?' ?*?4 ?? _NEW_LINE?=?'\n' ?? _FOLDER_NODE?=?'VOC2007' ?? _ROOT_NODE?=?'annotation' ?? _DATABASE_NAME?=?'CROHME?offline?ME?Dataset' ?? _CLASS?=?'person' ?? _ANNOTATION?=?'PASCAL?VOC2007' ?? _AUTHOR?=?'hanchao' ?? ?? _SEGMENTED?=?'0' ?? _DIFFICULT?=?'0' ?? _TRUNCATED?=?'0' ?? _POSE?=?'Unspecified' ?? ?? _IMAGE_PATH?=?'Train_IMG' ?? ?? _ANNOTATION_SAVE_PATH?=?'Annotations' ?? ?? _IMAGE_CHANNEL?=?3 ?? ?? ?? ?? def ?createElementNode(doc,?tag,?attr):???????? ????element_node?=?doc.createElement(tag)?? ?? ?????? ????text_node?=?doc.createTextNode(attr)?? ?? ?????? ????element_node.appendChild(text_node)?? ?????? ????return ?element_node?? ?? ?? ?? def ?createChildNode(doc,?tag,?attr,?parent_node):???? ????child_node?=?createElementNode(doc,?tag,?attr)?? ????parent_node.appendChild(child_node)?? ?? ?? def ?createObjectNode(doc,?attrs):??????object_node?=?doc.createElement('object' )?? ????createChildNode(doc,?'name' ,?attrs['classification' ],?object_node)?? ????createChildNode(doc,?'pose' ,?_POSE,?object_node)?? ????createChildNode(doc,?'truncated' ,?_TRUNCATED,?object_node)?? ????createChildNode(doc,?'difficult' ,?_DIFFICULT,?object_node)?? ?? ????bndbox_node?=?doc.createElement('bndbox' )?? ????createChildNode(doc,?'xmin' ,?attrs['xmin' ],?bndbox_node)?? ????createChildNode(doc,?'ymin' ,?attrs['ymin' ],?bndbox_node)?? ????createChildNode(doc,?'xmax' ,?attrs['xmax' ],?bndbox_node)?? ????createChildNode(doc,?'ymax' ,?attrs['ymax' ],?bndbox_node)?? ????object_node.appendChild(bndbox_node)?? ?? ????return ?object_node?? ?? ?? def ?writeXMLFile(doc,?filename):??????tmpfile?=?open('tmp.xml' ,?'w' )?? ????doc.writexml(tmpfile,?addindent='?' *4 ,?newl='\n' ,?encoding='utf-8' )?? ????tmpfile.close()?? ?? ?? ?????? ????fin?=?open('tmp.xml' )?? ????fout?=?open(filename,?'w' )?? ????lines?=?fin.readlines()?? ?? ????for ?line?in ?lines[1 :]:?? ????????if ?line.split():?? ????????????fout.writelines(line)?? ?? ?????? ?????? ????fin.close()?? ????fout.close()?? ?? ?? def ?createXMLFile(attrs,?width,?height,?filename):???? ?????? ????my_dom?=?xml.dom.getDOMImplementation()?? ????doc?=?my_dom.createDocument(None ,?_ROOT_NODE,?None )?? ?? ?????? ????root_node?=?doc.documentElement?? ?? ?????? ????createChildNode(doc,?'folder' ,?_FOLDER_NODE,?root_node)?? ?????? ?????? ????createChildNode(doc,?'filename' ,?attrs['name' ],?root_node)?? ?? ?????? ????source_node?=?doc.createElement('source' )?? ?????? ????createChildNode(doc,?'database' ,?_DATABASE_NAME,?source_node)?? ????createChildNode(doc,?'annotation' ,?_ANNOTATION,?source_node)?? ????createChildNode(doc,?'image' ,?'flickr' ,?source_node)?? ????createChildNode(doc,?'flickrid' ,?'NULL' ,?source_node)?? ????root_node.appendChild(source_node)?? ?? ?????? ????owner_node?=?doc.createElement('owner' )?? ?????? ????createChildNode(doc,?'flickrid' ,?'NULL' ,?owner_node)?? ????createChildNode(doc,?'name' ,?_AUTHOR,?owner_node)?? ????root_node.appendChild(owner_node)?? ?? ?????? ????size_node?=?doc.createElement('size' )?? ????createChildNode(doc,?'width' ,?str(width),?size_node)?? ????createChildNode(doc,?'height' ,?str(height),?size_node)?? ????createChildNode(doc,?'depth' ,?str(_IMAGE_CHANNEL),?size_node)?? ????root_node.appendChild(size_node)?? ?? ?????? ????createChildNode(doc,?'segmented' ,?_SEGMENTED,?root_node)?? ?? ?????? ????object_node?=?createObjectNode(doc,?attrs)?? ????root_node.appendChild(object_node)?? ?? ?????? ????writeXMLFile(doc,?filename)?? ?? def ?generate_xml(txt_filename):???????? ????ouput_file?=?open(txt_filename)?? ????current_dirpath?=?os.path.dirname(os.path.abspath('__file__' ))?? ?? ????if ?not ?os.path.exists(_ANNOTATION_SAVE_PATH):?? ????????os.mkdir(_ANNOTATION_SAVE_PATH)?? ?? ????lines?=?ouput_file.readlines()?? ????for ?line?in ?lines:?? ????????s?=?line.rstrip()?? ????????array?=?s.split('?' )?? ?????????? ????????attrs?=?dict()?? ????????attrs['name' ]?=?array[0 ].split('\\' )[1 ]?? ????????attrs['classification' ]?=?array[1 ]?? ????????attrs['xmin' ]?=?array[2 ]?? ????????attrs['ymin' ]?=?array[3 ]?? ????????attrs['xmax' ]?=?array[4 ]?? ????????attrs['ymax' ]?=?array[5 ]?? ?? ?????????? ????????xml_file_name?=?os.path.join(_ANNOTATION_SAVE_PATH,?(attrs['name' ].split('.' ))[0 ]?+?'.xml' )?? ?????????? ?? ????????if ?os.path.exists(?xml_file_name):?? ?????????????? ????????????existed_doc?=?xml.dom.minidom.parse(xml_file_name)?? ????????????root_node?=?existed_doc.documentElement?? ?????????????? ?????????????? ????????????object_node?=?createObjectNode(existed_doc,?attrs)?? ????????????root_node.appendChild(object_node)?? ?? ?????????????? ????????????writeXMLFile(existed_doc,?xml_file_name)?? ?????????????? ????????else :?? ?????????????? ?????????????? ????????????img_name?=?attrs['name' ]?? ????????????img_path?=?os.path.join(current_dirpath,?_IMAGE_PATH,?img_name)?? ?????????????? ????????????img?=?Image.open(img_path)?? ????????????width,?height?=?img.size?? ????????????img.close()?? ?????????????? ?????????????? ????????????createXMLFile(attrs,?width,?height,?xml_file_name)?? ?????????????? 執行的Main程序如下:
[python] view plaincopy
?? '' ?? ? ? ?? import ?generate_xml??import ?os.path??import ?cv2???? if ?__name__?==?"__main__" :??????for ?dir,path,filenames?in ?os.walk('Train_BB' ):?? ????????for ?filename?in ?filenames:?? ????????????print ?dir??+?'/' ?+?filename?? ????????????generate_xml.generate_xml(dir??+?'/' ?+?filename)?? 最終生成的xml文件會保存在Annotations目錄中。
對于ImageSets/Main中的幾個文件就比較好建立了,只需要把訓練、驗證和測試集中圖像名稱寫入到對應的文件中即可。
接下來,可以自己在py-faster-rcnn/data中建立一個VOCdevkit2007目錄,在該目錄中建立VOC2007子目錄,然后把JPEGImages,Annotaions和ImageSets目錄復制到該目錄中,即準備好了自己的數據。
注意:
Faster RCNN對圖像和目標的大小以及長寬比是有一定要求的 ,具體的討論見用ImageNet的數據集(ILSVRC2014)訓練Faster R-CNN——Jiajun的博客
在這里我有深痛的教訓T_T,我的數據集中設置目標的長寬比在0.1~10之間,圖像的長寬比在0.3~7之間
2.2 訓練自己的數據
經過前面的鋪墊,我們終于可以訓練自己的數據了。
接下來我們需要下載幾個模型,看過論文的都知道,作者的訓練首先使用在ImageNet上訓練好的模型對網絡結構進行初始化,然后再訓練的網絡,我們現在下載的模型就是進行初始化的模型,下載地址:https://pan.baidu.com/s/1c2tfkRm?下載完成后,把文件加壓把模型放在py-faster-rcnn/data/imagenet_models中即可。
接下來,我們只需要更改幾個文件即可。(注意,我這里使用的是端到端的訓練方式,所以改動全部為end to end的,如果使用論文中的分階段訓練的方式,則需要改動alt_opt對應的文件)
(1)更改py-faster-rcnn/experiments/scripts/faster_rcnn_end2end.sh文件,該文件中的問題在于沒有設置各個文件的絕對路徑,所以運行起來可能有問題,把兩個time后的路徑設置加上py-faster-rcnn的絕對路徑即可,我更改后示例如下:
[python] view plaincopy
time?/home/hanchao/py-faster-rcnn/tools/train_net.py?--gpu?${GPU_ID}?\?? ??--solver?/home/hanchao/py-faster-rcnn/models/${PT_DIR}/${NET}/faster_rcnn_end2end/solver.prototxt?\?? ??--weights?/home/hanchao/py-faster-rcnn/data/imagenet_models/${NET}.v2.caffemodel?\?? ??--imdb?${TRAIN_IMDB}?\?? ??--iters?${ITERS}?\?? ??--cfg?/home/hanchao/py-faster-rcnn/experiments/cfgs/faster_rcnn_end2end.yml?\?? ??${EXTRA_ARGS}?? (2)更改py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_end2end目錄中的train,test和solver文件(我采用的是VGG16網絡,如果使用ZF網絡,去修改對應目錄下文件即可)。把train和test中所有的21和84改成
你的分類類型數+1 和
(你的分類類型數+1)×4 即可。在solver文件中加入絕對路徑;
(3)更改py-faster-rcnn/lib/datasets/pascal_voc.py文件,更改self._classes中標簽換成自己的標簽,即
[python] view plaincopy
self ._classes?=?('__background__' ,????????????????????'你的標簽1' ,?'你的標簽2' ,?'你的標簽3' )?? 注意這里的標簽不要有大寫符號。
(4)文章中還采用了horizontal flip以擴充數據, 我的數據是字符,因此不能擴充,所以我把py-faster-rcnn/lib/datasets/imdb.py中append_flipped_images(self)函數改為
[python] view plaincopy
????def ?append_flipped_images(self ):?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ????????self ._image_index?=?self ._image_index?? 如果你的數據比較多的話同樣也可以去掉這個函數,該函數也可能會引發一些問題,具體更改和解決方法參見Faster-RCNN+ZF用自己的數據集訓練模型(Python版本)——小咸魚_的博客
好啦,接下來應該就可以愉快的訓練自己的數據了,進入py-faster-rcnn/experiments/scripts目錄,使用命令
[python] view plaincopy
faster_rcnn_end2end.sh?0 ?VGG16?pascal_voc?? 其中0表示你訓練時使用的gpu標號,VGG16是模型類型,具體的內容可以去讀faster_rcnn_end2end.sh的源碼。
(注意:如果更改數據以后,再次訓練以前一定要把py-faster-rcnn/data中的cache目錄刪掉,否則訓練時用的還是以前的數據)
訓練結束后,把訓練好的模型放入到py-faster-rcnn/data/faster_rcnn_models中,然后調用把demo.py中的測試圖片換成自己的測試圖像,運行進行測試,如果有需要,也可以自己修改demo文件,程序還是很容易看懂的。
這就是訓練的全部過程了,因為配置起來也有一定的時間了,有些錯誤也記不太清了,我把我還記得的坑都寫在上面了,如果配置過程中有其他的一些錯誤,歡迎在評論里一起交流。
附錄
————————————————————————
1. 在測試的時候,可能會出現IndexError: too many indices for array,出現這個錯誤的原因是你的測試集中有的類別沒有出現,在這里只需要在py-faster-rcnn/lib/datasets/voc_eval.py中BB = BB[sorted_ind, :]前一句加上if len(BB) != 0:即可。
2. 在測試時還有可能出現KeyError: '某樣本名',這是因為你數據更改了,但是程序有cache文件,把py-faster-rcnn/data/VOCdevkit2007/annotations_cache目錄刪除即可。
總結
以上是生活随笔 為你收集整理的[深度学习] RCNNs系列(1) Ubuntu下Faster RCNN配置及训练和测试自己的数据方法 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。