[深度学习] RCNNs系列(1) Ubuntu下Faster RCNN配置及训练和测试自己的数据方法
最近用到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的源码下载到本地:
- gitclone--recursivehttps://github.com/rbgirshick/py-faster-rcnn.git
(2)安装cpython和python-opencv
- pipinstallcpython
- apt-getinstallpython-opencv
(3)下载Faster RCNN并安装好cpython以后,进入py-faster-rcnn/lib中使用命令编译一下
- 把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之后:
@H_
403_110@
可以看到
显存减少了将近2G,非常可观,强烈建议开启。
(5)配置好Makefile以后,在caffe-fast-rcnn中执行命令进行编译
(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
文件,不出意外的话,会出现如下
错误:
@H_
403_110@
修改py-faster-rcnn/lib/fast_rcnn/train.py
文件,在
文件中引入text_format
- importgoogle.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可以根据
文件名就知道哪些是训练数据列表,哪些是测试数据列表。
这里解说一下我的
获取方法,因为我的数据比较特殊,是由在线数据转换过来的,所以我很容易的就获得了各个目标的bounding
Box信息,所以没有使用参考
博客的
方法。我最终获得的bounding
Box信息
文件如下所示:
- Train_IMG\000001.jpg976174202305
- Train_IMG\000001.jpg856198282162
- ......省略若干行
- Train_IMG\000001.jpg90537194699314
其中每个图像对应一个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开源
代码(但是作者的原址找不到了,所以这里没能给出参考
链接,如果有人有原址欢迎告知,我会把原址贴上)上进行的改动,源码如下:
- __author__="peic"
- importxml.dom
- importxml.dom.minidom
- importos
- fromPILimportImage
- ''
- _INDENT=''*4
- _NEW_LINE='\n'
- _FOLDER_NODE='VOC2007'
- _ROOT_NODE='annotation'
- _DATABASE_NAME='CROHMEofflineMEDataset'
- _CLASS='person'
- _ANNOTATION='PASCALVOC2007'
- _AUTHOR='hanchao'
- _SEGMENTED='0'
- _DIFFICULT='0'
- _TRUNCATED='0'
- _POSE='Unspecified'
- _IMAGE_PATH='Train_IMG'
- _ANNOTATION_SAVE_PATH='Annotations'
- _IMAGE_CHANNEL=3
- defcreateElementNode(doc,tag,attr):
- element_node=doc.createElement(tag)
- text_node=doc.createTextNode(attr)
- element_node.appendChild(text_node)
- returnelement_node
- defcreateChildNode(doc,attr,parent_node):
- child_node=createElementNode(doc,attr)
- parent_node.appendChild(child_node)
- defcreateObjectNode(doc,attrs):
- object_node=doc.createElement('object')
- createChildNode(doc,'name',attrs['classification'],object_node)
- createChildNode(doc,'pose',_POSE,object_node)
- createChildNode(doc,'truncated',_TRUNCATED,'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'],'ymax',attrs['ymax'],bndBox_node)
- object_node.appendChild(bndBox_node)
- returnobject_node
- defwriteXMLFile(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()
- forlineinlines[1:]:
- ifline.split():
- fout.writelines(line)
- fin.close()
- fout.close()
- defcreateXMLFile(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','flickrid','NULL',source_node)
- root_node.appendChild(source_node)
- owner_node=doc.createElement('owner')
- createChildNode(doc,owner_node)
- createChildNode(doc,_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)
- defgenerate_xml(txt_filename):
- ouput_file=open(txt_filename)
- current_dirpath=os.path.dirname(os.path.abspath('__file__'))
- ifnotos.path.exists(_ANNOTATION_SAVE_PATH):
- os.mkdir(_ANNOTATION_SAVE_PATH)
- lines=ouput_file.readlines()
- forlineinlines:
- 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')
- ifos.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,xml_file_name)
执行的Main程序如下:
- ''
- importgenerate_xml
- importos.path
- importcv2
- if__name__=="__main__":
- fordir,path,filenamesinos.walk('Train_BB'):
- forfilenameinfilenames:
- printdir+'/'+filename
- generate_xml.generate_xml(dir+'/'+filename)
最终
生成的xml
文件会保存在Annotations目录中。
对于ImageSets/Main中的几个
文件就比较好建立了,只需要把训练、验证和测试集中图像
名称写入到对应的
文件中即可。
接下来,可以自己在py-faster-rcnn/data中建立一个VOCdevkit2007目录,在该目录中建立VOC2007子目录,然后把JPEGImages,Annotaions和ImageSets目录复制到该目录中,即准备好了自己的数据。
在这里我有深痛的教训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的
绝对路径即可,我更改后示例如下:
- 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中
标签换成自己的
标签,即
- self._classes=('__background__',
- '你的标签1','你的标签2','你的标签3')
注意这里的
标签不要有大写符号。
(4)
文章中还采用了horizontal flip以扩充数据, 我的数据是字符,因此不能扩充,所以我把py-faster-rcnn/lib/datasets/imdb.py中append_flipped_images(self)
函数改为
- defappend_flipped_images(self):
- self._image_index=self._image_index
如果你的数据比较多的话同样也可以去掉这个
函数,该
函数也可能会引发一些问题,具体更改和
解决方法参见
Faster-RCNN+ZF用自己的数据集训练模型(Python版本)——小咸鱼_的博客
好啦,接下来应该就可以愉快的训练自己的数据了,进入py-faster-rcnn/experiments/scripts目录,使用命令
- faster_rcnn_end2end.sh0VGG16pascal_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目录
删除即可。