武夷山币最近要开始预约了,公众号后台很多小伙伴也跟我咨询交流脚本。本着技术交流的思路,重新整理了之前的代码,发现识别验证码的成功率并没有想象的那么高。本着简化流程和“杀鸡用大锤”的精神,本文将使用YoloV3来解决这个问题。本期文章较长,比较熟悉的同学可以粗略看一下。之前的做法之前的做法是把每个字符切出来,然后用3层卷积加2层全连接卷积神经网络模型对字符进行分类。如果能切的准确,效果自然没得说,但是在实践中发现还是很难完美的切所有的验证码组合(其实主要是笨,没想到一个完美而稳健的切割方法)。现在的切割方式往往会把字符切割成下面这样:这显然是错误的,也会导致最终的验证码出错。虽然页面错误的验证码会继续识别刷新的新验证码,但考虑到这是一个类似抢购的使用环境,“一键式”的验证码识别方式还是很有必要的。为什么使用YoloV3最近Yolo系列的目标检测算法可谓是热闹非凡。YoloV3提出后,很长一段时间Yolo系列算法都没有太大进展,但最近YoloV4和YoloV5却如雨后春笋般冒出来。且不说官方还没有批准YoloV5以Yolo命名,但单从算法性能来看,在一定程度上,YoloV5现阶段已经算是速度与精度兼备的SoTA了。新的算法层出不穷,这对算法工程师来说真的很辛苦。不少同学苦笑:我学不会了!但如果你抱怨,你就不得不抱怨。新算法出来后,你还得自己去捣鼓。我从全球最大的男交友网站下载了YoloV5的代码,当然也不忘给个大Star。我平时用Keras比较多,但是这段代码是基于Pytorch写的。考虑到我对Keras和YoloV3比较熟悉,所以这次先用YoloV3,所以下载了Keras版本的YoloV3代码。需要下载地址的可以在公众号后台发送Yolo获取。恰好武夷山币即将开始预约,又是一个调试码通技术的好机会。下面来看看YoloV3是如何解决验证码识别问题的。数据集搭建准备数据集在上一篇文章中,我们获取了大量的验证码图片,没有的同学可以到以下地址获取。https://github.com/TitusWongCN/WeChatSubscriptionArticlesAutoTokenAppointment/ABC/CaptchaHandler/captchas(熟悉YoloV3数据集格式的同学可以直接跳到下一节)??打标软件YoloV3是一个目标检测模型,训练它需要超过只是图片本身还必须提供待检测目标的类别和位置,也就是常说的“标签”。标注数据集的工具有很多,比如LabelImg和Labelme等,这里使用第一个工具。LabelImg是一款专门为目标检测数据集生成标签的开源软件,用户可以轻松地对目标进行“标注”。下面提供了打包后的exe可执行文件。使用Windows10系统的同学可以直接从下面的链接下载使用(其他Windows系统没有测试过,可以自行下载测试):https://github.com/tzutalin/labelImg下载后/files/2638199/windows_v1.8.1.zip,先将data文件夹下的predefined_classes.txt文件内容改为实际验证码的所有类别。需要注意的是,每一行只能有一个类别名,类别名的顺序没有强制要求,比如:ABC...025...这样方便后面标注和准备训练。开始标记OpenlabelImg.exe,出现如下界面:点击左侧菜单栏OpenDir,在弹出的文件夹选择框中选择验证码图片所在的文件夹,软件会显示找到的第一张图片.然后在验证码图片文件夹同级目录下新建labels文件夹,然后点击左侧菜单栏ChangeSaveDir选择labels文件夹,这样软件会默认将生成的标签文件存放在labels文件夹中.现在你可以正式标记图片了。点击左侧菜单栏的Create\nRectBox,此时鼠标会变成一个十字,用鼠标在图片上的目标周围画一个最小的矩形。当您松开鼠标时,会弹出一个对话框,在此框中选择目标的类别。该类别之前已配置。在下面的框中选择相应的类别并确认。上面标注了一个字符,每张验证码图片有四个字符,所以每张验证码图片应该有四个框。查看标记文件每个文件被标记后,都会生成一个对应的.xml格式的标记文件。文件内容如下(部分内容省略):captchas1.jpgABC\CaptchaHandler\captchas\1.jpg未知0030生成的文件中包含标记文件的路径和目标在对应图片中的位置和类别等信息。这些信息将用于生成Yolo特定格式与其他目标检测模型相比,Yolo系列模型所需的训练数据格式是独一无二的。参考下载的YoloV3源码的描述文件,可以找到支持的数据集格式如下:Onerowforoneimage;Rowformat:image_file_pathbox1box2...boxN;Boxformat:x_min,y_min,x_max,y_max,class_id(无空格)。这个解释简单明了,就是说:一张图片占一行,每行的格式为:图片路径targetbox1targetbox2...targetboxNtargetbox的格式为:最小值X轴的最小值,Y轴的最小值Value,X轴最大值,Y轴最大值,类别ID最后举个例子:path/to/img1.jpg50,100,150,200,030,50,200,120,3path/to/img2.jpg120,300,250,600,2既然大家都说的这么清楚,那么我们要做的就是循葫芦画瓢了。代码如下:importosimportxml.etree.ElementTreeasET#用于读取XML文件的包importcv2#首先根据前面写的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'#os.listdir(dirpath)中fp的xml文件存放目录:root=ET.parse(os.path.join(dirpath,fp)).getroot()path=root.find('path').textimg=cv2.imread(path,cv2.IMREAD_GRAYSCALE)高度,宽度=img.shape框=[path,]forchildinroot.findall('object'):#查找图片中的所有框label=child.find('name')label_index=labels.index(label.text)#获取idcategorynamesub=child.find('bndbox')#找到box的标签值并读取xmin=sub[0].textymin=sub[1].textxmax=sub[2].textymax=sub[3].textboxes.append(','.join([xmin,ymin,xmax,ymax,str(label_index)]))#将数据写入data.txt文件withopen('./capchars/data.txt','a+')asf:f.write(''.join(boxes)+'\n')写入的数据如下所示:F:\...\capchars\images\1.png1,9,9,24,2419,14,29,28,2938,11,50,26,2658,7,73,22,17F:\...\images\10.png1,8,15,23,718,11,29,26,2639,15,49,29,3058,14,73,27,17F:\...\images\100.png1,6,10,19,2618,11,29,26,2538,10,59,25,2060,9,73,24,6F:\...\images\101.png1,8,14,24,2118,8,33,23,138,13,55,28,957,12,69,26,8F:\...\images\102.png1,12,18,28,1118,9,34,24,1938,5,49,20,2959,10,74,29,14F:\...\images\103.png1,10,18,25,2018,5,29,19,3038,7,54,22,1259,7,74,25,14F:\...\images\104.png1,10,12,24,1817,8,32,22,538,10,54,25,1858,14,74,28,9F:\...\images\105.png1,13,9,27,818,9,34,24,2237,12,50,27,858,11,74,29,14F:\...\图片\106.png1,14,13,29,1218,8,34,22,1839,6,55,24,1459,13,74,27,6F:\...\images\107.png1,8,13,22,217,9,32,24,438,8,53,23,458,11,73,26,11到这里,即使YoloV3训练使用的数据集是为模型训练搭建的,修改模型参数配置,打开train.pyi在源码根目录下,需要把main函数_main的前几行改成我们对应的文件。这里需要注意的是,由于要解决验证码检测的问题,并不是很复杂,所以我选择使用tiny版本的Yolo,模型的输入大小为(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的create_model方法和create_tiny_model方法中的第二行代码。pyto:image_input=Input(shape=(None,None,1))修改训练数据读取方式查看源码中读取数据的部分,发现yolo3/utils.py中的get_random_data方法做了一些数据同时读取数据增强操作。但是这道题不需要这些操作,所以我选择删除这些代码。【本文来自TitusCosmos微信公众号Titus,ID为TitusCosmos,转载请注明!】【为了防止各种爬虫在网上四处乱爬,特意删除了原作者信息,所以文章中间加上了作者信息,希望读者谅解】另外,输入的大小前面提到的图片应该是(416,416),和验证码图片的大小明显不一样,所以需要做一些操作来改变大小。改后的get_random_data方法是这样的:fromPILimportImageimportnumpyasnpdefget_random_data(annotation_line,input_shape,max_boxes=4):line=annotation_line.split()image=Image.open(line[0])iw,ih=image.sizeh,w=input_shape#(416,416)boxes=np.array([np.array(list(map(int,box.split(','))))forboxinline[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),浮动(h/ih)#计算验证码图片在水平和垂直方向的缩放比例forindex,boxinenumerate(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,:]=boximage_data=np.expand_dims(image_resize,轴=-1)image_data=np.array(image_data)/255.返回image_data、box_data模型训练一切就绪,可以运行train.py开始训练,训练开始后,控制台会输出如下信息:...Epoch2/1001/16[>................................]-ETA:6s-损失:511.16132/16[==>..........................]-ETA:6s-损失:489.36323/16[====>............................]-ETA:5s-损失:474.89864/16[======>......................]-预计到达时间:5秒-损失:458.02435/16[========>......................]-预计到达时间:4秒-损失:443.57926/16[==========>......................]-ETA:4s-损失:430.45117/16[============>.................]-ETA:4s-损失:416.01588/16[==============>......................]-预计到达时间:3秒-损失:402.71119/16[==============>。............]-预计到达时间:3s-损失:390.400110/16[===============>..........]-预计到达时间:2秒-损失:378.450211/16[===================>......]-预计到达时间:2秒-损失:368.690712/16[=====================>......]-ETA:1s-损失:359.188613/16[======================>......]-ETA:1s-损失:350.005514/16[=========================>....]-ETA:0s-损失:342.047515/16[==========================>..]-ETA:0s-损失:333.668716/16[================================]-8s482ms/step-loss:325.5808-val_loss:211.4188...源码中默认先用预训练模型训练50轮,此时只解冻最后两层;然后解冻所有层并训练50轮。我们观察损失和val_loss不再减少时,模型几乎训练完毕。当然,一般只等程序结束也是可以的。程序运行后会在logs文件夹下自动生成一个trained_weights_final.h5文件,这就是我们需要的训练好的模型文件。模型测试模型训练完成后,接下来当然就是激动人心的测试环节了。但是,我们不能着急吃热豆腐。在我们开始测试我们的模型之前,我们必须修改源代码中的一些测试代码。打开根目录下的yolo.py。模型测试时,会调用这个脚本生成一个Yolo类对象,然后用这个对象做预测,所以运行脚本前需要配置一下。其实和之前的配置训练脚本差不多。修改后的代码如下:txt',...}这时候就可以开始运行测试模型的脚本yolo_video.py开始预测了。这时程序需要输入要预测图片的路径。我们输入一张未经训练的验证码图片:Inputimagefilename:data/capchars/images/416.png很快,结果就出来了:很明显,结果是LFG8,和实际情况是一致的。多测试几个:综上所述,该方法不需要对验证码图片进行不稳定的切割,避免了切割过程中出现的错误。因此,这种验证码的识别方法比以前的方法成功率更高。当然,这个方法还有优化的空间。比如在训练前,按照系列前面几篇文章的内容,去掉验证码图片中的干扰线,这样准确率肯定会更高。至此,YoloV3身份验证码制作完成。本系列的所有源码都会放在下面的github仓库中,有需要的可以参考,有问题欢迎指正,欢迎交流,谢谢!https://github.com/TitusWongCN/WeChatSubscriptionArticles【蟒盘纪念币系列】往期推荐:第一期:蟒盘纪念币系列一:简介第二期:蟒盘纪念币系列二:身份验证码01第三期:蟒盘纪念币系列二:身份验证码02期第四期:蟒盘纪念币系列二:身份验证码03期五:蟒盘纪念币系列二:身份验证码04期第六期:蟒盘纪念币系列三:自动预约脚本01期7:蟒盘纪念币系列三:自动预约脚本02第八期:蟒盘纪念币系列三:自动预约脚本03&系列总结以下是我的公众号,有兴趣的可以扫一扫: