1 比赛简介
1.1 赛题背景:
验证码作为性价较高的安全验证方法,在多场合得到了广泛的应用,有效地防止了机器人进行身份欺骗,其中,以基于文本字符的静态验证码最为常见。随着使用的深入,噪声点、噪声线、重叠、形变等干扰手段层出不穷,不断提升安全防范级别。RPA技术作为企业数字化转型的关键,因为其部署的非侵入式备受企业青睐,验证码识别率不高往往限制了RPA技术的应用。一个能同时过滤多种干扰的验证码模型,对于相关自动化技术的拓展使用有着一定的商业价值。
1.2 赛题背景:
验证码作为性价较高的安全验证方法,在多场合得到了广泛的应用,有效地防止了机器人进行身份欺骗,其中,以基于文本字符的静态验证码最为常见。随着使用的深入,噪声点、噪声线、重叠、形变等干扰手段层出不穷,不断提升安全防范级别。RPA技术作为企业数字化转型的关键,因为其部署的非侵入式备受企业青睐,验证码识别率不高往往限制了RPA技术的应用。一个能同时过滤多种干扰的验证码模型,对于相关自动化技术的拓展使用有着一定的商业价值。
1.3 赛题任务:
本次大赛以已标记字符信息的实例字符验证码图像数据为训练样本,参赛选手需基于提供的样本构建模型,对测试集中的字符验证码图像进行识别,提取有效的字符信息。训练数据集不局限于提供的数据,可以加入公开的数据集。
2 数据与评测
2.1 数据简介
此次比赛为选手提供15000张带标注信息的训练数据集,每张训练数据都是包含一个4位文本字符的验证码图像,并对当前图像中的文本字符进行了标注;测试数据集含25000张验证码图像。
2.2 数据说明
提供训练数据集打包文件train_imgs.zip(文件名称即对应该图片文本字符标签);提供测试数据集打包文件test_imgs.zip,测试数据集包含待识别的图像文件。
2.3 评测标准
本次比赛采用评价方式为准确率(accuracy),对于参赛者提交的结果,要求完全识别出完整的验证码文本信息,最终根据测试图像数据预测的准确率进行从高到低的排序。
同等准确率的以提交结果的时间排名,先提交者胜出。
P ( 准 确 率 ) = 所 有 待 检 测 的 目 标 数 量 / 检 测 正 确 的 目 标 数 量 P( 准确率 )= 所有待检测的目标数量 /检测正确的目标数量
P(准确率)=所有待检测的目标数量/检测正确的目标数量
3 构建训练集和验证集
数据集链接🔗:https://aistudio.baidu.com/aistudio/datasetdetail/126477
大家运行项目直接需要挂载该比赛数据集
3.1 数据集准备
!ls data/data126477/ # 一共三个文件 # submit_example.csv test_dataset.zip training_dataset.zip submit_example.csv test_dataset.zip training_dataset.zip # 解压数据集 !unzip -o data/data126477/training_dataset.zip -d data/ !unzip -o data/data126477/test_dataset.zip -d data/ !cp data/data126477/submit_example.csv data/ Archive: data/data126477/training_dataset.zip creating: data/training_dataset/ inflating: data/training_dataset/00IS.png inflating: data/training_dataset/00O3.png inflating: data/training_dataset/0180.png inflating: data/training_dataset/01BA.png ......
3.2 划分数据集
我们可以将15000张训练集按照8:2进行划分,12000张作为训练集 3000作为验证集
import pandas as pd import shutil import os import glob from tqdm import tqdm from sklearn.model_selection import train_test_split data_path = 'train_data/' dcic_data_path = './PaddleOCR/train_data/dcic_data/' dcic_train = './PaddleOCR/train_data/dcic_data/train' dcic_valid = './PaddleOCR/train_data/dcic_data/valid' dcic_test = './PaddleOCR/train_data/dcic_data/test' os.makedirs(dcic_data_path, exist_ok=True) os.makedirs(dcic_train, exist_ok=True) os.makedirs(dcic_valid, exist_ok=True) os.makedirs(dcic_test, exist_ok=True) # print([filepath for filepath in glob.glob('data/dcic_data/training_dataset/')]) # print(glob.glob('data/dcic_data/training_dataset/*.png')) # print(os.listdir('data/training_dataset')) train_images = os.listdir('data/training_dataset') test_images = os.listdir('data/test_dataset') train_imgs, valid_imgs = train_test_split(train_images, test_size=0.2, random_state=42, shuffle=True) print(len(train_imgs), len(valid_imgs)) all_txts = [] # shutil.copy('data/dcic_data/training_dataset/0A5o.png', 'train_data/dcic_data/train/0A5o.png') with open('./PaddleOCR/train_data/dcic_data/rec_gt_train.txt', 'w', encoding='utf-8') as f: for image in tqdm(train_imgs): shutil.copy(f'data/training_dataset/{image}', f'./PaddleOCR/train_data/dcic_data/train/{image}') txt = image.split('.png')[0] all_txts.append(txt) f.write(f'train/{image}\t{txt}' + '\n') with open('./PaddleOCR/train_data/dcic_data/rec_gt_valid.txt', 'w', encoding='utf-8') as f: for image in tqdm(valid_imgs): shutil.copy(f'data/training_dataset/{image}', f'./PaddleOCR/train_data/dcic_data/valid/{image}') txt = image.split('.png')[0] all_txts.append(txt) f.write(f'valid/{image}\t{txt}' + '\n') for image in tqdm(test_images): shutil.copy(f'data/test_dataset/{image}', f'./PaddleOCR/train_data/dcic_data/test/{image}') # with open('train_data/dcic_data/captcha.txt', 'w', encoding='utf-8') as f: # all_str = ''.join(all_txts) # dict_char=sorted(set(all_str)) # for char in dict_char: # f.write(char+'\n')
import cv2 import matplotlib.pyplot as plt # 读图 raw_img = cv2.imread("train_data/dcic_data/valid/01jQ.png") plt.figure() plt.subplot(2,1,1) # 可视化原图 plt.imshow(raw_img) # 缩放并归一化 padding_im, draw_img = resize_norm_img(raw_img) plt.subplot(2,1,2) # 可视化网络输入图 plt.imshow(draw_img) plt.show()
4 配置文件
PaddleOCR训练与验证可以通过config文件进行配置,以下为确认配置文件中的数据路径是否正确,以 rec_icdar15_train.yml为例:
Train: dataset: name: SimpleDataSet # 训练数据根目录 data_dir: ./train_data/ic15_data/ # 训练数据标签 label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - RecResizeImg: image_shape: [3, 32, 100] # [3,32,320] - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order loader: shuffle: True batch_size_per_card: 256 drop_last: True num_workers: 8 use_shared_memory: False Eval: dataset: name: SimpleDataSet # 评估数据根目录 data_dir: ./train_data/ic15_data # 评估数据标签 label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - RecResizeImg: image_shape: [3, 32, 100] - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order loader: shuffle: False drop_last: False batch_size_per_card: 256 num_workers: 4 use_shared_memory: False
# 复制一份配置文件,作为dcic比赛配置文件 !cp ./PaddleOCR/configs/rec/rec_icdar15_train.yml ./PaddleOCR1/rec_dcic_train.yml
将以下内容填充到./PaddleOCR/configs/rec/rec_dcic_train.yml,为了方面大家理解,我这里加了一些核心注释:
Global: use_gpu: true # 训练轮数 epoch_num: 300 log_smooth_window: 20 print_batch_step: 10 # 模型保存路径 save_model_dir: ./output/rec/dcic/ save_epoch_step: 3 # evaluation is run every 2000 iterations eval_batch_step: [0, 2000] cal_metric_during_train: True pretrained_model: pretrain_models/rec_mv3_none_bilstm_ctc/best_accuracy checkpoints: save_inference_dir: ./ use_visualdl: False infer_img: doc/imgs_words_en/word_10.png # for data or label process character_dict_path: ppocr/utils/en_dict.txt max_text_length: 4 infer_mode: False use_space_char: False save_res_path: ./output/rec/predicts_dcic.txt # 优化器设置 Optimizer: name: Adam beta1: 0.9 beta2: 0.999 lr: learning_rate: 0.0005 regularizer: name: 'L2' factor: 0 # 模型结构 Architecture: model_type: rec algorithm: CRNN Transform: Backbone: name: MobileNetV3 scale: 0.5 model_name: large Neck: name: SequenceEncoder encoder_type: rnn # rnn隐层单元个数,超参数 hidden_size: 96 Head: name: CTCHead fc_decay: 0 Loss: name: CTCLoss PostProcess: name: CTCLabelDecode Metric: name: RecMetric main_indicator: acc Train: dataset: name: SimpleDataSet # 训练集路径 data_dir: ./train_data/dcic_data/ # 训练集标签文件 label_file_list: ["./train_data/dcic_data/rec_gt_train.txt"] transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - RecResizeImg: image_shape: [3, 32, 96] - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order loader: shuffle: True batch_size_per_card: 256 drop_last: True num_workers: 0 use_shared_memory: False Eval: dataset: name: SimpleDataSet data_dir: ./train_data/dcic_data label_file_list: ["./train_data/dcic_data/rec_gt_valid.txt"] transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - RecResizeImg: image_shape: [3, 32, 96] - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order loader: shuffle: False drop_last: False batch_size_per_card: 256 num_workers: 4 use_shared_memory: False
训练模型介绍
CRNN模型
本项目模型采用文字识别经典CRNN模型(CNN+RNN+CTC),其中部分模型代码经过PaddleOCR源码改编,完成识别模型的搭建、训练、评估和预测过程。训练时可以手动更改config配置文件(数据训练、加载、评估验证等参数),默认采用优化器采用Adam,使用CTC损失函数。
CRNN网络结构:
详细结构图:
CRNN网络结构包含三部分,从下到上依次为:
(1)卷积层。作用是从输入图像中提取特征序列。
(2)循环层。作用是预测从卷积层获取的特征序列的标签(真实值)分布。
(3)转录层。作用是把从循环层获取的标签分布通过去重整合等操作转换成最终的识别结果。
CRNN模型训练:
在模型训练过程中,首先使用标准的CNN网络提取文本图像的特征,再利用BLSTM将特征向量进行融合以提取字符序列的上下文特征,然后得到每列特征的概率分布,最后通过转录层(CTC)进行预测得到文本序列。
其具体模型训练流程为:
1.将输入图像统一缩放至32W3。
2.利用CNN提取后图像卷积特征,得到的大小为:1W/4512。
3.通过上述输入到LSTM提取序列特征,得到W/4*n后验概率矩阵。
4.利用CTC损失,实现标签和输出一一对应,进行训练。
CTC损失函数介绍:
CTC是一种Loss计算方法,用CTC代替Softmax Loss,训练样本无需对齐。引入blank字符,解决有些位置没有字符的问题,通过递推,快速计算梯度。
以下以apple单词为例,引入“-”符号,解释CTC网络是如何展开路径进行计算的。
路径展开原则:
CTC训练流程和传统的神经网络类似,构建损失函数,然后根据BP算法进行训练,不同之处在于传统的神经网络的训练准则是针对每帧数据,即每帧数据的训练误差最小,而CTC的训练准则是基于序列的,比如最大化 p(l|x) ,序列化的概率求解比较复杂,因为一个输出序列可以对应很多的路径,所有引入前后向算法来简化计算。
前向概率:
后向概率:
计算CTC Loss:
更多CRNN学习资料可参考我的这篇博客:[https://blog.csdn.net/qq_36816848/article/details/121723891](https://blog.csdn.net/qq_36816848/article/details/121723891)
4 训练评估与预测
下载预训练模型:为了加快收敛速度,建议下载训练好的模型在 比赛 数据上进行 finetune
4.1 训练
%cd PaddleOCR/ # 下载MobileNetV3的预训练模型 !wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_none_bilstm_ctc_v2.0_train.tar # 解压模型参数 !tar -xf pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar && rm -rf pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar !mv rec_mv3_none_bilstm_ctc_v2.0_train ./pretrain_models /home/aistudio/PaddleOCR
正在解析主机 paddleocr.bj.bcebos.com (paddleocr.bj.bcebos.com)... 182.61.200.229, 182.61.200.195, 2409:8c04:1001:1002:0:ff:b001:368a
正在连接 paddleocr.bj.bcebos.com (paddleocr.bj.bcebos.com)|182.61.200.229|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度: 51200000 (49M) [application/x-tar]
正在保存至: “./pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar”
rec_mv3_none_bilstm 100%[===================>] 48.83M 17.6MB/s in 2.8s 2022-02-19 20:43:11 (17.6 MB/s) - 已保存 “./pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar” [51200000/51200000])
启动训练命令很简单,指定好配置文件即可。另外在命令行中可以通过 -o 修改配置文件中的参数值。启动训练命令如下所示
其中:
Global.pretrained_model: 加载的预训练模型路径 Global.character_dict_path : 字典路径(这里只支持26个小写字母+数字) Global.eval_batch_step : 评估频率 Global.epoch_num: 总训练轮数 !pwd !cd PaddleOCR !pwd
/home/aistudio /home/aistudio !python3 tools/train.py -c configs/rec/rec_dcic_train.yml \ -o Global.pretrained_model=./pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train/best_accuracy python3: can't open file 'tools/train.py': [Errno 2] No such file or directory
4.2 模型评估
评估数据集可以通过 configs/rec/rec_dcic_train.yml 修改Eval中的 label_file_path 设置。
这里默认使用 dcic 的评估集,加载刚刚训练好的模型权重:
!python tools/eval.py -c configs/rec/rec_dcic_train.yml -o Global.checkpoints=output/rec/dcic/best_accuracy
4.3 预测
使用 PaddleOCR 训练好的模型,可以通过以下脚本进行快速预测。
train_data/dcic_data/train/0a1E.png
默认预测图片存储在 infer_img 里,通过 -o Global.checkpoints 加载训练好的参数文件:
!python tools/infer_rec.py -c configs/rec/rec_dcic_train.yml \ -o Global.checkpoints=./output/rec/dcic/best_accuracy \ Global.infer_img=./train_data/dcic_data/valid/01jU.png
[2022/01/24 03:54:25] root INFO: Architecture : [2022/01/24 03:54:25] root INFO: Backbone : [2022/01/24 03:54:25] root INFO: model_name : large [2022/01/24 03:54:25] root INFO: name : MobileNetV3 [2022/01/24 03:54:25] root INFO: scale : 0.5 [2022/01/24 03:54:25] root INFO: Head : [2022/01/24 03:54:25] root INFO: fc_decay : 0 [2022/01/24 03:54:25] root INFO: name : CTCHead [2022/01/24 03:54:25] root INFO: Neck : [2022/01/24 03:54:25] root INFO: encoder_type : rnn [2022/01/24 03:54:25] root INFO: hidden_size : 96 [2022/01/24 03:54:25] root INFO: name : SequenceEncoder [2022/01/24 03:54:25] root INFO: Transform : None [2022/01/24 03:54:25] root INFO: algorithm : CRNN [2022/01/24 03:54:25] root INFO: model_type : rec [2022/01/24 03:54:25] root INFO: Eval : [2022/01/24 03:54:25] root INFO: dataset : [2022/01/24 03:54:25] root INFO: data_dir : ./train_data/dcic_data [2022/01/24 03:54:25] root INFO: label_file_list : ['./train_data/dcic_data/rec_gt_valid.txt'] [2022/01/24 03:54:25] root INFO: name : SimpleDataSet [2022/01/24 03:54:25] root INFO: transforms : [2022/01/24 03:54:25] root INFO: DecodeImage : [2022/01/24 03:54:25] root INFO: channel_first : False [2022/01/24 03:54:25] root INFO: img_mode : BGR [2022/01/24 03:54:25] root INFO: CTCLabelEncode : None [2022/01/24 03:54:25] root INFO: RecResizeImg : [2022/01/24 03:54:25] root INFO: image_shape : [3, 32, 96] [2022/01/24 03:54:25] root INFO: KeepKeys : [2022/01/24 03:54:25] root INFO: keep_keys : ['image', 'label', 'length'] [2022/01/24 03:54:25] root INFO: loader : [2022/01/24 03:54:25] root INFO: batch_size_per_card : 256 [2022/01/24 03:54:25] root INFO: drop_last : False [2022/01/24 03:54:25] root INFO: num_workers : 4 [2022/01/24 03:54:25] root INFO: shuffle : False [2022/01/24 03:54:25] root INFO: use_shared_memory : False [2022/01/24 03:54:25] root INFO: Global : [2022/01/24 03:54:25] root INFO: cal_metric_during_train : True [2022/01/24 03:54:25] root INFO: character_dict_path : ppocr/utils/en_dict.txt [2022/01/24 03:54:25] root INFO: checkpoints : ./output/rec/dcic/best_accuracy [2022/01/24 03:54:25] root INFO: debug : False [2022/01/24 03:54:25] root INFO: distributed : False [2022/01/24 03:54:25] root INFO: epoch_num : 300 [2022/01/24 03:54:25] root INFO: eval_batch_step : [0, 2000] [2022/01/24 03:54:25] root INFO: infer_img : ./train_data/dcic_data/valid/01jU.png [2022/01/24 03:54:25] root INFO: infer_mode : False [2022/01/24 03:54:25] root INFO: log_smooth_window : 20 [2022/01/24 03:54:25] root INFO: max_text_length : 4 [2022/01/24 03:54:25] root INFO: pretrained_model : pretrain_models/rec_mv3_none_bilstm_ctc/best_accuracy [2022/01/24 03:54:25] root INFO: print_batch_step : 10 [2022/01/24 03:54:25] root INFO: save_epoch_step : 3 [2022/01/24 03:54:25] root INFO: save_inference_dir : ./ [2022/01/24 03:54:25] root INFO: save_model_dir : ./output/rec/dcic/ [2022/01/24 03:54:25] root INFO: save_res_path : ./output/rec/predicts_dcic.txt [2022/01/24 03:54:25] root INFO: use_gpu : True [2022/01/24 03:54:25] root INFO: use_space_char : False [2022/01/24 03:54:25] root INFO: use_visualdl : False [2022/01/24 03:54:25] root INFO: Loss : [2022/01/24 03:54:25] root INFO: name : CTCLoss [2022/01/24 03:54:25] root INFO: Metric : [2022/01/24 03:54:25] root INFO: main_indicator : acc [2022/01/24 03:54:25] root INFO: name : RecMetric [2022/01/24 03:54:25] root INFO: Optimizer : [2022/01/24 03:54:25] root INFO: beta1 : 0.9 [2022/01/24 03:54:25] root INFO: beta2 : 0.999 [2022/01/24 03:54:25] root INFO: lr : [2022/01/24 03:54:25] root INFO: learning_rate : 0.0005 [2022/01/24 03:54:25] root INFO: name : Adam [2022/01/24 03:54:25] root INFO: regularizer : [2022/01/24 03:54:25] root INFO: factor : 0 [2022/01/24 03:54:25] root INFO: name : L2 [2022/01/24 03:54:25] root INFO: PostProcess : [2022/01/24 03:54:25] root INFO: name : CTCLabelDecode [2022/01/24 03:54:25] root INFO: Train : [2022/01/24 03:54:25] root INFO: dataset : [2022/01/24 03:54:25] root INFO: data_dir : ./train_data/dcic_data/ [2022/01/24 03:54:25] root INFO: label_file_list : ['./train_data/dcic_data/rec_gt_train.txt'] [2022/01/24 03:54:25] root INFO: name : SimpleDataSet [2022/01/24 03:54:25] root INFO: transforms : [2022/01/24 03:54:25] root INFO: DecodeImage : [2022/01/24 03:54:25] root INFO: channel_first : False [2022/01/24 03:54:25] root INFO: img_mode : BGR [2022/01/24 03:54:25] root INFO: CTCLabelEncode : None [2022/01/24 03:54:25] root INFO: RecResizeImg : [2022/01/24 03:54:25] root INFO: image_shape : [3, 32, 96] [2022/01/24 03:54:25] root INFO: KeepKeys : [2022/01/24 03:54:25] root INFO: keep_keys : ['image', 'label', 'length'] [2022/01/24 03:54:25] root INFO: loader : [2022/01/24 03:54:25] root INFO: batch_size_per_card : 256 [2022/01/24 03:54:25] root INFO: drop_last : True [2022/01/24 03:54:25] root INFO: num_workers : 0 [2022/01/24 03:54:25] root INFO: shuffle : True [2022/01/24 03:54:25] root INFO: use_shared_memory : False [2022/01/24 03:54:25] root INFO: profiler_options : None [2022/01/24 03:54:25] root INFO: train with paddle 2.2.1 and device CUDAPlace(0) W0124 03:54:25.561218 8122 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1 W0124 03:54:25.566077 8122 device_context.cc:465] device: 0, cuDNN Version: 7.6. [2022/01/24 03:54:30] root INFO: resume from ./output/rec/dcic/best_accuracy [2022/01/24 03:54:30] root INFO: infer_img: ./train_data/dcic_data/valid/01jU.png [2022/01/24 03:54:30] root INFO: result: o1lU 0.9584374 [2022/01/24 03:54:30] root INFO: success!
我们可以看到预测结果
[2022/01/24 03:54:30] root INFO: infer_img: ./train_data/dcic_data/valid/01jU.png [2022/01/24 03:54:30] root INFO: result: o1lU 0.9584374 [2022/01/24 03:54:30] root INFO: success!
# 预测全部测试集 !python tools/infer_rec.py -c configs/rec/rec_dcic_train.yml \ -o Global.checkpoints=./output/rec/dcic/best_accuracy \ Global.infer_img=../data/test_dataset !pwd /home/aistudio/PaddleOCR import pandas as pd submit = pd.read_csv('../data/data126477/submit_example.csv') # print(submit) nums = [] results = [] with open('output/rec/predicts_dcic.txt', 'r', encoding='utf-8') as f: # print(f.read().split('\t')[:2]) data = f.read().split('\t') for i in range(2, len(data), 2): img,res=data[i - 2:i] # print(img) img=img.split('/')[-1].split('.png')[0] # print(img) nums.append(int(img)) results.append(res) result_df=pd.DataFrame({'num':nums,'tag':results}) result_df=result_df.sort_values('num',ascending=True) result_df.to_csv('baseline.csv',index=None) result_df 1 num tag 0 1 01Fb 1 10 04xs 2 100 0Onx 113 101 0OU1 224 102 0p3c ... ... ... 234 10208 OxkP 235 10209 oxmH 237 10210 0XMy 238 10211 0xp6 239 10212 0xq2
项目总结:
本项目基于PaddOCR源码改编,主要了解Paddle基本使用。通过经典文字识别领域CRNN模型进行验证码识别,通过数据集划分,修改模型参数配置,训练、测试到最终预测,最终准确率可以达到95%以上,后期可通过模型微调或者寻找更适合网络进行训练,不断优化。