[验证码识别]点选验证码识别的通用解决方案-相似度识别

查看 163|回复 10
作者:s1lencee   
[验证码识别]点选验证码识别的通用解决方案-相似度识别

本文章所有内容仅供学习和研究使用,本人不提供具体模型和源码。若有侵权,请联系我立即删除!维护网络安全,人人有责。

前言
最近发了个关于机器学习的教程,没想到大家都挺感兴趣的。


1.png (653.53 KB, 下载次数: 0)
下载附件
1
2024-2-5 21:33 上传

点选验证码是比较常见的验证码之一,主要有3种:
  • 文字点选验证
  • 图标点选验证
  • 语序点选验证

    其中语序点选是难度最大的。
    以下是一些主流验证码


    2.png (2.84 MB, 下载次数: 0)
    下载附件
    2
    2024-2-5 21:34 上传

    我看了网上关于这类验证码的教程有些过时了或讲解比较模糊,便打算自己出文章
    本文章我将以某美文字点选验证码为例子介绍如何识别点选验证码
    目录

  • 准备工作

  • 验证码图片下载

  • 神经网络选择

  • 注意事项

  • 数据集构建

  • 图片标注

  • 注意事项

  • yolov5目标检测

  • 训练模型

  • 相似度模型构建

  • 数据集

  • 代码准备

  • 模型训练

  • 导出onnx

  • 模型评估

  • 验证图片识别

  • 两种模型结合

  • 示例代码

  • 其他验证类型

  • 语序识别

  • 语音验证码

  • 手势验证码

  • 九宫格验证码

  • 总结和注意事项

    准备工作
    验证码图片下载
    老样子,我们将下载好的图片按照内容计算MD5值,保存到本地。(当然也可以不用怎么干)


    3.png (95.89 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    因为该验证码图片干扰较少,所以目标检测不需要太多的数据集,我认为大概200张到400张就够了。
    神经网络选择
    目前主流的文字点选识别大部分使用2种方式:
  • yolo目标检测 + siamese网络计算相似度
  • yolo目标检测 + crnn识别具体文字

    文字和图标我推荐第一种,而语序只能使用第二种,
    因为crnn需要的数据集巨大,并且训练周期长,所有本文我将使用第一种方式来识别图片
    注意事项
  • 某验4代的参考文字和图标图片是带有透明背景的图片,在训练相似模型时会有较大影响,所以我们可以将该图片的透明背景转换为白色,这样在预测相似度时就比较准确了。

    数据集构建
    图片标注
    我们只需要标注一种类型,即图片中的文字即可,不需要其他标签。


    4.png (657.74 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    图片标注我在之前的文章已经讲过了,在这里我不做过多赘述。
    注意事项

  • 某验3代参考文字和图标是和目标文字是在一张图片上的,这时我们需要把两种都标记上,即参考文字一种类型,目标文字一种类型。

  • 某盾的图标也在图片上,但是坐标是固定的,裁剪下来即可。(极小概率会出现4种图标)



    5.png (901.14 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    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写了一个界面来进行人工分类


    6.png (156.86 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传



    7.png (400.23 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    最后整理一下分类好的图片


    8.png (55.78 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    目录结构如下
    train
       丁
         丁_672.png
         丁_673.png
         .....
       七
         七_7408.png
         七_7409.png
         .....
    val
       丁
         丁_672.png
         丁_673.png
         .....
       七
         七_7408.png
         七_7409.png
         .....

    验证集和数据集比较按照你的爱好给即可, 用的是1:9

    代码准备
    相关存储库:
    [ol]
  • https://github.com/bubbliiiing/Siamese-pytorch
  • https://github.com/2833844911/dianxuan (这位作者代码写的不是很好,但是能用)
    [/ol]
    其中链接1是大家用得比较多的,但是该项目并不适应于我们。
    查看代码会发现


    9.png (118.77 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    它会从每种类型的数据集中取出若干图片。
    而我们数据集有些这类最少只要2张,如果使用该存储库,代码会找不到更多图片,就会进入死循环。

    当然,你也可以使用数据增强来增加数据集,为了方便我就不演示了。

    我们将第二个存储库克隆下来,并按照里面的教程训练即可。

    这位作者把test写成了text

    刚开始, 模型会下载预训练模型vgg16-397923af.pth


    10.png (10.14 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    但是这个下载速度实在太慢了, 我们可以直接打开浏览器输入以下链接下载
    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即可
    识别结果


    11.png (261.46 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传



    12.png (239.96 KB, 下载次数: 0)
    下载附件
    2024-2-5 21:34 上传

    示例代码
    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[0] + p[2]) / 2), int((p[1] + p[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%左右
    其他验证类型
    语序识别
    语序验证码识别流程一般如下:
    [ol]
  • 目标检测
  • 文字识别
  • 语序还原
    [/ol]
    前面两步较为简单, 第三步较为困难, 网上已经有教程使用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大模型的识别结果


    13.png (2.42 KB, 下载次数: 0)
    下载附件
    13
    2024-2-5 21:34 上传

    语音验证码
    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}")


    14.png (41.41 KB, 下载次数: 0)
    下载附件
    14
    2024-2-5 21:34 上传

    可以看到识别结果还是可以的
    手势验证码
    目标检测 + 回归模型(待更新)
    九宫格验证码
    九宫格可以使用相似度模型或分类模型(待更新)
    总结和其他
    其他
    [ol]
  • 如果大家显卡比较好的话建议安装torch的GPU版本, 一般情况下训练模型GPU速度比GPU快很多
  • 关于为什么使用onnx, onnx可以跨平台和跨设备使用, 并且在CPU上推理速度上比较快, 便于在服务器使用, 并且不像安装torch那样繁琐
  • 其他语言可以参考思路分析
    [/ol]
    总结
    验证码与打码的攻防对抗是一场持久战,随着技术的发展,双方不断演进和创新。
    在黑灰产盛行的年代,人工智能将扮演着重要的角色,对于攻方和防守方都具有深远的影响。
    从最开始的单纯字母验证码到现在的智能无感验证, 验证码技术经历了多个阶段的演进。
    我们需要了解验证码的破解才能更好地防御, 知己知彼百战百胜, 维护网络安全人人有责。
    最后, 我就是一个小白, 欢迎大佬们指教。

    验证码, 模型

  • 落克   

    AI太难了


    image.png (1.39 MB, 下载次数: 0)
    下载附件
    2024-2-6 21:11 上传

    一边GPT一边学知识,到最后准确率为0。。。
    结果你那边200张久解决了。。。
    能给个方向不。。。我用的是(GPT说)CNN做预测。。。
    有点迷茫
    wangguang   

    很强哦,3060可不可以
    Learn3days   

    能录个视频教程就更好了
    dircou   

    学到了感谢大佬
    zhang120300   

    学到了感谢大佬
    ameiz   

    请问 google的验证码 怎么破?
    Shadowshaw   

    学到了感谢大佬,能做个浏览器插件么。
    cp8086   

    学习到了,这样比二次验证还多一个,可以避免被暴力
    xiawan   

    这个比较好,学习了。
    您需要登录后才可以回帖 登录 | 立即注册