python生成文字点选验证码→训练yolo目标检测模型→识别文字点选验证码

python生成文字点选验证码→训练yolo目标检测模型→识别文字点选验证码

前言

一、生成文字点选验证码

作为十几年杰迷的我,那必须得弄点范特西的数据集,于是乎,我想到了,用周杰伦的歌曲名称+专辑封面来生成我的文字点选验证码图片,效果如下:

原图添加随机歌名之后
在这里插入图片描述

具体实现逻辑如下:

  1. 从专辑图片中随机选取一张作为背景,并将其大小改为520x520
  2. 从歌曲中随机选取1~5首歌曲的歌名
  3. 遍历选取到的几首歌曲的歌名
  4. 从40~50的字号中随机选取一个字号
  5. 从-90°~90°中随机选取文字旋转的角度
  6. 从所有颜色中随机选取一种字体颜色
  7. 以及在背景图片中随机选取一个坐标点xmin,ymin
  8. 最后将处理后的文字图片放到背景图片中,以及生成每首歌的归一化后label坐标x,y,w,h

代码如下:

from tqdm import tqdm
from PIL import Image, ImageDraw, ImageFont, ImageOps
import shutil,os
import numpy as np

class CreateData:
    def __init__(self,create_num):
        self.jay_img_paths=['JAY/' + i for i in os.listdir('JAY/')] # 背景图片路径
        self.font_path='../simhei.ttf' # 字体路径
        self.img_save_path='images/' # 生成训练集图片的路径
        self.label_save_path='labels/' # 生成图片对应label的路径
        self.test_path='test/' # 生成测试集图片的路径
        # 100首周杰伦歌曲名称
        self.songs=['可爱女人', '星晴', '黑色幽默', '龙卷风', '屋顶', '爱在西元前', 
                    '简单爱', '开不了口', '上海一九四三', '双截棍', '安静', '蜗牛', 
                    '你比从前快乐', '世界末日', '半岛铁盒', '暗号', '分裂', '爷爷泡的茶',
                    '回到过去', '最后的战役', '晴天', '三年二班', '东风破', '你听得到', 
                    '她的睫毛', '轨迹', '断了的弦', '七里香', '借口', '搁浅', '园游会', 
                    '夜曲', '发如雪', '黑色毛衣', '枫', '浪漫手机', '麦芽糖', '珊瑚海', 
                    '一路向北', '听妈妈的话', '千里之外', '退后', '心雨', '白色风车', 
                    '千山万水', '不能说的秘密', '牛仔很忙', '彩虹', '青花瓷', '阳光宅男',
                    '蒲公英的约定', '我不配', '甜甜的', '最长的电影', '周大侠', 
                    '给我一首歌的时间', '花海', '魔术先生', '说好的幸福呢', '時光機',
                    '乔克叔叔', '稻香', '说了再见', '好久不见', '愛的飛行日記', 
                    '超人不会飞', 'Mine Mine', '公主病', '你好吗', '疗伤烧肉粽', 
                    '水手怕水', '世界未末日', '超跑女神', '明明就', '爱你没差', 
                    '夢想啟動', '大笨钟', '傻笑', '手语', '乌克丽丽', '哪裡都是你',
                    '算什么男人', '怎么了', '我要夏天', '手写的从前', '听爸爸的话',
                    '美人魚', '听见下雨的声音', '说走就走', '一点点', '前世情人', 
                    '不该', '告白气球', '愛情廢柴', '等你下课', '不爱我就拉倒', 
                    '说好不哭', '我是如此相信', 'Mojito', '瓦解']
        self.song2label={song:i for i,song in enumerate(self.songs)}
        self.label2song={i:song for i,song in enumerate(self.songs)}
        self.create_num=create_num
        self.image_w=520
        self.image_h=520
        self.max_iou=0.5  # 每首歌名的boxes的iou不能超过0.5
        
    def create_folder(self):
        while True:
            try:
                for path in [self.img_save_path,self.label_save_path,self.test_path]:
                    shutil.rmtree(path,ignore_errors=True)
                    os.makedirs(path,exist_ok=True)
                break
            except:
                pass
        
    def bbox_iou(self,box2):
        '''两两计算iou'''
        for box1 in self.tmp_boxes:
            inter_x1=max([box1[0],box2[0]])
            inter_y1=max([box1[1],box2[1]])
            inter_x2=min([box1[2],box2[2]])
            inter_y2=min([box1[3],box2[3]])
            inter_area=(inter_x2-inter_x1+1) * (inter_y2-inter_y1+1)
            box1_area=(box1[2]-box1[0]+1) * (box1[3]-box1[1]+1)
            box2_area=(box2[2]-box2[0]+1) * (box2[3]-box2[1]+1)
            iou=inter_area / (box1_area + box2_area - inter_area + 1e-16)
            if iou > self.max_iou:
                # 只要有一个与之的iou大于阈值则重新来过
                return iou
        else:
            return 0

    def draw_text(self,image,image_draw,song):
        iou=np.inf
        num=0
        while iou > self.max_iou:
            if num >= 100:
                # 为了避免陷进死循环,如果循环100次都没有找到合适的位置,则iou>0.5的阈值失效
                break
            random_font_size=np.random.randint(40,50) # 随机字号
            random_rotate=np.random.randint(-90,90) # 随机旋转角度
            random_color=np.random.randint(0,256,3) # 随机字体颜色
            random_x,random_y=np.random.randint(1,520,2) # 随机xmin,ymin
            
            font = ImageFont.truetype(self.font_path, random_font_size)
            label=self.song2label[song]
            size_wh=font.getsize(song)
            
            img = Image.new('L', size_wh)
            img_draw = ImageDraw.Draw(img)
            img_draw.text((0, 0), song, font=font, fill=255)
            img_rotate = img.rotate(random_rotate, resample=2, expand=True)
            img_color = ImageOps.colorize(img_rotate, (0,0,0), random_color)
            w,h=img_color.size
            xmin=random_x
            ymin=random_y
            # 为了避免超出520x520,修正xmin,ymin
            if random_x+w > self.image_w:
                xmin=self.image_w - w - 2
            if random_y+h > self.image_h:
                ymin=self.image_h - h - 2
            xmax=xmin+w
            ymax=ymin+h
            boxes=(xmin,ymin,xmax,ymax)
            
            iou=self.bbox_iou(boxes)
            num+=1
        image.paste(img_color, box=(xmin,ymin), mask=img_rotate)
