最近武夷山币要开始准备预定了,公众号后台也有不少朋友向我咨询和交流脚本。本着技术交流的想法,把以前的代码从新梳理了一遍,发现识别验证码的成功率并无想象中那么高。本着简化流程和“杀鸡就要用牛刀”的精神,这篇文章会经过YoloV3
来解决这个问题。本期文章较长,比较熟悉的同窗能够粗略看看。html
以前的作法是把每一个字符切割出来,而后用一个3
层卷积加2
层全链接的卷积神经网络模型对字符分类。若是能精确切割,那效果天然没的说,但实际操做下来发现要想对全部组合的验证码做完美的切割仍是有困难的(其实主要仍是笨,没想到完美健壮的切割方法)。python
目前的切割方法常常会把字符切割成下面这种样子:git
这明显是不对的,也致使最终验证码的错误。虽然页面上验证码填写错误会继续识别刷新出的新验证码,但考虑到这是一个相似于抢购的使用环境,“一击即中”的验证码识别方法是颇有必要的。github
YoloV3
最近Yolo
系列的目标检测算法可谓是至关热闹。YoloV3
提出后,Yolo
系列算法好久都没有比较大的进展,可近段时间YoloV4
和YoloV5
却如雨后春笋般冒了出来。先不说官方暂时还没承认YoloV5
以Yolo
命名,但单从算法性能来看,在必定程度上YoloV5
已经算是现阶段速度与精度并存的SoTA
了。算法
新算法层出不穷,真是苦了算法工程师们了,很多同窗苦笑:学不动了!windows
但发牢骚归发牢骚,新算法出来那是必须得好好摆弄一番的了。从全球最大的男性交友网站上下载了YoloV5
的代码,固然同时也不忘给了一个大大的Star
。我平时用Keras
比较多,但这套代码是基于Pytorch
编写的。考虑到对Keras
和YoloV3
更加熟悉,此次暂时仍是先用YoloV3
来作,因而又下载了Keras
版本的YoloV3
代码。须要下载地址的能够在公众号后台发送Yolo
获取。微信
正好武夷山币即将开始预定,又是一次调试代码交流技术的好机会,下面就来看看YoloV3
是怎么解决验证码的识别问题的吧。网络
在以前的文章中,咱们已经拿到了大量的验证码图片,没有的同窗能够到下面这个地址获取。app
https://github.com/TitusWongCN/WeChatSubscriptionArticlesAutoTokenAppointment/ABC/CaptchaHandler/captchas
(熟悉YoloV3
数据集格式的同窗能够直接跳到下一节)dom
YoloV3
是一个目标检测模型,训练它须要的不仅是图片自己,还要一块儿提供要检测目标的类别和位置,也就是常说的“标签”。给数据集打标的工具备不少,好比LabelImg
和Labelme
等,这里使用第一种工具。LabelImg
是一款专门用于为目标检测数据集生成标签的开源软件,用户能够很方便的为目标“画框贴标签”。下面提供已经打包好的exe
可执行文件,使用Windows 10
系统的同窗能够直接到下面的连接下载使用(其余Windows
系统没有测试,能够下载来本身测试):
https://github.com/tzutalin/labelImg/files/2638199/windows_v1.8.1.zip
下载好以后,先把data
文件夹下的predefined_classes.txt
文件内容改成实际的验证码的全部类别。要注意,每一行只能有一个类别名,类别名的顺序没有强制要求,好比:
A B C ... 0 2 5 ...
这样作会为打标签和后面准备开始训练提供方便。
打开labelImg.exe
出现下面的界面:
点击左侧菜单栏的Open Dir
,在弹出的文件夹选择框中选择验证码图片所在的文件夹,这时候软件就会显示找到的第一张图片了。而后在验证码图片文件夹同级目录新建一个labels
文件夹,接着点击左侧菜单栏的Change Save Dir
选择labels
文件夹,这样软件会默认把生成的标记文件存储在labels
文件夹。
下面能够正式为图片打标了。点击左侧菜单栏的Create\nRectBox
,这时鼠标会变成一个十字叉,用鼠标在图片上画出一个包裹目标的最小的矩形框。松开鼠标的时刻会出来一个对话框要选择这个框内目标的类别,前面已经配置过类别,在下面的框中选择对应的类别而后确认便可。
上面就标记好了一个字符,每张验证码图片都有四个字符,全部每张验证码图片应该都有四个框。
每一个文件打标后会生成对应的一个.xml
格式的标记文件,文件内容以下(省略了部份内容):
<annotation> <folder>captchas</folder> <filename>1.jpg</filename> <path>ABC\CaptchaHandler\captchas\1.jpg</path> <source> <database>Unknown</database> </source> <size> <width>0</width> <height>0</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>4</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>38</xmin> <ymin>10</ymin> <xmax>50</xmax> <ymax>26</ymax> </bndbox> </object> <object> ... </object> <object> ... </object> <object> ... </object> </annotation>
生成的文件中有被标记文件的路径和对应图片中目标的位置以及类别等相关信息,这些信息下面都会用到。
Yolo
特定格式相对于其余的目标检测模型来讲,Yolo
系列模型须要的训练数据的格式比较独特。参考下载的YoloV3
源码的说明文件,能够找到支持的数据集格式以下:
One row for one image;Row format: image_file_path box1 box2 ... boxN;
Box format: x_min,y_min,x_max,y_max,class_id (no space).
这个解释简单明了,也就是说:
最后还给出了示例:
path/to/img1.jpg 50,100,150,200,0 30,50,200,120,3path/to/img2.jpg 120,300,250,600,2
既然人家都说的这么清楚了,那咱们须要作的就是依葫芦画瓢了。代码以下:
import os import xml.etree.ElementTree as ET # 用来读取XML文件的包 import cv2 # 首先根据前面写的predefined_classes.txt文件内容定义好类别的顺序 labels = ['A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q', 'R','S','T','U','V','W','X','Y','Z','2','3','4','5','6','7','8'] dirpath = r'./capchars/labels' # 存放xml文件的目录 for fp in os.listdir(dirpath): root = ET.parse(os.path.join(dirpath, fp)).getroot() path = root.find('path').text img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) height, width = img.shape boxes = [path, ] for child in root.findall('object'): # 找到图片中的全部框 label = child.find('name') label_index = labels.index(label.text) # 获取类别名称的ID sub = child.find('bndbox') # 找到框的标注值并进行读取 xmin = sub[0].text ymin = sub[1].text xmax = sub[2].text ymax = sub[3].text boxes.append(','.join([xmin, ymin, xmax, ymax, str(label_index)])) # 将数据写入data.txt文件 with open('./capchars/data.txt', 'a+') as f: f.write(' '.join(boxes) + '\n')
写好的数据是这样的:
F:\...\capchars\images\1.png 1,9,9,24,24 19,14,29,28,29 38,11,50,26,26 58,7,73,22,17 F:\...\images\10.png 1,8,15,23,7 18,11,29,26,26 39,15,49,29,30 58,14,73,27,17 F:\...\images\100.png 1,6,10,19,26 18,11,29,26,25 38,10,59,25,20 60,9,73,24,6 F:\...\images\101.png 1,8,14,24,21 18,8,33,23,1 38,13,55,28,9 57,12,69,26,8 F:\...\images\102.png 1,12,18,28,11 18,9,34,24,19 38,5,49,20,29 59,10,74,29,14 F:\...\images\103.png 1,10,18,25,20 18,5,29,19,30 38,7,54,22,12 59,7,74,25,14 F:\...\images\104.png 1,10,12,24,18 17,8,32,22,5 38,10,54,25,18 58,14,74,28,9 F:\...\images\105.png 1,13,9,27,8 18,9,34,24,22 37,12,50,27,8 58,11,74,29,14 F:\...\images\106.png 1,14,13,29,12 18,8,34,22,18 39,6,55,24,14 59,13,74,27,6 F:\...\images\107.png 1,8,13,22,2 17,9,32,24,4 38,8,53,23,4 58,11,73,26,11
到这里,用于YoloV3
训练用的数据集就算是建好了。
打开源代码根目录的train.py
,须要把主函数_main
的前几行更改为咱们对应的文件。这里要注意,因为要解决的是验证码检测的问题,并非十分复杂,因此选择使用Yolo
的tiny
版本,同时模型的输入尺寸为(416,416)
。下面是更改后的代码:
annotation_path = 'data/capchars/data.txt' log_dir = 'logs/' classes_path = 'model_data/cap_classes.txt' # 与前面的predefined_classes.txt文件内容同样 anchors_path = 'model_data/tiny_yolo_anchors.txt'
YoloV3
默认的输入图像通道数为3
,考虑到要解决的问题的难度,但通道足矣,为了简化模型,把train.py
的create_model
方法和create_tiny_model
方法中的第二行代码都修改成:
image_input = Input(shape=(None, None, 1))
查看源代码中读取数据的部分,发现yolo3/utils.py
中的get_random_data
方法在读取数据的同时作了一些数据加强的操做。但在这个问题中是不太须要这些操做的,因此选择删除这些代码。
【本文来自微信公众号Titus的小宇宙,ID为TitusCosmos,转载请注明!】
【为了防止网上各类爬虫一通乱爬还故意删除原做者信息,故在文章中间加入做者信息,还望各位读者理解】
另外,前面说到输入图像的尺寸应该是(416,416)
,而验证码图片的尺寸明显不同,所以就要作一些更改尺寸的操做。更改以后的get_random_data
方法是这样的:
from PIL import Image import numpy as np def get_random_data(annotation_line, input_shape, max_boxes=4): line = annotation_line.split() image = Image.open(line[0]) iw, ih = image.size h, w = input_shape # (416,416) boxes = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]]) image_resize = image.resize((w, h), Image.BICUBIC) box_data = np.zeros((max_boxes,5)) np.random.shuffle(boxes) x_scale, y_scale = float(w / iw), float(h / ih) # 计算验证码图片在横纵方向上缩放的倍数 for index, box in enumerate(boxes): box[0] = int(box[0] * x_scale) box[1] = int(box[1] * y_scale) box[2] = int(box[2] * x_scale) box[3] = int(box[3] * y_scale) box_data[index, :] = box image_data = np.expand_dims(image_resize, axis=-1) image_data = np.array(image_data)/255. return image_data, box_data
一切准备工做就绪,就能够运行train.py
开始训练了,训练开始后控制台会输出以下信息:
... Epoch 2/100 1/16 [>.............................] - ETA: 6s - loss: 511.1613 2/16 [==>...........................] - ETA: 6s - loss: 489.3632 3/16 [====>.........................] - ETA: 5s - loss: 474.8986 4/16 [======>.......................] - ETA: 5s - loss: 458.0243 5/16 [========>.....................] - ETA: 4s - loss: 443.5792 6/16 [==========>...................] - ETA: 4s - loss: 430.4511 7/16 [============>.................] - ETA: 4s - loss: 416.0158 8/16 [==============>...............] - ETA: 3s - loss: 402.7111 9/16 [===============>..............] - ETA: 3s - loss: 390.4001 10/16 [=================>............] - ETA: 2s - loss: 378.4502 11/16 [===================>..........] - ETA: 2s - loss: 368.6907 12/16 [=====================>........] - ETA: 1s - loss: 359.1886 13/16 [=======================>......] - ETA: 1s - loss: 350.0055 14/16 [=========================>....] - ETA: 0s - loss: 342.0475 15/16 [===========================>..] - ETA: 0s - loss: 333.6687 16/16 [==============================] - 8s 482ms/step - loss: 325.5808 - val_loss: 211.4188 ...
源代码中默认是先用预训练模型训练50
轮,此时只解冻最后两层;而后解冻全部层再训练50轮。咱们观察loss
和val_loss
再也不降低时,模型就训练的差很少了,固然就直接等待程序运行结束通常也是能够的。
程序运行结束会在logs
文件夹下自动生成一个trained_weights_final.h5
文件,这就是咱们须要的训练好的模型文件。
模型训练完成,接下来固然就是激动人心的测试环节啦。
不过,心急吃不了热豆腐,咱们还得先修改源代码中的部分测试代码才能开始对咱们的模型进行测试。
打开根目录下的yolo.py
,模型测试的时候会调用这个脚本生成一个Yolo
类的对象,而后用这个对象来预测,因此须要在运行脚本以前先配置好。其实跟前面配置训练脚本差很少,修改后的代码以下:
_defaults = { "model_path": 'logs/trained_weights_final.h5', "anchors_path": 'model_data/tiny_yolo_anchors.txt', "classes_path": 'model_data/cap_classes.txt', ... }
这时就能够开始运行测试模型的脚本yolo_video.py
来开始预测了,这时程序须要输入要预测的图片路径。咱们输入一个没有训练过的验证码图片:
Input image filename:data/capchars/images/416.png
很快,结果就出来了:
很明显,结果是LFG8
,与实际状况一致。
再测试几个:
这个方法不须要对验证码图片做不稳定的切割,避免了在切割过程当中致使的错误。所以,这个验证码的识别方法在成功率上是要高于以前的方法的。固然,这个方法还有能够优化的地方。好比训练前先根据系列前几篇文章的内容把验证码图片中的干扰线去掉,这样准确率确定会更高。
至此,YoloV3
识别验证码就讲完了。
本系列的全部源代码都会放在下面的github仓库里面,有须要能够参考,有问题欢迎指正,欢迎交流,谢谢!
https://github.com/TitusWongCN/WeChatSubscriptionArticles
【Python盘记念币系列】往期推荐:
第六期:Python盘记念币系列之三:自动预定脚本编写 01
第七期:Python盘记念币系列之三:自动预定脚本编写 02
第八期:Python盘记念币系列之三:自动预定脚本编写 03 & 系列总结
下面是个人公众号,有兴趣能够扫一下: