吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1511|回复: 19
收起左侧

[Web逆向] 文字点选及语序点选验证码识别方案

  [复制链接]
1JEY 发表于 2026-3-26 15:29
本帖最后由 1JEY 于 2026-3-26 15:44 编辑

【Web逆向】 文字点选及语序点选验证码识别方案

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

前言

目标网址: aHR0cHM6Ly94d3F5LmdzeHQuZ292LmNuLw==
该网站验证码是混合类型的,触发验证会随机弹出以下类型的验证码:
九宫格点选,图标点选,文字点选,语序文字点选
本文主要围绕文字点选及语序点选的识别方案

叠甲:小白第一次发帖,文中代码大部分由AI辅助完成,有不足之处,还请各位大佬指点

思路:

因为两种都是文字点选验证码,只是验证方式不一样,所以决定采用同一种方式来做文字识别, 
后续再采用不同方案来作验证

文字识别方案:yolo目标检测+yolo分类识别

准备工作

验证码图片下载

请求图片接口,将图片保存在本地电脑上,因为后续要做yolo分类识别,所以需要非常多的数据集,我这边大概采了10000张左右

img.png

yolo目标检测

数据集准备(图片标注)

推荐数据标注网站:https://roboflow.com/ (需科学上网)
非常方便的在线数据标注网站,标注完后可一键导出yolo格式的数据集
网站使用步骤参考文章:https://blog.csdn.net/weixin_43466192/article/details/154242167

因为这里只做目标检测,不做文字识别,所以我们从下载好的图片拿出50-60张图片标注即可

img_1.png

标注好的图片,按训练集:验证集:测试集=7:2:1分,导出数据集即可

img_2.png

yolo预训练模型下载,直接去官网下载 https://docs.ultralytics.com/zh/
我这边下载的是yolo11n模型

img_3.png

模型训练

yolo训练及识别前的准备工作,例如python环境依赖等就不过多赘述了,网上有很多资料,自行查阅
# 训练代码
from ultralytics import YOLO

def train_yolo():
    model = YOLO("yolo11n.pt") 

    # 开始训练
    model.train(
        data="path/to/your_data.yaml", # 数据集配置文件路径
        epochs=100,                   # 训练轮数
        imgsz=640,                    # 输入图像尺寸
        batch=16,                     # 批次大小 (也可设为 -1 自动调整)
        device=0,                     # 使用 GPU (0, 1...) 或 "cpu"
        workers=8,                    # 数据加载线程数
    )
    model.export(format="onnx")

if __name__ == "__main__":
    train_yolo()

yolo分类识别

数据集准备

现在我们要利用刚训练好的检测模型来识别之前的10000张验证码图片,将识别出目标(单个中文字)分割出来,都放到同一个文件夹里
# 验证码文字分割
import os
import cv2
import numpy as np
import onnxruntime as ort

# ================= 配置修改 =================
MODEL_PATH = "./best.onnx"
INPUT_DIR = "img/" #输入文件夹
OUTPUT_DIR = "targets/" #输出文件夹
IMG_SIZE = (320, 224)  # 必须与模型训练时的 input shape 一致
CONF_THRES = 0.25
IOU_THRES = 0.45
SAVE_SIZE = (64, 64)
# ===========================================

if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR)

session = ort.InferenceSession(MODEL_PATH, providers=['CPUExecutionProvider'])
input_name = session.get_inputs()[0].name