#         image_draw.rectangle(boxes,outline=tuple(random_color))
        return image,boxes,label
    
    def process(self,boxes):
        '''
        将xmin,ymin,xmax,ymax转为x,y,w,h
        以及归一化坐标,生成label
        '''
        x1,y1,x2,y2=boxes
        x=((x1+x2)/2)/self.image_w
        y=((y1+y2)/2)/self.image_h
        w=(x2-x1)/self.image_w
        h=(y2-y1)/self.image_h
        return [x,y,w,h]
        
    def main(self):
        '''主函数'''
        self.create_folder() # 重置所需文件夹
        for i in tqdm(range(self.create_num+3)):
            random_song_num=np.random.randint(1,5) # 随机1~4首
            random_jay_img_path=np.random.choice(self.jay_img_paths) # 随机背景
            image=Image.open(random_jay_img_path).convert('RGB').resize((self.image_w,self.image_h))
            image_draw=ImageDraw.Draw(image)
            boxes_list=[]
            label_list=[]
            self.tmp_boxes=[] # 用于计算两两boxes的iou
            for j in range(random_song_num):
                song=np.random.choice(self.songs)
                image,boxes,label=self.draw_text(image,image_draw,song)
                self.tmp_boxes.append(boxes)
                boxes_list.append(self.process(boxes))
                label_list.append(label)
                
            # save image and label
            image_filename=self.img_save_path+f'image{i}.png' if i < self.create_num else self.test_path+f'test{i}.png'
            label_filename=self.label_save_path+f'image{i}.txt' if i < self.create_num else self.test_path+f'test{i}.txt'
            image.save(image_filename)
            with open(label_filename,'w') as f:
                for k in range(len(label_list)):
                    # label x y w h
                    f.write(f'{label_list[k]} {boxes_list[k][0]} {boxes_list[k][1]} {boxes_list[k][2]} {boxes_list[k][3]}\n')
                        
