前言
公司项目的系统登录有一套验证码系统,之前想写一些自动化测试时总是会被这个验证码卡住,不能完全自动运行。去找开发同事关一下验证码,也是一开一关挺麻烦的,不能总麻烦人家。秉承着工作是自己的,麻烦到头来总要自己解决的原则,开始找方案。
第一个是发现可以把验证码图片给AI去解析,得到的答案正确率很高,就是不能调用太多,毕竟token是要花钱的,虽然量不大,但便宜归便宜,付费上班总归不是办法。然后又在网上找到了用OCR的方案,商用OCR当然也是要付费的,好在我们有伟大的开源方案,大不了自己训练嘛,这么简单的验证码,应该还挺好识别的吧...
主角
最后选择的就是开源的Tesseract-OCR,目前的版本是5.4.1,需要自己编译。好在也有懒人解决方案,在用户手册里可以找到第三方提供的编译好的安装包,提供了三个常用的系统,包括Windows。从链接进去就可以找到下载链接,不过Windows安装包提供的版本是5.4.0,并不是最新的...也不差多少是了。
下载安装,完成后会提供十数个命令行工具,其中用来做识别的是tesseract,其它的都不会用XD。
安装包只包含一个eng的训练数据(也就是模型),在安装位置的tessdata文件夹中,其它的模型需要到库中自行下载,就像它自己的介绍,提供了100+种语言。不过验证码是这样的,只需要数字和几个运算符,用英语也足够了,总之先尝试一下吧!
tesseract imageFile outputFile
# outputFile 使用 - 可以直接把结果打印在窗口中
# 使用 -l 选项指定使用的模型,只需要输入名称
很可惜,效果并不好,看来,想用的话只能自己训练了。
开始训练
训练是个很麻烦的事情,尤其是对我这种门外汉来说,在做的时候主要参考了这篇博客和官方用户手册,以及官方提供的一个简化训练步骤的脚本库。
1 准备训练图片
验证码的图片直接从登录页面拿就可以了,不过训练的话,即使构成简单还是要用几百张的,最好是用接口去获取图片,又因为训练脚本只支持TIFF和PNG类型的图片,其它类型的图片需要在保存时转换格式,还是要写个脚本来处理这个事情的。
import base64
from requests import get
for i in range(1, 101):
r = get("Url") # 验证码获取链接
if r.ok:
j = r.json()
s = j["data"]["code"] # 解析到图片数据,没有做验证,有问题的话就报错了
p = base64.b64decode(s) # base64数据需要解码
with open(f"./trainImage/{i}.png", "wb+") as f: # 创建文件
f.write(p)
print(f'write complete.{i}.png')
else: # 请求出错时调试用的
print(r.text)
break
这时,我遇到的第一个问题出现了,倒不是获取图片遇到问题,而是每个训练图片都是需要答案的,答案要保存在和图片同名的后缀为.gt.txt文件中,比如图片文件名是1.png,那么答案文件就是1.gt.txt,文件的内容只包含图片中的文本答案,比如上面那张图,它的答案应该写
6x4=?
不需要换行,但是应该包含图片中的所有目标字符。
注意到这个事情时,我已经保存了一百张图片,不多,但是手动创建文件还是很麻烦的,还要打开图片,还有写入文件。所以,这种事还是交给脚本来做吧XD~
我的思路是写一个UI程序,按顺序加载图片,再准备一个输入框,看到图片后我把答案输进去,再由脚本把答案写到对应文件里。非常的清晰XD。下面是我用的脚本
import logging
import tkinter as tk
class App(tk.Frame):
def __init__(self, master: tk.Tk) -> None:
super().__init__(master)
self.master = master
self.image = read_image()
self.create_widget()
def create_widget(self):
self.label = tk.Label(self.master, image=self.image)
self.label.grid(column=0,row=0)
self.input = tk.Entry(self.master, width=6 ,font=('Arial', 14))
self.input.bind('<Return>', self.complete)
self.input.grid(column=0,row=1)
self.label1 = tk.Label(self.master, text="=?", font=('Arial', 14))
self.label1.grid(row=1,column=1)
self.button2 = tk.Button(self.master, text="刷新", command= self.refresh_img)
self.button2.grid(row=0, column=1)
self.button = tk.Button(self.master, text="写入", command= self.complete)
self.button.grid(column=2,row=1)
def complete(self, event=''):
content = self.input.get()
if len(content) == 0 :
logging.warning('未输入数据')
return
save_file(content)
global I_NUM
I_NUM += 1
self.refresh_img()
self.input.delete(0, tk.END)
def refresh_img(self):
img = read_image()
if img:
self.image = img
self.label.configure(image= self.image)
def save_file(content):
file_name = f"{I_NUM}.gt.txt"
with open(image_dir + file_name, 'w+') as f:
i = f.write(content + '=?') # 我这个验证码最后两个都是'=?' 所以偷个懒XD
logging.info(f'成功向文件{file_name}写入{i}个字节') # 会打印在命令行里,UI里没写显示提示信息的功能
return i
def read_image():
image_name = f"{I_NUM}.png"
image = image_dir + image_name
return tk.PhotoImage(name= image_name, file= image)
# 文件不存在的话会报错,错误信息都会打印在命令行里,UI里没写显示提示信息的功能
I_NUM = 1 # 其实是图片的名称,这样我可以比较简单的控制从哪张图片开始
image_dir = "D:\\tesseract\\trainImage\\" # 包含图片的文件夹,答案文件也保存在这里了
gui = tk.Tk()
gui.geometry('400x250') # 设置了UI大小,不然太小我总是找不到
app = App(gui)
app.mainloop()
这样就可以只输入验证码来创建答案文件了XD
已经保存的图片创建完了,对新获取图片的话,只需要把上面两个脚本合并起来,再稍微修改一下read_image就好了。
# 省略其它代码
def read_image():
image_name = f"{I_NUM}.png"
image = image_dir + image_name
if get_image(image): # 获取失败的时候点【刷新】就能重新获取了
return tk.PhotoImage(name= image_name, file= image)
def get_image(image):
r = get("Url")
if r.ok:
j = r.json()
s = j["data"]["code"] # 解析到图片数据,没有做验证,有问题的话就报错了
p = base64.b64decode(s) # base64数据需要解码
with open(f"./trainImage/{I_NUM}.png", "wb+") as f: # 创建文件
f.write(p)
logging.debug(f'write complete.{I_NUM}.png')
return True
else:
logging.error("获取图片失败")
return False
把读取本地文件改成先获取再读就好了,缺少的依赖请自行添加。
这样手动准备个两三百张图片就差不多了。
2 尝试使用训练脚本进行训练
把训练脚本的库下载下来,然后按使用说明把环境配置好(Windows):
- 把Tesseract的目录添加到环境变量
- 安装Python3和依赖(在requirements.txt里)
- 需要一些Linux工具,有安装Git的话,直接用Git Bash就行
- 安装make(文档里是用winget安装的)
训练脚本的说明文档很清晰,如果能认真看的话,我就能少踩俩坑了:(
在训练前,在下载好的脚本文件夹里,先在data文件夹中新建一个MODEL_NAME-ground-truth的文件夹,再把训练用的图片数据复制到这个文件夹里。MODEL_NAME是训练出的模型的名称,只要不重复就行。然后在Git Bash里打开脚本文件夹。
脚本的入口是MakeFile,最简单的训练命令是
make training
但是只下载脚本库是运行不了。脚本里提供了一系列的变量,全部都有默认值。MODEL_NAME就是一个,指模型名称,现在只需要把这个变量填进去就能运行了;D
make training MODEL_NAME=模型名称
虽然运行起来了,但是这么多变量,显然不可能都用默认就能训练出合适的模型(比如我,这样直接训练出来的模型,正确率不到个位数,当然如果训练出来就能用那算你运气好)。这些变量中,NET_SPEC是比较重要的一个,包含了初始的训练参数。而其它变量大多数都是输出位置,关联文件位置等,即使使用默认,也不会影响训练效果。
不过,在调整参数前,还有一件事需要做。
3 图片处理
处理图片消耗了我不少的时间,毕竟这个东西我也要现找方案XD
其实在Tesseract的使用文档中,图片的处理也是放在前面的,就是提高质量。在文档中,它解释了清晰的图片才有准确的识别结果,并提供了一些处理图片的方法和工具。这些方法同样可以用来处理训练图片,而处理的目标,就是尽可能消除噪音、去掉背景、或是让图片更符合识别引擎,在这个过程中我参考了许多教程,比如这篇。
图片处理起来很费时,要选择方法,不断调整参数。调试时总是用一张图,然而一个方案在这张图上合适时,对其它图片也可能不尽人意。我做了三四个版本,每个版本的训练效果都没什么明显的提示。大概是我比较菜吧:(
最后我选择的处理方案就是不处理,除了把图片的宽高缩放到了原本的一半。原图片高是48像素,缩小后就是24像素。会这么做的原因是:之前的模型识别后,总是会多一些字符,所以我想,是不是图比较大,所以噪点部分容易被识别,如果把图片缩小一点儿,会不会就不会识别噪点了。
很奇怪的想法,但是实验一下很简单,所以就这样做了。把图片缩放后再去识别,同一张图片,真的就比原图识别结果贴近很多,至少没有了多很多字符。既然如此,把图片都处理好,接下来大概可以开始训练了?
4 调整训练参数
看来还不能开始训练,还有一个重要参数没有调整,就是NET_SPEC
标签:训练,模型,self,Tesserast,验证码,OCR,image,图片 From: https://www.cnblogs.com/soon003/p/18502860