def process_image(img_path):
    img_raw = cv2.imread(img_path)
    if img_raw is None: return

    # 1. 预处理
    # BGR -> RGB (重要!YOLO通常需要RGB)
    img = cv2.cvtColor(img_raw, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, IMG_SIZE)

    # HWC -> CHW, 归一化, 添加 Batch 维度
    img = img.transpose(2, 0, 1)
    img = np.expand_dims(img, axis=0).astype(np.float32) / 255.0

    # 2. 推理
    outputs = session.run(None, {input_name: img})[0]

    # 兼容处理: (1, 85, N) -> (1, N, 85)
    if outputs.shape[1] < outputs.shape[2]:
        outputs = outputs.transpose(0, 2, 1)
    preds = outputs[0]

    # 3. 筛选与 NMS
    boxes, scores, class_ids = [], [], []

    # 假设格式: [x, y, w, h, obj_conf, cls_conf...]
    # 如果是只有1个类别的验证码,通常取第4列(obj)或第5列(cls)为置信度
    for det in preds:
        # 这里做了简化:取第4个值作为置信度(针对不同YOLO版本可能需要调整为 det[4] * det[5:] 的最大值)
        score = det[4]
        if score > CONF_THRES:
            # 还原坐标到原图
            h_raw, w_raw = img_raw.shape[:2]
            x, y, w, h = det[0:4]

            # 缩放比例
            scale_x, scale_y = w_raw / IMG_SIZE[0], h_raw / IMG_SIZE[1]

            # 转为左上角坐标 (x, y, w, h)
            x = (x - w / 2) * scale_x
            y = (y - h / 2) * scale_y
            w *= scale_x
            h *= scale_y

            boxes.append([int(x), int(y), int(w), int(h)])
            scores.append(float(score))

    # NMS 去重
    indices = cv2.dnn.NMSBoxes(boxes, scores, CONF_THRES, IOU_THRES)

    # 保存前3个目标
    if len(indices) > 0:
        indices = indices.flatten()[:3]  # 取前3个
        file_base = os.path.basename(img_path).split('.')[0]

        for i, idx in enumerate(indices):
            x, y, w, h = boxes[idx]
            # 边界保护
            x, y = max(0, x), max(0, y)

            if w > 0 and h > 0:
                crop = img_raw[y:y + h, x:x + w]
                if crop.size > 0:
                    # 统一 Resize 到 64x64
                    cv2.imwrite(f"{OUTPUT_DIR}/{file_base}_{i}.jpg", cv2.resize(crop, SAVE_SIZE))

# 批量运行
for f in os.listdir(INPUT_DIR):
    process_image(os.path.join(INPUT_DIR, f))
分割完成后,就会获得一个未经过分类的文字数据集

img_4.png

现在的目标是将相同的文字放入同一个文件夹,当然我们不可能手动一个个去做分类
我们可以调用ocr模型的接口来帮我们做分类,我这边使用的是qwen-vl-ocr(可以去百炼平台复制自己的api-key,一般会有免费额度)
注意:图片存在干扰,ocr识别不同模型不同效果
import os
import json
import dashscope
import shutil
import uuid
from tqdm import tqdm
from dashscope import MultiModalConversation
from concurrent.futures import ThreadPoolExecutor

dashscope.base_http_api_url = "https://dashscope.aliyuncs.com/api/v1"
SOURCE_DIR = "targets/"       # 待识别的小图文件夹
DATASET_ROOT = "dataest/" # 结果存放根目录(按汉字命名文件夹)
MAX_WORKERS = 15

def qwen_ocr(local_path):
    try:
        image_path = f"file://{local_path}"

        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "image": image_path,
                        "min_pixels": 32 * 32 * 3,
                        "max_pixels": 32 * 32 * 8192,
                        "enable_rotate": True,
                    },
                    {
                        "text": """
                                请提取图像中的单个文字,只有一个中文字,图像是经过混淆的,字体可能存在旋转30度左右的情况,仔细辨别。
                                返回数据格式以json方式输出,格式为:{'result': 'x'}
                                """
                    },
                ]
            }
        ]

        response = MultiModalConversation.call(
            api_key='YOUR_TOKEN', # 替换成你自己的api-key
            model="qwen-vl-ocr",
            messages=messages,
        )
        text = response["output"]["choices"][0]["message"].content[0]["text"]
        json_str = text.strip().replace('```json', '').replace('```', '').replace("'", '"')
        json_data = json.loads(json_str)
        return json_data

    except Exception as e:
        print(e)

def clean_text(text):
    """清洗识别结果,去除特殊符号,防止创建文件夹失败"""
    if not text: return None
    text = text.strip()
    # 排除 Windows/Linux 文件系统非法字符
    invalid_chars = ['\\', '/', ':', '*', '?', '"', '<', '>', '|']
    for char in invalid_chars:
        text = text.replace(char, '')
    return text if text else None

