Easy-Classification-验证码识别
1.背景
- 任务输入:4位大小写字母和数字混合组成的验证码图片, 图片大小为100*40。
- 任务输出:识别图像中的字母和数字,并输出验证码编码。
2.验证码识别
2.1 生成训练数据
- 基于脚本模拟验证码图片,生成的验证码字符做大小写区分。
- 每个验证码图片,对应的验证码字符串为图片名称,如00FS_69570.png,00FS是验证码字符串,后面的是随机数避免文件重名。
2.2 编写训练脚本
"""
@Description : 构建Dataset类,不同的任务,dataset自行编写,如基于csv,文本等加载标签,均可从cfg配置文件中读取后,自行扩展编写
编写自定义Dataset类时,初始化参数需定义为source_img, cfg。否则数据加载通用模块,data_load_service.py模块会报错。
source_img :传入的图像地址信息
cfg:传入的配置类信息,针对不同的任务,可能生成的label模式不同,可基于配置类指定label的加载模式,最终为训练的图像初始化label (用户自定义实现)
本例为验证码加载类:基于文件名称生成标签(如验证码:0AaW_54463.png,标签值为:0AaW,返回one-hot编码)
"""import torch
from torch.utils.data.dataset import Dataset
import torchvision.transforms as transforms
import cv2
from universe.data_load.normalize_adapter import NormalizeAdapter
from PIL import Image
from universe.utils.utils import one_hot
classTrainDataset(Dataset):
"""
构建一个 加载原始图片的dataSet对象
此函数可加载 训练集数据,基于路径识别验证码真实的label,label在转换为one-hot编码
若 验证集逻辑与训练集逻辑一样,验证集可使用TrainDataset,不同,则需自定义一个,参考如下EvalDataset
"""def__init__(self, source_img, cfg):
self.source_img = source_img
self.cfg = cfg
self.transform = createTransform(cfg, TrainImgDeal)
def__getitem__(self, index):
img = cv2.imread(self.source_img[index])
if self.transform isnotNone:
img = self.transform(img)
# ../ data / train\Qigj_73075.png label = self.source_img[index].split("_")[0][-4:]
target = torch.Tensor(one_hot(label))
return img, target, self.source_img[index]
def__len__(self):
returnlen(self.source_img)
classEvalDataset(Dataset):
"""
构建一个 加载原始图片的dataSet对象
此函数可加载 验证集数据,基于路径识别验证码真实的label,label在转换为one-hot编码
"""def__init__(self, source_img, cfg):
self.source_img = source_img
self.cfg = cfg
# 若验证集图片处理逻辑(增强,调整)与 训练集不同,可自定义一个EvalImgDeal self.transform = createTransform(cfg, TrainImgDeal)
def__getitem__(self, index):
img = cv2.imread(self.source_img[index])
if self.transform isnotNone:
img = self.transform(img)
# ../ data / train\Qigj_73075.png label = self.source_img[index].split("_")[0][-4:]
target = torch.Tensor(one_hot(label))
return img, target, self.source_img[index]
def__len__(self):
returnlen(self.source_img)
classPredictDataset(Dataset):
"""
构建一个 加载预测图片的dataSet对象
此函数可加载 测试集数据,应用集数据(返回图像信息)
"""def__init__(self, source_img,cfg):
self.source_img = source_img
# 若预测集图片处理逻辑(增强,调整)与 训练集不同,可自定义一个PredictImgDeal self.transform = createTransform(cfg, TrainImgDeal)
def__getitem__(self, index):
img = cv2.imread(self.source_img[index])
if self.transform isnotNone:
img = self.transform(img)
# 用于记录实际的label值(因为应用数据也是脚本生成的,所以可以知道正确的验证码) real_label = self.source_img[index].split("_")[0][-4:]
return img, real_label, self.source_img[index]
def__len__(self):
returnlen(self.source_img)
classTrainImgDeal:
def__init__(self, cfg):
img_size = cfg['target_img_size']
self.h = img_size[0]
self.w = img_size[1]
def__call__(self, img):
img = cv2.resize(img, (self.h, self.w))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img)
return img
defcreateTransform(cfg, img_deal):
my_normalize = NormalizeAdapter.getNormalize(cfg['model_name'])
transform = transforms.Compose([
img_deal(cfg),
transforms.ToTensor(),
my_normalize,
])
return transform
2.3 训练结果展示
2.3.1 图像训练
2.3.2 混合图像训练
1/100 [9600/10000 (96%)] - ETA: 0:00:19, loss: 0.0003, acc: 0.9234 LR: 0.001000
[VAL] loss: 0.00012, acc: 81.060% 2/100 [9600/10000 (96%)] - ETA: 0:00:23, loss: 0.0000, acc: 0.9977 LR: 0.001000
[VAL] loss: 0.00011, acc: 82.000% 3/100 [9600/10000 (96%)] - ETA: 0:00:20, loss: 0.0000, acc: 0.9978 LR: 0.001000
[VAL] loss: 0.00012, acc: 79.520% 4/100 [9600/10000 (96%)] - ETA: 0:00:18, loss: 0.0000, acc: 0.9888 LR: 0.001000
[VAL] loss: 0.00013, acc: 78.020% 5/100 [9600/10000 (96%)] - ETA: 0:00:18, loss: 0.0000, acc: 0.9824 LR: 0.001000
[VAL] loss: 0.00012, acc: 80.260% 6/100 [9600/10000 (96%)] - ETA: 0:00:19, loss: 0.0000, acc: 0.9903 LR: 0.001000
[VAL] loss: 0.00013, acc: 80.040% 7/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9923 LR: 0.000100
[VAL] loss: 0.00010, acc: 83.900% 8/100 [9600/10000 (96%)] - ETA: 0:00:20, loss: 0.0000, acc: 0.9977 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.280% 9/100 [9600/10000 (96%)] - ETA: 0:00:18, loss: 0.0000, acc: 0.9987 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.400% 10/100 [9600/10000 (96%)] - ETA: 0:00:20, loss: 0.0000, acc: 0.9992 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.600% 11/100 [9600/10000 (96%)] - ETA: 0:00:19, loss: 0.0000, acc: 0.9993 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.460% 12/100 [9600/10000 (96%)] - ETA: 0:00:19, loss: 0.0000, acc: 0.9995 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.600% 13/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9998 LR: 0.000100
[VAL] loss: 0.00009, acc: 85.100% 14/100 [9600/10000 (96%)] - ETA: 0:00:19, loss: 0.0000, acc: 0.9996 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.720% 15/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9998 LR: 0.000100
[VAL] loss: 0.00009, acc: 85.140% 16/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9998 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.720% 17/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9999 LR: 0.000100
[VAL] loss: 0.00009, acc: 85.220% 18/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9999 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.900% 19/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9999 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.980% 20/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 1.0000 LR: 0.000100
[VAL] loss: 0.00009, acc: 85.280% 21/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9999 LR: 0.000100
[VAL] loss: 0.00009, acc: 85.140% 22/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 1.0000 LR: 0.000100
[VAL] loss: 0.00009, acc: 85.140% 23/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 0.9998 LR: 0.000100
[VAL] loss: 0.00009, acc: 84.880% 24/100 [9600/10000 (96%)] - ETA: 0:00:20, loss: 0.0000, acc: 1.0000 LR: 0.000100
[VAL] loss: 0.00010, acc: 85.120% 25/100 [9600/10000 (96%)] - ETA: 0:00:20, loss: 0.0000, acc: 1.0000 LR: 0.000010
[VAL] loss: 0.00009, acc: 85.160% 26/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 1.0000 LR: 0.000010
[VAL] loss: 0.00009, acc: 85.180% 27/100 [9600/10000 (96%)] - ETA: 0:00:21, loss: 0.0000, acc: 1.0000 LR: 0.000010
[VAL] loss: 0.00009, acc: 85.220% [INFO] Early Stop with patient 7 , best is Epoch - 20 :0.852800
--------------------------------------------------
{'model_name': 'mobilenetv3', 'GPU_ID': '', 'class_number': 248, 'random_seed': 42, 'cfg_verbose': True, 'num_workers': 8, 'train_path': 'data/train', 'val_path': 'data/val', 'test_path': 'data/test', 'label_type': 'DIR', 'label_path': '', 'pretrained': 'output/mobilenetv3_e21_0.84700.pth', 'try_to_train_items': 10000, 'save_best_only': True, 'save_one_only': True, 'save_dir': 'output/', 'metrics': ['acc'], 'loss': 'CE', 'show_heatmap': False, 'show_data': False, 'target_img_size': [224, 224], 'learning_rate': 0.001, 'batch_size': 64, 'epochs': 100, 'optimizer': 'Adam', 'scheduler': 'default-0.1-3', 'warmup_epoch': 0, 'weight_decay': 0, 'k_flod': 5, 'start_fold': 0, 'early_stop_patient': 7, 'use_distill': 0, 'label_smooth': 0, 'class_weight': None, 'clip_gradient': 0, 'freeze_nonlinear_epoch': 0, 'dropout': 0.5, 'mixup': False, 'cutmix': False, 'sample_weights': None, 'model_path': '../../config/weight/mobilenet/mobilenetv3_e22_1.00000.pth', 'TTA': False, 'merge': False, 'test_batch_size': 1}
-------------------------------------------------- Process finished with exit code 0
2.4 预测应用
def predict(cfg):
initConfig(cfg)
model = ModelService(cfg)
data = DataLoadService(cfg) test_loader = data.getPredictDataloader(PredictDataset) runner = RunnerCaptchaService(cfg, model)
modelLoad(cfg['model_path'])
res_dict = runner.predict(test_loader)
print(len(res_dict)) # to csv
res_df = pd.DataFrame.from_dict(res_dict, orient='index', columns=['label'])
res_df = res_df.reset_index().rename(columns={'index': 'image_id'})
res_df.to_csv(os.path.join(cfg['save_dir'], 'pre.csv'),
index=False, header=True) if __name__ == '__main__':
predict(cfg)
2.4.1 图像训练-预测结果
2.4.2 混合图像模式-预测结果
3.扩展与思考
- 验证码识别中,大小写字母(如x,X),这种容易识别失败。
- 当训练数据偏少时,对于o,O,0,这种看起来相似的字母也容易识别错误。若训练的数据中某一个字母的图像较少,识别该字母的准确度也偏低。所以,本质还是尽可能的丰富训练数据。
- 验证码识别,基于特定场景,基于脚本构建的训练数据,测试数据一定要尽可能的与实际的图像相似(清晰度,字母间的间隔),这样准确度才高。参考上面的两种测试,若训练的验证码图像与最终预测的图像相似,则准确度高。
- 字符验证码不区分大小写:本算法不变,一样支持。
- 多字符验证码识别:基于具体的位数,如6位验证码,调整分类数值为62*6,改造one-hot,调整为6位,调整acc匹配函数,即可训练支持。
- 纯数字验证码:改造one-hot函数,现有算法的每一位字符串,均存在62种情况(10个数字,52个大小写字母),数字版本只存在10种情况,比现有模式更简单,调整输出分类数值为40,改造one-hot,调整acc匹配函数,即可训练支持。
- 简单数学计算验证码识别:如5-1=?,4+3=?,这种模式的验证码识别,本质还是简单字符识别,
Easy-Classification-验证码识别的更多相关文章
- 验证码识别之w3cschool字符图片验证码(easy级别)
起因: 最近在练习解析验证码,看到了这个网站的验证码比较简单,于是就拿来解析一下攒攒经验值,并无任何冒犯之意... 验证码所在网页: https://www.w3cschool.cn/checkmph ...
- Tensorflow实战(二):Discuz验证码识别
一.前言 验证码是根据随机字符生成一幅图片,然后在图片中加入干扰象素,用户必须手动填入,防止有人利用机器人自动批量注册.灌水.发垃圾广告等等 . 验证码的作用是验证用户是真人还是机器人. 本文将使用深 ...
- captcha_trainer 验证码识别-训练 使用记录
captcha_trainer 验证码识别-训练 使用记录 在爬数据的时候,网站出现了验证码,那么我们就得去识别验证码了.目前有两种方案 接入打码平台(花钱,慢) 自己训练(费时,需要GPU环境,快) ...
- 字符型图片验证码识别完整过程及Python实现
字符型图片验证码识别完整过程及Python实现 1 摘要 验证码是目前互联网上非常常见也是非常重要的一个事物,充当着很多系统的 防火墙 功能,但是随时OCR技术的发展,验证码暴露出来的安全问题也越 ...
- 验证码识别<1>
1. 引子 前两天访问学校自助服务器()缴纳网费,登录时发现这系统的验证码也太过“清晰”了,突然脑袋里就蹦出一个想法:如果能够自动识别验证码,然后采用暴力破解的方式,那么密码不是可以轻易被破解吗? p ...
- 简单的验证码识别(opecv)
opencv版本: 3.0.0 处理验证码: 纯数字验证码 (颜色不同,有噪音,和带有较多的划痕) 测试时间 : 一天+一晚 效果: 比较挫,可能是由于测试的图片是在太小了的缘故. 原理: 验证码 ...
- 利用开源程序(ImageMagick+tesseract-ocr)实现图像验证码识别
--------------------------------------------------低调的分割线-------------------------------------------- ...
- 基于LeNet网络的中文验证码识别
基于LeNet网络的中文验证码识别 由于公司需要进行了中文验证码的图片识别开发,最近一段时间刚忙完上线,好不容易闲下来就继上篇<基于Windows10 x64+visual Studio2013 ...
- Java验证码识别解决方案
建库,去重,切割,识别. package edu.fzu.ir.test; import java.awt.Color; import java.awt.image.BufferedImage; im ...
- 简单验证码识别(matlab)
简单验证码识别(matlab) 验证码识别, matlab 昨天晚上一个朋友给我发了一些验证码的图片,希望能有一个自动识别的程序. 1474529971027.jpg 我看了看这些样本,发现都是很规则 ...
随机推荐
- spring native 初体验实现 小米控制美的空调
目前关于 spring native 分享的文章还比较少 写这篇文章的主要目前是分享一下自己写的一个 小米控制美的空调 的程序 集成 spring native 过程中碰到的一些问题和解决方法 先放地 ...
- js中new的原理
面向对象 在了解new的原理之前,先简单地了解一下构造函数和对象. js可以通过构造函数创建对象: function Test() { } var t = new Test(); 构造函数的首字母大写 ...
- Docker 数据共享与持久化
- 一文搞懂容器运行时 Containerd
文章转载自:https://www.qikqiak.com/post/containerd-usage/ 在学习 Containerd 之前我们有必要对 Docker 的发展历史做一个简单的回顾,因为 ...
- 天翼云主机某一IP多次登录失败导致IP被锁无法登录,天翼云主机莫名其妙无法远程登陆
情况说明: 直接使用该IP通过ssh远程连接失败,但是先通过ssh远程连接其他主机上,然后在这个主机上再ssh刚才连接失败的主机,就能登陆上. 说明,root用户不是被锁了, 而是远程登陆IP被锁了 ...
- P7476 苦涩 题解
Link 一道很好的复杂度均摊题目. 只需要考虑删除操作时的时间复杂度.保证复杂度的重点之一是精确定位到所有包含最大值的区间,即不去碰多余的区间.每次删除操作会删除若干个整个区间,以及至多两个区间被删 ...
- LeetCode - 数组的旋转总结
1. 数组的旋转总结 数组的旋转指的是将数组的最后若干个数提前到数组前面,数组的翻转指的是将数组的顺序颠倒.旋转可以通过多次翻转实现. 数组的翻转很简单,通过双指针来实现:交换数组的第一个数和最后一个 ...
- 洛谷P5788 单调栈(模板)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=3e6+10; 4 int n,a[N],s[N],ans[N ...
- P1706 全排列问题 方法记录
原题链接 全排列问题 题目描述 按照字典序输出自然数 \(1\) 到 \(n\) 所有不重复的排列,即 \(n\) 的全排列,要求所产生的任一数字序列中不允许出现重复的数字. 输入格式 一个整数 \( ...
- IPv4 与 IPv6的区别
在介绍 IPv4 到 IPv6 区别之前,我们先来简单了解一下 IPv4 和 IPv6. IPv4 网际协议版本4(英语:Internet Protocol version 4,IPv4),又称互联网 ...