[验证码识别]点选验证码识别的通用解决方案-相似度识别
本文章所有内容仅供学习和研究使用,本人不提供具体模型和源码。若有侵权,请联系我立即删除!维护网络安全,人人有责。
前言
最近发了个关于机器学习的教程,没想到大家都挺感兴趣的。
点选验证码是比较常见的验证码之一,主要有3种:
其中语序点选是难度最大的。
以下是一些主流验证码
我看了网上关于这类验证码的教程有些过时了或讲解比较模糊,便打算自己出文章
本文章我将以某美文字点选验证码为例子介绍如何识别点选验证码
目录
-
准备工作
-
数据集构建
-
yolov5目标检测
-
相似度模型构建
-
数据集
-
代码准备
-
模型训练
-
导出onnx
-
模型评估
-
验证图片识别
-
其他验证类型
-
总结和注意事项
准备工作
验证码图片下载
老样子,我们将下载好的图片按照内容计算MD5值,保存到本地。(当然也可以不用怎么干)
因为该验证码图片干扰较少,所以目标检测不需要太多的数据集,我认为大概200张到400张就够了。
神经网络选择
目前主流的文字点选识别大部分使用2种方式:
- yolo目标检测 + siamese网络计算相似度
- yolo目标检测 + crnn识别具体文字
文字和图标我推荐第一种,而语序只能使用第二种,
因为crnn需要的数据集巨大,并且训练周期长,所有本文我将使用第一种方式来识别图片
注意事项
- 某验4代的参考文字和图标图片是带有透明背景的图片,在训练相似模型时会有较大影响,所以我们可以将该图片的透明背景转换为白色,这样在预测相似度时就比较准确了。
数据集构建
图片标注
我们只需要标注一种类型,即图片中的文字即可,不需要其他标签。
图片标注我在之前的文章已经讲过了,在这里我不做过多赘述。
注意事项
yolov5目标检测
训练模型
yolo训练我已经在之前的文章讲过了,有兴趣可以去看看
https://www.52pojie.cn/thread-1886001-1-1.html
相似度模型构建
介绍
这里我们使用以VGG16为主干的Siamese神经网络来训练相似度模型
https://blog.csdn.net/weixin_44791964/article/details/107343394
我们要实现的功能是: 将两张图片处理后输入网络,网络会输出0-1之间的值。该值就是这两张图片的相似度。
数据集
按照你喜欢的方式将所有经过目标检测并切割下来后的图片进行分类
因为相似度模型不能输入文字,我们需要将文字转换为图片
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
def text_to_image(text, text_color='black', bg_color='white'):
"""文字转图片"""
img = Image.new('RGB', (100, 100), color=bg_color)
img_draw = ImageDraw.Draw(img)
width, height = img.size
font = ImageFont.truetype('msyh.ttc', size=75)
w = font.getlength(text)
img_draw.text(((width - w) / 2, 0), text, fill=text_color, font=font)
buf = BytesIO()
img.save(buf, format="JPEG")
return buf.getvalue()
这里我用pyqt5写了一个界面来进行人工分类
最后整理一下分类好的图片
目录结构如下
train
丁
丁_672.png
丁_673.png
.....
七
七_7408.png
七_7409.png
.....
val
丁
丁_672.png
丁_673.png
.....
七
七_7408.png
七_7409.png
.....
验证集和数据集比较按照你的爱好给即可, 用的是1:9
代码准备
相关存储库:
- https://github.com/bubbliiiing/Siamese-pytorch
- https://github.com/2833844911/dianxuan (这位作者代码写的不是很好,但是能用)
其中链接1是大家用得比较多的,但是该项目并不适应于我们。
查看代码会发现
它会从每种类型的数据集中取出若干图片。
而我们数据集有些这类最少只要2张,如果使用该存储库,代码会找不到更多图片,就会进入死循环。
当然,你也可以使用数据增强来增加数据集,为了方便我就不演示了。
我们将第二个存储库克隆下来,并按照里面的教程训练即可。
这位作者把test写成了text
刚开始, 模型会下载预训练模型vgg16-397923af.pth
但是这个下载速度实在太慢了, 我们可以直接打开浏览器输入以下链接下载
https://download.pytorch.org/models/vgg16-397923af.pth
然后在资源管理器打开%USERPROFILE%\.cache\torch\hub\checkpoints
, 将下载好的模型复制进去即可, 如果提示找不到文件夹就按照提示创建
模型训练
输入以下命令开始训练
python train.py
等待训练结束
其中loss代表模型的损失, acc代表正确率
导出onnx
在该存储库同一文件夹创建export.py
import torch
from text import Siamese
out_onnx = 'model.onnx'
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
dummy = (torch.randn(1, 3, 105, 105).to(device), torch.randn(1, 3, 105, 105).to(device))
model = torch.load('你的模型路径')
model.eval()
model = model.to(device)
torch_out = torch.onnx.export(model, dummy, out_onnx,input_names=["x1", "x2"])
print("finish!")
模型评估
创建predict.py
import cv2
import onnx
import onnxruntime
import numpy as np
# 导出的ONNX模型的路径
onnx_model_path = 'model.onnx'
# 加载ONNX模型
onnx_model = onnx.load(onnx_model_path)
# 创建ONNX Runtime推理会话
ort_session = onnxruntime.InferenceSession(onnx_model_path)
image1 = cv2.imread(r'1.jpg')
image2 = cv2.imread(r'2.jpg')
image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB)
# 将图片转换为numpy数组
input1 = cv2.resize(image1, (105, 105)).astype(np.float32) / 255
input2 = cv2.resize(image2, (105, 105)).astype(np.float32) / 255
# 确保输入的形状是 (C, H, W)
input1 = np.transpose(input1, (2, 0, 1))
input2 = np.transpose(input2, (2, 0, 1))
# 添加 batch 维度
input1 = np.expand_dims(input1, axis=0)
input2 = np.expand_dims(input2, axis=0)
outputs = ort_session.run(None, {'x1': input1, 'x2': input2})
# 输出
print(outputs[0][0][0])
运行后会输出
0.29165006
该值就是相似度了
验证图片识别
两种模型结合
同样的, 先使用目标检测并切割出目标图片, 然后将文字转换为图片, 再使用相似度模型预测每2张图片之间的相似度
这样会得到一个矩阵
[
[0.1, 0.2, 0.8, 0.5],
[0.9, 0.3, 0.5, 0.1],
[0.5, 0.1, 0.1, 0.8],
[0.2, 0.7, 0.1, 0.3],
]
然后我们取最大值的索引, 将该索引的行和列设为0, 依次类推
[
[0.1, 0.2, 0.8, 0.5],
[0.9, 0.3, 0.5, 0.1],
[0.5, 0.1, 0.1, 0.8],
[0.2, 0.7, 0.1, 0.3],
]
# 最大值0.9, 索引0, 1
# 将该索引的行和列设为0, 处理后
[
[0. , 0.2, 0.8, 0.5],
[0. , 0. , 0. , 0. ],
[0. , 0.1, 0.1, 0.8],
[0. , 0.7, 0.1, 0.3],
]
直到该二维矩阵全部为0即可
识别结果
示例代码
from PIL import Image, ImageDraw, ImageFont
from matplotlib import pyplot as plt
from io import BytesIO
import numpy as np
def get_position(bg_img_data, words):
"""获取点选坐标"""
word_img_list = [text_to_image(word) for word in words]
# 目标位置识别, 返回左上角和右下角坐标
result = detection(bg_img_data)
draw(bg_img_data, result)
img = Image.open(BytesIO(bg_img_data))
# 相似度矩阵
siamese_matrix = []
plt.figure()
i = 1
for j, word_img in enumerate(word_img_list):
icon_img = Image.open(BytesIO(word_img))
plt.subplot(len(word_img_list), len(result) + 1, i)
plt.imshow(icon_img.resize((50, 50))), plt.title(f'word-{j}'), plt.axis('off')
row = []
i += 1
for box in result:
crop_img = img.crop(box).convert("L")
# siamese_pre为相似度识别, 返回一个浮点数
s = siamese_pre(icon_img, crop_img)
plt.subplot(len(word_img_list), len(result) + 1, i)
plt.imshow(crop_img.resize((50, 50))), plt.title(f'{s:.4f}'), plt.axis('off')
row.append(s)
i += 1
siamese_matrix.append(row)
plt.show()
siamese_matrix = np.array(siamese_matrix)
p = []
for i in range(siamese_matrix.shape[0]):
max_index = np.argmax(siamese_matrix[i, :])
update_matrix(siamese_matrix, (i, max_index))
p.append(result[max_index])
points = [[int((p[i][0] + p[i][2]) / 2), int((p[i][1] + p[i][3]) / 2)] for i in range(len(p))]
return points
def update_matrix(matrix, index):
"""将最大值所在的行和列置为零"""
matrix[index[0], :] = 0 # 将行置为零
matrix[:, index[1]] = 0 # 将列置为零
return matrix
def draw(img_path, data):
"""绘制识别结果"""
image_ = Image.open(BytesIO(img_path))
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签SimHei
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.imshow(image_, interpolation='none')
current_axis = plt.gca()
for i, box in enumerate(data):
if len(box) == 2:
x, y = box
box_w = 40
box_h = 40
box_ = (x - 20, y - 20)
else:
x, y, x2, y2 = box
box_w = x2 - x
box_h = y2 - y
box_ = (x, y)
current_axis.add_patch(
plt.Rectangle(box_, box_w, box_h, color='blue', fill=False, linewidth=2))
plt.text(
box_[0],
box_[1],
s=f"{i}",
color="white",
verticalalignment="top",
bbox={"color": "black", "pad": 0},
)
plt.show()
def text_to_image(text, text_color='black', bg_color='white'):
img = Image.new('RGB', (100, 100), color=bg_color)
img_draw = ImageDraw.Draw(img)
width, height = img.size
font = ImageFont.truetype('msyh.ttc', size=75)
w = font.getlength(text)
img_draw.text(((width - w) / 2, 0), text, fill=text_color, font=font)
buf = BytesIO()
img.save(buf, format="JPEG")
return buf.getvalue()
最后, 携带坐标去验证, 最后正确率85%左右
其他验证类型
语序识别
语序验证码识别流程一般如下:
- 目标检测
- 文字识别
- 语序还原
前面两步较为简单, 第三步较为困难, 网上已经有教程使用jieba分词来还原语序, 但是效果不是很理想
实际上, 语序还原已经有了解决方案
https://github.com/shibing624/pycorrector
这是一款开源的中文纠错工具, 并提供了多种模型
Kenlm统计语言模型不仅能纠正拼写错误, 也能用于语序还原
开发者提供了小中大三种模型, 其中大模型达到了3GB, 但是语序还原效果非常好
当然也可以自己使用数据集训练
import os
import kenlm
from itertools import permutations
class WordOrder(object):
def __init__(self, model_path):
self.model = kenlm.LanguageModel(model_path)
def n_gram(self, word):
word_list = list(word)
# n-gram
candidate_list = list(permutations(word_list, r=len(word_list)))
a_c_s = -100
a_c = ""
b_c_s = 1000
for candidate in candidate_list:
candidate = ' '.join(candidate)
a = self.model.score(candidate)
b = self.model.perplexity(candidate)
if a > a_c_s:
a_c = candidate
a_c_s = a
if b_c_s > b:
b_c_s = b
return a_c.replace(" ", '')
def predict(self, text):
return self.n_gram(text)
if __name__ == "__main__":
# 模型路径
model_dir = os.path.join(os.path.dirname(__file__), 'models')
language_model_path = os.path.join(model_dir, '语言模型路径')
char_order = WordOrder(language_model_path)
word = "等烟天雨色青"
order = char_order.predict(word)
print(word + " => " + order)
使用zh_giga.no_cna_cmn.prune01244.klm
大模型的识别结果
语音验证码
OpenAI已经开放了免费的语言识别模型whisper
, 虽然速度有些慢, 但是在没有干扰的情况下识别正确率还可以
https://github.com/openai/whisper
安装whisper
pip install whisper
识别代码
import whisper
# 可以选base, small等多种模型, 具体查看官方文档
model = whisper.load_model("small", download_root="models")
voice_path = "xxx.mp3"
result = model.transcribe(voice_path)
text = result["text"]
print(f"{voice_path} => {text}")
可以看到识别结果还是可以的
手势验证码
目标检测 + 回归模型(待更新)
九宫格验证码
九宫格可以使用相似度模型或分类模型(待更新)
总结和其他
其他
- 如果大家显卡比较好的话建议安装torch的GPU版本, 一般情况下训练模型GPU速度比GPU快很多
- 关于为什么使用onnx, onnx可以跨平台和跨设备使用, 并且在CPU上推理速度上比较快, 便于在服务器使用, 并且不像安装torch那样繁琐
- 其他语言可以参考思路分析
总结
验证码与打码的攻防对抗是一场持久战,随着技术的发展,双方不断演进和创新。
在黑灰产盛行的年代,人工智能将扮演着重要的角色,对于攻方和防守方都具有深远的影响。
从最开始的单纯字母验证码到现在的智能无感验证, 验证码技术经历了多个阶段的演进。
我们需要了解验证码的破解才能更好地防御, 知己知彼百战百胜, 维护网络安全人人有责。
最后, 我就是一个小白, 欢迎大佬们指教。