让YoloV2帮你识别马克杯

人工智障的成材之路

Posted by Kriz on 2017-06-02

应邀写了个教程造福水深火热的树莓

准备工作

写在前面的话

就目前而言,做图像识别最好用的三种算法基本已经确定为FCNNSSDYolo了。FCNN是开山鼻祖,虽然速度稍慢但是准确性还是很高的。Yolo年轻气盛所以很快,不过经常会出现识别不准的问题。最后出现的SSD是一个华人的算法(虽然被吐槽代码乱然而效果还是很厉害的),一度很火,于是不甘成为老二的暴脾气Yolo摇身一变进化成YoloV2/Yolo9000更快了总之各种意义上都变得碾压SSD了。

虽然仍然偶尔会有奇怪的识别结果↓

doge

毕竟要紧跟潮流,所以这次打算首先用YoloV2来试着做。

打标签

首先找训练集。chrome的插件Fatkun可以抓取网页上所有图片,谷歌搜搜图抓下来就好了。而且这样获得的图片集刚好尺寸统一又美好,平均225*225充当训练集还是非常合适的。

因为VOC和ImageNet都没有能直接搬来用的轮子,所以接下来就要面临非常烦人的标定环节了。使用labelImg处理这个过程。因为我在找轮子的时候mac不支持PyQt4的问题无解,因此转移阵地到win。现在好像作者升级到PyQt5了,因此mac编译的具体步骤请参考官方文档。

win/linux是不需要编译过程的,直接点这里下载GUI工具就好(需要科学上网)。

labelImg的使用方法请参阅官网。其实不参阅也没关系整个软件用起来都非常无脑速成零基础。请注意,如果标定和训练是同个平台的话,在打标签之前最好能先完成文件夹的层次处理(在下一小节提到),免去修改路径的麻烦。大概的画风是这样的:

li

学习ImageNet的优良传统,我手撸了一千张,总共大概要用掉3-4小时,充足的曲库很重要【

文件层次处理

回到mac这边,按照VOC的格式进行层次处理:

1
2
3
4
5
6
7
8
9
--VOC
--Annotations
--ImageSets
--Main
--Layout
--Segmentation
--JPEGImages
--SegmentationClass
--SegmentationObject

把所有图像文件丢进JPEGImages里,所有xml文件丢进Annotations里,在Main中新建一个文本文档train.txt,把所有图像的文件名填进去(无后缀无路径)。最外层目录的名字建议按照字母+年号的规范命名,方便一会儿改配置文件。

打开一个xml看看样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<annotation verified="no">
<folder>cup _ Google &#25628;&#32034;</folder>
<filename>00001.jpg</filename>
<path>C:\Users\Kevinz\Documents\cv3\cup _ Google &#25628;&#32034;\00001.jpg</path>
<source>
<database>Unknown</database>
</source>
<size>
<width>225</width>
<height>225</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>cup</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>14</xmin>
<ymin>12</ymin>
<xmax>216</xmax>
<ymax>212</ymax>
</bndbox>
</object>
</annotation>

发现路径沿用了win……赶快改过来(后记:好像path并不影响最后的训练过程,不改也无所谓。但folder路径请务必改成JPEGImages

默认的macOS是自带perl编译环境的,所以这里可以简单地使用语法

1
find . -name 'name' |xargs perl -pi -e 's|a|b|g'

将当前目录下name文件中的a内容替换成b内容。以下是我的实例:

1
2
find . -name '*.xml' |xargs perl -pi -e 's|cup _ Google &#25628;&#32034;</folder>|JPEGImages</folder>|g'
find . -name '*.xml' |xargs perl -pi -e 's|C:\\Users\\Joshua Kevinz\\Documents\\cv3\\cup _ Google &#25628;&#32034;\\|/Users/kevinz/Documents/CV/cv3/KRIZ2017/JPEGImages/|g'

然后再把文件名加上后缀:

1
find . -name '*.xml' |xargs perl -pi -e 's|</filename>|.jpg</filename>|g'

应用网络

接下来就是yolo的时间了。

准备网络框架

首先把寄主(?)darknet从git上拖下来。根据自己的情况可以打开Makefile中的GPU、CUDNN和OPENCV(置1即可)。

1
2
3
git clone https://github.com/pjreddie/darknet
cd darknet
make

darknet官网是邪教现场(手动再见)非常不建议前去精神污染

如果make报找不到pthread_t的话,打开include文件夹下的darknet.h,添加#include <pthread.h>即可。(现在好像修复了)

进入scripts文件夹,新建文件夹VOCdevkit,把刚才的整个大文件夹全部搬过来。

配置网络

打开voc_label.py文件,根据自己的情况修改sets,classes和路径。修改结束后,运行

1
python voc_label.py

如果是用win做标定的话,跑的过程中可能会发现有几个xml里的width和height谜之变0,根据图片尺寸人肉补上就好了。运行完成后可以发现根目录下生成了labels文件夹和一个记录全部文件绝对路径的2017_train.txt

接下来要根据cfg文件训练网络了,这里选用正统的yolo-voc.2.0.cfg。打开cfg文件,把region中的classes改成类个数,这里是1。改其他参数的过程就因人而异了,两个建议:一个是把max_batches改到3W以下(只是因为太多的话ddl之前肯定跑不完:)如果有时间有精力的话能跑完4W5当然是坠吼的),另一个是改一下最后一层conv中的filter,参考公式filter = num * (classes + coords + 1)。如果是撸网大神的话学习率什么的也是在这里改。