def process_single_image(file_name):
    """单个图片处理逻辑"""
    local_path = os.path.join(SOURCE_DIR, file_name)

    try:
        # 1. 调用你的 OCR 函数
        result = qwen_ocr(local_path)
        text = result['result']

        # 2. 清洗结果
        folder_name = clean_text(text)

        # 如果识别为空,或者是长文本(超过1个字可能是识别错了),归入 unknown
        if not folder_name or len(folder_name) > 1:
            folder_name = "unknown"

        # 3. 创建目标文件夹
        target_dir = os.path.join(DATASET_ROOT, folder_name)
        os.makedirs(target_dir, exist_ok=True)

        # 4. 生成新文件名 (防止覆盖)
        # 使用 uuid 确保唯一性,保留原后缀
        ext = os.path.splitext(file_name)[1]
        new_name = f"{uuid.uuid4().hex[:8]}{ext}"
        target_path = os.path.join(target_dir, new_name)

        # 5. 移动文件 (或用 shutil.copy 复制)
        shutil.move(local_path, target_path)
        return True

    except Exception as e:
        print(f"Error processing {file_name}: {e}")
        return False

def main():
    if not os.path.exists(DATASET_ROOT):
        os.makedirs(DATASET_ROOT)

    # 获取所有图片
    images = [f for f in os.listdir(SOURCE_DIR) if f.lower().endswith(('.jpg', '.png', '.jpeg'))]
    print(f"共发现 {len(images)} 张图片,开始分类...")

    # 多线程并发处理
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
        # 使用 tqdm 显示进度条
        list(tqdm(executor.map(process_single_image, images), total=len(images)))

    print("分类完成!")

if __name__ == '__main__':
    main()
等待分类完成后,就会获得这样的文件夹,大概会有一千多类

img_5.png

接下来也是最耗时间,最折磨的一步,人工审查这一千多类的文件夹
将识别错的文字放入正确的文件夹,例如以下这个情况

img_6.png

全部分类好后,我们需要分出训练集以及验证集
先做统一标签体系(建立映射表)
import os
import json
import csv
from pathlib import Path