if __name__ == '__main__':
    creator=CreateData(5000)
    creator.main()

二、YOLOv3

代码过多,就不放上来了,直接看结果。

参考了崔庆才崔大写的滑块验证码识别https://github.com/Python3WebSpider/DeepLearningImageCaptcha2,只是修改了dataset相关代码,配置文件信息,以及训练的代码等等。

  • dataset的修改:因为我复现崔大的代码时,发现其label文件的格式是:class xmin ymin w h
    所以就自己重新改写了一下,修改成:class x y w h
  • 配置文件即yolov3.cfg的修改
  1. 三个yolo层的classes参数改为数据集的类别个数,即100
  2. 三个yolo层的上一个convolutional层的filters参数改为3*(类别个数+5),即315
  • 训练策略
  1. epoch:100
  2. batch size:16
  3. img size:416
  4. 数据集划分:80%即4000张用于训练,20%即1000张用于验证
  5. gradient accumulations:5(即被5整除的批次,优化器暂停更新梯度信息)
  6. 优化器:Adam
  • GPU内存占用:15G
  • 训练耗时:4.5小时

训练结果的如下:

Train LossValid LossValid Metrics

---- best_mAP@0.5: 0.29220820871021674
---- best_epoch: 98

小结

知识巩固:

  • TP: 将正类预测为正类的数量
  • FN: 将正类预测为负类的数量
  • FP: 将负类预测为正类的数量
  • TN: 将负类预测为负类的数量
AccuracyPrecisionRecallF1 Score
a c c = T P + T N T P + T N + F P + F N acc =\frac{TP+TN}{TP+TN+FP+FN} acc=TP+TN+FP+FNTP+TN P = T P T P + F P P=\frac{TP}{TP+FP} P=TP+FPTP R = T P T P + F N R=\frac{TP}{TP+FN} R=TP+FNTP F 1 = 2 × P × R P + R F_{1} =2\times \frac{P\times R}{P + R} F1=2×P+RP×R
  1. 从验证集的指标看,recall高,precision低,说明FN比FP小。也就是说,模型将一首歌名识别成多首,且多数情况下其中就有一首是预测正确的
  2. 训练了100个epoch,然而mAP@0.5的分数也才0.3左右,提升缓慢
  3. 看趋势,随着训练epoch的增加,各项指标还是会再提升的

三、YOLOv5

代码过多,就不放上来了,直接看结果。

参考了作者的代码https://github.com/ultralytics/yolov5,去掉了我认为是冗余的代码,主要是简化了train.py、val.py和detect.py的代码。

因为yolov5有提供多个模型:yolov5n、yolov5s、yolov5l、yolov5m、yolov5x,于是我就都尝试了一遍。

均采用以下训练策略:

  • epoch:100
  • learning rate:0.001
  • batch size:16
  • image size:416
  • augment:False
  • rect:False
  • quad:False
  • multi scale:True
  • 数据集划分:80%即4000张用于训练,20%即1000张用于验证
  • 优化器:Adam
  • lr scheduler:LambdaLR
  • 配置文件:hyp.scratch.yaml
  • 混合精度训练
  • 不使用平滑标签
  • 评价指标:fitness = 0.1 * mAP@0.5 + 0.9 * mAP@0.5:0.95

1. yolov5n

GPU内存占用:2G
训练耗时:2.2小时
模型大小:7.39MB
---- best_fitness=0.6837750339549021
---- best_epoch=87

训练结果的如下:

Train LossValid LossValid Metrics

2. yolov5s

GPU内存占用:4.25G
训练耗时:2.5小时
模型大小:27.9MB
---- best_fitness=0.878297457833696
---- best_epoch=99

训练结果的如下:

Train LossValid LossValid Metrics

3. yolov5l

GPU内存占用:12.8G
训练耗时:3.75小时
模型大小:178MB
---- best_fitness=0.9015696258245549
---- best_epoch=93

训练结果的如下:

Train LossValid LossValid Metrics

4. yolov5m

GPU内存占用:8.5G
训练耗时:3小时
模型大小:81.4MB
---- best_fitness=0.9197881788592055
---- best_epoch=98