默认激活函数疑似Leaky ReLU!!终于翻身了你这家伙!!【泣

接下来修改data/voc.names中的分类,我们这里只有cup一种,因此删除所有内容并添加cup就好。最后修改cfg/voc.data,按自己的需要来改,贴出我的作为参考:

1
2
3
4
classes = 1
train = /Users/kevinz/darknet/scripts/2017_train.txt
names = data/voc.names
backup = /Users/kevinz/darknet/results/

训练

准备就绪,可以训练了。

1
./darknet detector train ./cfg/voc.data cfg/yolo-voc.2.0.cfg

YoloV2此处的网络结构是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
layer filters size input output
0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32
1 max 2 x 2 / 2 416 x 416 x 32 -> 208 x 208 x 32
2 conv 64 3 x 3 / 1 208 x 208 x 32 -> 208 x 208 x 64
3 max 2 x 2 / 2 208 x 208 x 64 -> 104 x 104 x 64
4 conv 128 3 x 3 / 1 104 x 104 x 64 -> 104 x 104 x 128
5 conv 64 1 x 1 / 1 104 x 104 x 128 -> 104 x 104 x 64
6 conv 128 3 x 3 / 1 104 x 104 x 64 -> 104 x 104 x 128
7 max 2 x 2 / 2 104 x 104 x 128 -> 52 x 52 x 128
8 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256
9 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128
10 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256
11 max 2 x 2 / 2 52 x 52 x 256 -> 26 x 26 x 256
12 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512
13 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256
14 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512
15 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256
16 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512
17 max 2 x 2 / 2 26 x 26 x 512 -> 13 x 13 x 512
18 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024
19 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512
20 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024
21 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512
22 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024
23 conv 1024 3 x 3 / 1 13 x 13 x1024 -> 13 x 13 x1024
24 conv 1024 3 x 3 / 1 13 x 13 x1024 -> 13 x 13 x1024
25 route 16
26 reorg / 2 26 x 26 x 512 -> 13 x 13 x2048
27 route 26 24
28 conv 1024 3 x 3 / 1 13 x 13 x3072 -> 13 x 13 x1024
29 conv 30 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 30
30 detection

其实这里原本也可以导入预训练好的权值的,但网上关于这个的吐槽络绎不绝所以我就没敢试【

这段时间跑网络的时候都没有出现过内存塞满然后疯狂吃硬盘的情况了?!我怀疑torch有毒
可是mac简直慢得原地飞天经过了一晚上的心理挣扎终于决定千里迢迢前往世界边缘的济事楼用ubuntu训练。搞一个网居然用了三种系统想来也是无敌了
啊♂GTX980真的爽上天

在发功现场蹲了三个小时,发现训练前期收敛还算快,选框的重合程度(Region AVG IOU)从0.2左右很快就升到了0.7左右,单个epoch中检测率达到1(Avg Recall)的百分比也稳步上升,可见网络是有效的。不过后来仿佛就不怎么动了……请你务必要接着学啊机器生活不止眼前的局部最优还有单ep全1的检测率和0.9+的选框契合度呢

这两个数据是很重要的,如果训练过程中发现异常,请修改网络参数。

如果中途训练断掉了,可以使用备份文件恢复之前的工作。只需要在训练命令后加入备份权值参数:

1
./darknet detector train ./cfg/voc.data cfg/yolo-voc.2.0.cfg results/yolo-voc.backup

具体文件名请根据实际情况确定,设定的特定周期结束后生成的*.weight文件也可以作为有效的恢复点。

两天后再去看发现我的网络不知道被谁control+C了:)互相伤害不要明着伤害好吗:)

不过届时已经跑了3W多个迭代,感觉也差不太多了。

测试

将测试图放在data文件夹下,导入最后一次保存的权重进行测试。只需要添加测试图路径参数。

1
./darknet detector test ./cfg/voc.data cfg/yolo-voc.2.0.cfg results/yolo-voc_30000.weights data/test.jpg

测试结果还算是可以的。

1

2

3