def generate_dataset_metadata(data_root, output_dir='.'):
    """
    扫描数据集文件夹,生成 class_map.json 和 dataset.csv
    """
    data_root = Path(data_root)
    valid_exts = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'}  # 根据需要增减

    print(f" 正在扫描目录: {data_root} ...")

    # 1. 获取所有类别文件夹并排序 (保证索引一致性)
    # 使用 list comprehension + sorted 确保跨平台顺序一致
    classes = sorted([
        d.name for d in os.scandir(data_root)
        if d.is_dir() and not d.name.startswith('.')
    ])

    if not classes:
        print("[!] 未找到类别文件夹,请检查路径。")
        return

    # 2. 生成映射字典 {汉字: ID}
    class_to_idx = {cls_name: i for i, cls_name in enumerate(classes)}

    # 3. 遍历采集图片路径
    data_list = []
    file_count = 0

    for cls_name in classes:
        cls_dir = data_root / cls_name
        cls_idx = class_to_idx[cls_name]

        # 使用 scandir 高效遍历
        with os.scandir(cls_dir) as entries:
            for entry in entries:
                if entry.is_file():
                    # 后缀检查 (转小写比较)
                    if os.path.splitext(entry.name)[1].lower() in valid_exts:
                        # 记录绝对路径和标签ID
                        data_list.append((entry.path, cls_idx))
                        file_count += 1

    print(f" 扫描完成。共 {len(classes)} 个汉字类别,{file_count} 张图片。")

    # 4. 导出映射文件 (class_map.json)
    map_path = os.path.join(output_dir, 'class_map.json')
    with open(map_path, 'w', encoding='utf-8') as f:
        json.dump(class_to_idx, f, ensure_ascii=False, indent=4)
    print(f"[+] 映射文件已保存: {map_path}")

    # 5. 导出数据列表 (dataset.csv)
    csv_path = os.path.join(output_dir, 'dataset.csv')
    with open(csv_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(['image_path', 'label_idx'])  # 表头
        writer.writerows(data_list)
    print(f"[+] 数据列表已保存: {csv_path}")

if __name__ == '__main__':
    # 替换为你的实际数据集根目录路径
    DATASET_ROOT = r'./dataest'
    generate_dataset_metadata(DATASET_ROOT)
会得到一个csv文件以及json文件,利用这两个文件来分数据集
import os
import json
import shutil
import pandas as pd
from tqdm import tqdm

def convert_balanced_yolo(csv_path, map_path, output_dir="yolo_dataset", val_ratio=0.2, min_samples=20):
    # 1. 加载映射字典
    with open(map_path, 'r', encoding='utf-8') as f:
        class_map = json.load(f)
    # 确保 Key 都是字符串,防止匹配失败
    idx_to_char = {str(k): str(v) for k, v in class_map.items()}
    # 同时也做一个 {索引数字: 汉字} 的映射
    idx_to_char_rev = {str(v): str(k) for k, v in class_map.items()}

    # 2. 读取 CSV
    df = pd.read_csv(csv_path)
    # 强制将标签列转为字符串,确保分组准确
    df.iloc[:, 1] = df.iloc[:, 1].astype(str)
    col_path, col_label = df.columns[0], df.columns[1]

    print(f" 正在平衡数据,目标每类至少 {min_samples} 张训练图...")

    for label, group in tqdm(df.groupby(col_label)):
        # 优先从字典获取汉字名,找不到则用 label 数字
        char_name = idx_to_char_rev.get(label, idx_to_char.get(label, f"label_{label}"))

        # 组内打乱
        group = group.sample(frac=1, random_state=42).reset_index(drop=True)
        n = len(group)

        # 划分训练和验证
        if n == 1:
            train_set = group
            val_set = group
        else:
            val_count = max(1, int(n * val_ratio))
            val_set = group.iloc[:val_count]
            train_set = group.iloc[val_count:]

        # --- 核心过采样修复 ---
        # 如果训练集少于目标,进行倍数增加
        if len(train_set) < min_samples:
            repeat_times = (min_samples // len(train_set)) + 1
            train_set = pd.concat([train_set] * repeat_times).reset_index(drop=True)
            train_set = train_set.iloc[:min_samples]  # 精确截断到 20 张

        # 4. 物理存放
        for split_name, data in [("train", train_set), ("val", val_set)]:
            target_dir = os.path.join(output_dir, split_name, char_name)
            os.makedirs(target_dir, exist_ok=True)

            # 使用 enumerate 产生的 i 作为文件名后缀,确保不被覆盖
            for i, row in enumerate(data.itertuples()):
                src = str(getattr(row, col_path))
                if not os.path.exists(src):
                    continue

                # 文件名格式: 序号_原文件名
                ext = os.path.splitext(src)[1]
                filename = f"sample_{i}{ext}"
                dst = os.path.join(target_dir, filename)
                shutil.copy(src, dst)

    print(f"[+] 转换完毕!请检查 {output_dir}/train/ 目录下各文件夹图片数量是否为 {min_samples}")

if __name__ == "__main__":
    # 请确保两个文件名正确
    convert_balanced_yolo("dataset.csv", "class_map.json", min_samples=20)
这一步还做了些特殊处理,因为常见字和偏僻字出现的概率差距很大
一些常见字会有几百个的样本,而一些字只有一两个样本,就会导致数据不均衡
通俗点说,就是通过“过采样”和“结构化重组”,为模型喂一份“营养均衡”的饭
接下来就可以拿输出的yolo_dataset训练模型了

分类模型训练

还是去官网下载分类预训练模型,我这里使用的是yolo11n-cls

img_7.png

# 训练代码
from ultralytics import YOLO
import json

if __name__ == "__main__":
    model = YOLO('./yolo11n-cls.pt')

    # 开始训练 (它会自动在目录下找 train 和 val)
    # patience=10 开启早停机制,有效防止过拟合
    results = model.train(data='./yolo_dataset',
                          epochs=200,
                          imgsz=64,
                          batch=64,
                          patience=50,
                          amp=False,
                          fliplr=0.0,
                          flipud=0.0,
                          label_smoothing=0.1,
                          degrees=15,  # 随机旋转 ±15 度(让模型学会识别歪一点的字)
                          translate=0.1,  # 随机平移(防止模型只认居中的字)
                          scale=0.1,  # 随机缩放(防止模型只认固定大小的字)
                          shear=5,  # 随机剪切变形
                          perspective=0.001,  # 微小的透视变换
                          hsv_s=0.3,  # 随机饱和度变化
                          hsv_v=0.3  # 随机亮度变化(应对不同背景颜色的验证码)
                          )

    # 提取 YOLO 生成的新字典映射,并保存为新的 json 供推理时使用
    new_vocab = model.names # 格式类似 {0: '工', 1: '爬', 2: '虫'}
    with open('yolo_class_map.json', 'w', encoding='utf-8') as f:
        json.dump(new_vocab, f, ensure_ascii=False, indent=4)
    print("[+] 新的推理字典已保存至 yolo_class_map.json")

    # 导出为 ONNX 模型
    model.export(format='onnx')

文字点选验证方案

先使用目标检测模型检测到文字,再将得到文字小图做分类识别,最后与题目文字对比确定顺序
因为只做了验证码图片文字识别的模型,所以我们还需要借助开源的轻量ocr识别模型来识别题目的文字
这里用ddddocr或者paddleocr识别都可以,题目文字没有做干扰,基本都能识别正确
# paddleocr识别题目文字
import cv2
import numpy as np
from paddleocr import PaddleOCR

# 初始化模型:lang="ch" 指定中文,use_angle_cls=True 启用方向分类器(识别旋转文字)
# 默认开启 show_log=False 进一步减少控制台输出
ocr = PaddleOCR(use_angle_cls=False, lang="ch", show_log=False)

def process_transparent_image(image_input):
    """
    纯 OpenCV + Numpy 矩阵级处理,将透明底替换为白底。
    比 PIL 更底层,避免异常格式处理失败。
    """
    # 1. 统一转为 numpy 字节流
    if isinstance(image_input, bytes):
        img_np = np.frombuffer(image_input, np.uint8)
    else:
        with open(image_input, 'rb') as f:
            img_np = np.frombuffer(f.read(), np.uint8)

    # 2. cv2.IMREAD_UNCHANGED 是关键,强制保留 Alpha 通道 (-1)
    img = cv2.imdecode(img_np, cv2.IMREAD_UNCHANGED)

    # 兜底:如果图片本身没有 4 通道(没有透明度),直接返回它前三个通道
    if img is None or len(img.shape) < 3 or img.shape[2] != 4:
        return img[:, :, :3] if img is not None and len(img.shape) == 3 else img

    # 3. 分离 BGR 通道 和 Alpha 通道
    bgr = img[:, :, :3]
    alpha = img[:, :, 3]  # 取出 Alpha 通道 (0-255)

    # 4. 创建纯白背景 (全 255)
    white_bg = np.ones_like(bgr, dtype=np.uint8) * 255

    # 5. 矩阵运算:归一化 Alpha (0.0~1.0)
    # np.newaxis 用于扩充维度,使其能与 3 通道的 BGR 矩阵相乘
    alpha_factor = alpha[:, :, np.newaxis] / 255.0

    # 6. 融合公式:前景 * Alpha + 背景 * (1 - Alpha)
    result = bgr * alpha_factor + white_bg * (1 - alpha_factor)

    return result.astype(np.uint8)

def recognize_text(image_input):
    # 1. 继续使用之前提供的白底替换函数 (process_transparent_image_cv)
    processed_img = process_transparent_image(image_input)

    # 2. 核心改动:det=False 关闭检测,cls=False 关闭方向分类
    result = ocr.ocr(processed_img, det=False, cls=False)

    # 3. det=False 时的返回值结构较简单,通常为:[[('头', 0.998)]]
    if not result or not result[0]:
        return ""

    try:
        # 直接提取识别文本
        wz = result[0][0][0]
        return {'class': wz}
    except Exception:
        return ""

# if __name__ == "__main__":
#     # 测试调用
#     target_image = "test_3.png"
#     text = recognize_text(target_image)
#     print("识别结果:", text)
当然yolo分类识别文字不一定每一次都识别正确,所以我们还需要一些容错机制
先将正确识别的文字,确认其点击的顺序,再将接下来的文字做相似度做对比,最后确认顺序
from char_similar import std_cal_sim

def smart_match_coordinates(yolo_data, target_chars):
    """
    智能匹配 YOLO 检测结果与 OCR 题目顺序,包含自动容错机制。
    :param yolo_data: YOLO 检测出的字典列表,例:[{'char': '金', 'center': (174, 92)}, ...]
    :param target_chars: OCR 识别的题目顺序列表,例:['金', '针', '菇']
    :return: 按题目顺序排列的坐标列表 [(x1, y1), (x2, y2), ...]
    """
    # 初始化结果数组,长度与题目一致,占位 None
    matched_coords = [None] * len(target_chars)
    # 复制一份可用池,被匹配掉的就从池子里移除
    available_yolo = yolo_data.copy()

    # 第一阶段:绝对精确匹配
    for i, target in enumerate(target_chars):
        for j, item in enumerate(available_yolo):
            if item['char'] == target:
                matched_coords[i] = item['center']
                available_yolo.pop(j)  # 匹配成功,移出可用池
                break

    # 第二阶段:容错兜底匹配(处理剩下的 None)
    for i, target in enumerate(target_chars):
        if matched_coords[i] is None and available_yolo:
            best_idx = 0
            if len(available_yolo) > 1:
                best_score = -1
                for j, item in enumerate(available_yolo):
                    # kind="shape" 表示只计算字形结构相似度 (默认会加上拼音和语义)
                    # 结果是一个 0~1 的浮点数
                    score = std_cal_sim(target, item['char'], kind="shape")
                    if score > best_score:
                        best_score = score
                        best_idx = j

            matched_coords[i] = available_yolo[best_idx]['center']
            available_yolo.pop(best_idx)

    return matched_coords

# if __name__ == "__main__":
#     # 模拟数据 1:完美识别情况
#     yolo_perfect = [{'char': '金', 'center': (174, 92)}, {'char': '菇', 'center': (241, 122)},
#                     {'char': '针', 'center': (248, 31)}]
#     ocr_target = ['金', '针', '菇']
# 
#     # 模拟数据 2:YOLO 识别错位情况(把'针'识别成了'十',把'菇'识别成了'茹')
#     yolo_error_1 = [{'char': '金', 'center': (174, 92)}, {'char': '菇', 'center': (241, 122)},
#                   {'char': '十', 'center': (248, 31)}]
# 
#     yolo_error_2 = [{'char': '金', 'center': (174, 92)}, {'char': '茹', 'center': (241, 122)},
#                     {'char': '汁', 'center': (248, 31)}]
# 
#     print("完美情况按顺序点击坐标:", smart_match_coordinates(yolo_perfect, ocr_target))
#     print("一个错情况按顺序点击坐标:", smart_match_coordinates(yolo_error_1, ocr_target))
#     print("两个错情况按顺序点击坐标:", smart_match_coordinates(yolo_error_2, ocr_target))
最后结合检测模型及分类模型,去识别验证码图片及题目文字即可

img_8.png

语序文字验证方案

第一种方案是调用大语言模型的接口做语序排序
第二种是做离线词库去匹配语序
我这边采用第二种方案,在github上找到了开源的离线词库,有三百多万的词
https://github.com/fkxxyz/chinese-dictionary-3.6million/blob/e87060b2316ce02162edd89ab2354ff033e01e2b/%E8%AF%8D%E5%85%B8360%E4%B8%87%EF%BC%88%E4%B8%AA%E4%BA%BA%E6%95%B4%E7%90%86%EF%BC%89.txt

当然我们用不了那么多,只保留分类模型能识别的字,这里问AI帮我们写个‘瘦身’代码即可

img_9.png

# 语序匹配代码
import itertools
import math

class ImprovedLMSolver:
    def __init__(self, dict_path='dict_mini.txt'):
        self.word_dict = {}
        try:
            with open(dict_path, 'r', encoding='utf-8') as f:
                for line in f:
                    parts = line.split()
                    if not parts: continue
                    word = parts[0]

                    try:
                        freq = int(parts[2])
                    except:
                        freq = 1

                    # 【核心修复】:防止 freq=0 导致 log(1)=0 得分失效
                    # 强制让所有存在于词典中的词,基础频率至少为 1
                    if freq <= 0:
                        freq = 1

                    self.word_dict[word] = freq
        except Exception as e:
            print(f"词库加载失败: {e}")

    def score(self, text):
        """
        评分逻辑:遍历所有可能的子串,累加权重。
        权重 = 长度平方 * log(频率)
        这样 '物理系' (长度3) 的权重会远大于 '物理' (长度2) + '系'
        """
        total_score = 0
        n = len(text)

        # 遍历所有子串: text[i:j]
        for i in range(n):
            for j in range(i + 1, n + 1):
                sub_word = text[i:j]
                if sub_word in self.word_dict:
                    freq = self.word_dict[sub_word]
                    length = len(sub_word)
                    # 【关键修改2】长度的平方作为倍率
                    # 长度3的词权重是9,长度2是4。
                    # 只要匹配到'物理系',分数直接爆炸,完胜 '系物理'
                    total_score += (length ** 2) * math.log(freq + 1)
        return total_score

    def solve(self, chars):
        perms = [''.join(p) for p in itertools.permutations(chars)]
        return max(perms, key=self.score)

# --- 测试 ---
# solver = ImprovedLMSolver('dict_mini.txt')
# print(solver.solve(['性', '适', '应']))
最后也是结合yolo模型去做识别就可以了

img_10.png

img_11.png

测试

并发测试去做验证,两种验证码混合测试,成功率在80%-90%左右

img_12.png

免费评分

参与人数 15威望 +1 吾爱币 +33 热心值 +14 收起 理由
ioyr5995 + 1 + 1 热心回复!
jaffa + 1 谢谢@Thanks!
raz2026 + 1 + 1 热心回复!
Voccoo + 1 + 1 我很赞同!
zgrm1000 + 1 + 1 我很赞同!
CYF369 + 1 + 1 用心讨论,共获提升!
mikemelon + 1 + 1 用心讨论,共获提升!
1045837055lucy + 1 + 1 谢谢@Thanks!
surepj + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
liuxuming3303 + 1 + 1 谢谢@Thanks!
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
yangchang1 + 1 我很赞同!
Dickxie + 1 我很赞同!
helian147 + 2 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

mikemelon 发表于 2026-3-27 10:17
感谢分享,识别验证码都上yolo了,不知以后验证码会变成什么样,听说那种返璞归真的“我不是机器人” 复选框的验证破解极难
Jaxx 发表于 2026-3-27 10:00
qwen识别后人工审查这一步,其实还可以用其他模型对错误的样本再过几遍,减少错误样本数量
lck8 发表于 2026-3-26 16:52
神人,你都能yolo了,为啥不用ddddocr别人训练好的
Terwingogo 发表于 2026-3-26 17:21
这是自动识别验证码的嘛
 楼主| 1JEY 发表于 2026-3-26 17:33
lck8 发表于 2026-3-26 16:52
神人,你都能yolo了,为啥不用ddddocr别人训练好的

一开始就试过了,总体效果不理想,你可以自己试试
1809022887 发表于 2026-3-26 17:36
lck8 发表于 2026-3-26 16:52
神人,你都能yolo了,为啥不用ddddocr别人训练好的

感觉ddddocr不太准欸,我过个滑块感觉正确率80%,不知道你们用着怎么样?难道是我没用好吗?
dft2010 发表于 2026-3-26 21:50
1JEY 发表于 2026-3-26 17:33
一开始就试过了,总体效果不理想,你可以自己试试

可以用DDcor二次训练的吧,不过也一样繁琐
Jin_bao 发表于 2026-3-27 06:05
博主你好,训练集可以分享一下吗
hainanyu 发表于 2026-3-27 09:15
这个主要用来网站注册的吗
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-12 04:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表