训练结果的如下:

Train LossValid LossValid Metrics

5. yolov5x

GPU内存占用:15.5G
训练耗时:6.3小时
模型大小:332MB
---- best_fitness=0.9216454715562004
---- best_epoch=100

训练结果的如下:

Train LossValid LossValid Metrics

小结

  1. yolov5n模型最小,GPU内存占用最小,且训练时长也最短,可惜性能是最差的,但也比yolov3要好,这一点毋容置疑
  2. 如果要考虑GPU内存、模型大小、训练时长和性能,那么yolov5s是最好的选择,性价比最高
  3. yolov5l、yolov5m和yolov5x的性能均达到了90%以上,综合考虑的话,我会选择yolov5m。

四、识别效果

最后,yolov3和yolov5分别对开头的那张图片进行识别,来看看实际的效果吧。(过滤条件均设置为0.5)

因为边幅有限,就比较一张吧,可以看得出来:

  1. yolov3只识别出了等你下课稻香,且置信度也不是很高
  2. yolov5n将稻香识别成傻笑了,以及说好不哭的置信度也不是很高
  3. 剩下的均成功识别出来了四首歌,且置信度都是非常高的。差别可能也不是很明显,可以多用几张图片进行测试,在我测试多几张后,yolov5x是最好的,毕竟分数最高。
yolov3yolov5nyolov5s
yolov5lyolov5myolov5x

五、参考链接

周杰伦官方专辑封面高清

崔庆才崔大写的滑块验证码识别

yolov5作者代码


以上即为本篇全部内容,代码我整理了一遍,且运行后无bug,若需要源代码的可以关注我的微信公众号《Python王者之路》,回复关键词:20211226,即可获取。


写在最后

在看了崔大写的滑块验证码识别的推文后,我当时就已经想好了要出这一篇文章了

我先是复现了崔大的代码,当然,这其中遇到了很多的坑,毕竟第一次接触目标检测,之前只是会一点图片分类

可谓说历经千辛万苦,终于成功复现了,当时就想着要不直接写一篇:我的复现过程遇到的问题的文章算了

可是呢,看看yolov3的识别效果,如此难以接受

于是,又开始研究最新的yolov5,因为此时对yolo有了初步的认识,这时复现yolov5还算比较轻松

然后,花了一周左右的时间,测试出了最好的训练策略即本篇采用的策略

最后,又花了一周的时间,按我的训练策略来训练yolov5的5个模型,以及写这篇文章

虽然成功运用yolo模型来做目标检测了,但对于yolo具体细节还有待研究呀~

最后,提前祝大家2022年元旦快乐吧!!

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
【资源说明】 基于孪生神经网络实现的点选识别python源码(带注释)+运行说明+数据集+预训练模型.zip ## 效果 4090训练100轮 测试集可以达到98.6%以上,基本上已经破解了该类验证码。 ![效果演示](./beeb1dc9cdf4f18a98a51d631745ba75.png "效果演示") ## 坑 注意啊,建议重新把yolo分割文字那个部分训练下,因为我这个样本是别人从前台截图后标注训练的,导致泛化性能不是很好!! 有能力的话建议把样本都重新标注下。 ### 如何使用? 下载数据集和预训练模型:https://systems.lanzout.com/iWUqz15mo57a #### 环境安装 安装环境,我用到的是python3.10 ``` conda create -n geetest python=3.8 ``` 安装必要的环境 ``` pip3 install -r requirement.txt ``` ### CUDA安装 安装cuda和 cudnn ``` conda install cudnn=8.1.0.77 cudatoolkit=11.2.0 ``` #### 数据准备 准备数据集,放入data中,格式为 id_序号.jpg|png,id可以采用uuid,序号第一张图是1,第二张图是2,只能两张图 例如,相同的两个字, ``xxxxx_1.jpg``和``xxxxx_2.jpg`` ### 训练模型 配置训练参数,config.py中,一般来说只需要配置gpu就行了,如果你没有gpu就不填,就自动使用cpu 开始训练 ``` python train.py ``` 预测 ``` python predict.py ``` 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,也适用于小白学习入门进阶。当然也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或者热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载,沟通交流,互相学习,共同进步!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值