ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv11与Siamese孪生网络的验证码识别及ICP备案查询自动化

基于YOLOv11与Siamese孪生网络的验证码识别及ICP备案查询自动化 简介一套融合YOLOv11目标检测与Siamese孪生神经网络的实战项目包面向计算机视觉、验证码识别与自动化测试场景适合具备Python和OpenCV基础的中级开发者参考。资源采用zip压缩共22个文件、69.46MB主体包含Python源码、onnx推理模型、XML配置及说明文档py脚本覆盖目标检测、图像相似度比对和ICP备案API调用onnx模型可直接加载运行docx与md说明了环境部署与使用流程jpg样例图便于快速验证。已有74人学习内容涵盖模型推理、验证码自动识别与绕过、域名备案状态查询的完整链路并附requirements.txt、pyc缓存与项目工程配置目录结构清晰可作为算法选型、系统集成或课程设计的有力参考。1. 先拆技术栈YOLOv11检测、Siamese比对、ICP查询这条链路在解决什么验证码是Web自动化里绕不开的坎扭曲字符、干扰线、背景噪声传统OCR方案在这种图上前期处理还没做完准确率先掉了一半。这个标题给出的组合思路很直接——基于深度学习的目标检测与图像相似度比对系统把“找字符”和“认字符”拆成两个独立环节YOLOv11负责检测验证码里的每个字符位置Siamese孪生神经网络负责把裁剪出来的字符图和模板库做相似度比对识别结果再作为参数传给API接口查询域名ICP备案状态三个模块串成一条自动处理链路。这套方案适合三类人做Web自动化时被验证码卡住、想脱离打码平台的技术人员研究小样本图像分类、对孪生网络方向感兴趣的深度学习入门者这一波正好在搜yolov11环境配置和训练自己模型可以当成一个可落地的实验项目照着做以及需要在自建业务里批量核实域名备案状态、又不想人工登录反复查询的开发者。整条链路涉及的技术点只有目标检测、图像相似度比对和HTTP接口调用门槛没有想象高但坑一点不少。2. YOLOv11目标检测从ultralytics环境配置到字符检测模型训练2.1 yolov11ultralytics环境配置适合0基础纯小白的安装顺序YOLOv11实际是ultralytics这套框架在YOLOv8之后迭代出的检测模型训练、验证、预测、导出全走同一个包不需要自己搭检测头。热榜上问“yolov11(ultralytics)环境配置”“适合0基础纯小白”的人很多核心问题是安装顺序不对导致torch和CUDA版本打架。先创建虚拟环境再装依赖顺序不要乱conda create -n yolo11 python3.11 -y conda activate yolo11 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics装完之后跑一句验证能打印版本号就说明环境通了python -c from ultralytics import YOLO; print(YOLO.__version__) nvidia-smi | grep CUDA Version先说为什么先把torch装上再装ultralyticsultralytics的依赖里会自动带一份CPU版torch如果你先装ultralytics再装GPU版torch很可能被覆盖或冲突。用conda建一个干净的3.11环境torch单独装等于给后续的翻车提前买了道保险。cuda11.8还是cu121可以根据nvidia-smi里的驱动版本挑驱动够新就cu121。验证这步别跳过打印不出版本号直接排查torch不要急着改模型代码。2.2 字符检测数据集标注、格式转换与YOLO训练配置文件验证码字符检测的数据集可以自己标。用labelimg打开验证码图片框住每个字符类别统一叫char导出格式选YOLO。每张图对应一个同名txt一行一个目标五个数字类别ID、归一化中心x、归一化中心y、归一化宽、归一化高。import os import cv2 def voc_to_yolo(xml_path, img_path, out_dir): img cv2.imread(img_path) h, w img.shape[:2] # 解析VOC格式的xml这里省略xml解析细节 boxes parse_xml(xml_path) # [(xmin, ymin, xmax, ymax), ...] lines [] for xmin, ymin, xmax, ymax in boxes: cx (xmin xmax) / 2 / w cy (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))这段脚本把VOC格式的标注转成YOLO格式关键在归一化cx、cy、w、h全部除以原图宽高而不是除以目标尺寸。很多标注工具直接导出的是像素绝对值如果不归一化YOLO训练时会把框解释到图像外面去loss再低也不出正确结果。数据准备好之后写一个data.yamltrain: ./dataset/images val: ./dataset/images nc: 1 names: [char]train和val的路径可以用相对路径py文件所在目录放data.yaml模型训练脚本在同一目录跑路径解析不容易出错。2.3 训练参数与推理结果保存三个必调参数和两种验证方式训练命令很短但参数要会调。以字符检测为例from ultralytics import YOLO model YOLO(yolov11n.yaml) model.train( datadataset.yaml, epochs100, imgsz640, batch16, device0, patience20, )核心参数只有几个重点说说参数建议值理由imgsz640验证码字符普遍只有几十像素属于典型小目标分辨率低了字符直接糊掉epochs80-120字符检测任务特征简单100个epoch通常够patience设20做早停batch8-16看显存6G以下用812G以上才能上16batch对检测精度影响不如对分类大device0指定第一块GPU没有GPU就填cpu训练时间翻好几倍但能跑通训练完会打印模型保存路径best.pt和last.pt都在runs/detect目录下。验证推理用best.pt别用last.ptlast是最后一步的存档通常不如果断早停时的best。推理这块注意一下“yolov11保存推理结果”的姿势from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model(captcha.png, conf0.5, saveTrue, save_diroutput) for r in results: for box in r.boxes.xyxy: print(box.tolist())saveTrue会把检测框画到原图上保存到save_dir适合人工看效果如果要看模型在哪些位置产生响应可以用results[0].plot()生成带框图片也可以用model.predict的save_txtTrue导出坐标文本。想进一步理解模型关注区域可以输出特征图热力图改走model(..., embedTrue)或者用ultralytics内部的feature_visualization方法。这里只说结论热力图能帮你判断模型是在看字符还是看干扰线如果激活区域集中在背景纹理上说明数据里干扰图太多模型学偏了。3. Siamese孪生神经网络验证码字符相似度比对的原理与PyTorch实现3.1 为什么验证码字符识别适合用孪生网络而不是分类网络先回答一个高频疑问验证码字符就几十类用普通CNN分类不行吗能行但有个硬约束——每个类别需要足够的训练样本。验证码字符的难点在于同一字符在不同验证码里字体、倾斜、粗细都不一样分类网络要覆盖这种多样性每个字符至少得标几百张标注成本一下子失控。孪生网络换了个思路不直接学“这个字符是哪个类”而是学“两张图是不是属于同一个字符”。输入是一对图像两个分支共享同一套权重分别提特征再算两个特征向量的距离。距离小于阈值就是同一类大于阈值就是不同类。这样训练数据从“每类几百张”变成“成对的样本”同一张图既能跟同类图组成正样本对又能跟异类图组成负样本对样本利用率高很多。这个特性让它天然适合验证码识别准备一个只有几十张的字符模板库每个字符两三张干净图推理时把验证码裁剪出的字符图跟模板库里的图逐一做相似度比对取距离最近的那一个模板作为结果。这就是标题里“图像相似度比对”落地的位置。3.2 从零写一个轻量孪生网络网络结构、对比损失与训练循环不依赖现成框架用PyTorch直接写一个能跑的结构import torch import torch.nn as nn import torch.nn.functional as F class SiameseNet(nn.Module): def __init__(self, feature_dim128): super().__init__() self.cnn nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), ) self.fc nn.Sequential( nn.Flatten(), nn.Linear(64 * 8 * 8, feature_dim), ) def forward_one(self, x): return self.fc(self.cnn(x)) def forward(self, x1, x2): # 两个输入走同一套权重共享参数 out1 self.forward_one(x1) out2 self.forward_one(x2) return out1, out2forward里两个输入都调forward_one参数完全共享这就是“孪生”的意思。特征维度feature_dim取128对于几十个字符类别来说足够了太大反而让距离计算变迟钝。对比损失Contrastive Loss是训练孪生网络的核心def contrastive_loss(out1, out2, label, margin1.0): # label1表示同一类label0表示不同类 dist F.pairwise_distance(out1, out2) loss label * dist.pow(2) (1 - label) * F.relu(margin - dist).pow(2) return loss.mean()损失分两部分正样本对label1希望距离趋近0负样本对label0希望距离大于margin。margin越大网络被逼迫把不同类拉得越开但过大也会让训练不稳定一般从1.0开始试。训练循环按标准写法组织optimizer torch.optim.Adam(siamese.parameters(), lr1e-3) for epoch in range(60): total_loss 0 for batch in dataloader: img1, img2, label batch # 每个batch包含成对的字符图 out1, out2 siamese(img1, img2) loss contrastive_loss(out1, out2, label, margin1.0) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() print(fepoch {epoch}, loss {total_loss / len(dataloader):.4f})训练数据建议每张图都做一次小范围随机平移、旋转和亮度扰动让网络学到“同一个字符变一点样子还是同一个字符”而不是死记像素。这比堆更多原图效果更明显是孪生网络训练里性价比最高的技巧。3.3 推理阶段距离阈值怎么定玄学背后的正负样本分布训练完之后保存每个模板字符的特征向量推理时只算输入图跟所有模板向量的余弦相似度或欧氏距离取最近的那个。这里有个“玄学”问题阈值设多少算找到设多少算不认识我的做法是准备一小组验证数据跑一遍正样本对和负样本对的距离把两边的距离直方图画出来import numpy as np def choose_threshold(siamese, template_feats, val_data): pos_dists, neg_dists [], [] for img, temp_feat, is_same in val_data: feat siamese.forward_one(img) dist F.pairwise_distance(feat, temp_feat).item() if is_same: pos_dists.append(dist) else: neg_dists.append(dist) # 取正负分布交叉点作为阈值 return (np.mean(pos_dists) np.mean(neg_dists)) / 2阈值卡在正负样本距离分布的中间位置比拍脑袋可靠得多。如果两边的分布大量重叠说明特征没学好回去检查数据增强和margin而不是硬调阈值。阈值这个东西在孪生网络里本来就带点半玄学性质但把它建立在分布数据上至少心里有底。4. 验证码自动识别与处理把YOLOv11检测和Siamese比对串成一条链路4.1 预处理先做减法灰度、二值化与干扰线的取舍验证码图片进YOLO之前先做一遍轻量预处理。常见做法是灰度化、去背景、适度去噪声。import cv2 import numpy as np def preprocess_captcha(path): img cv2.imread(path, cv2.IMREAD_GRAYSCALE) # 大津法二值化自动找阈值 _, thresh cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 去孤立噪点3x3卷积核 thresh cv2.medianBlur(thresh, 3) return thresh关键在“取舍”两个字二值化能把背景和干扰线去掉但也会把字符的细笔画一起腐蚀掉。Siamse网络吃的是特征不是像素与其把图洗得干干净净再喂模型不如保留灰度图上的纹理信息。实际工程里我一般会同时保留二值图和灰度图二值图给YOLO做检测定位灰度图给Siamese做特征比对这样两边都吃到最合适的数据。4.2 用YOLOv11定位字符并排序裁剪验证码字符检测出来的框是无序的YOLO不负责排序。识别结果要按从左到右拼成字符串所以裁剪前必须按x坐标排序。from ultralytics import YOLO yolo YOLO(runs/detect/train/weights/best.pt) captcha cv2.imread(captcha.png) results yolo(captcha.png, conf0.4) boxes [] for r in results: for box in r.boxes.xyxy: x1, y1, x2, y2 box.tolist() boxes.append([x1, y1, x2, y2]) # 按x坐标升序排得到字符顺序 boxes.sort(keylambda b: b[0]) chars [] for x1, y1, x2, y2 in boxes: padding 4 crop captcha[max(0, int(y1) - padding):int(y2) padding, max(0, int(x1) - padding):int(x2) padding] chars.append(crop)padding加4个像素是为了把字符边缘的残缺部分包进来避免把笔画裁在边界上。如果字符间距很近padding太大会把相邻字符的边带进来这个值要根据实际图微调经验上4到8像素是安全区间。4.3 端到端识别脚本检测裁剪到比对输出的完整代码两个模型都训练好后串起来的完整推理链路是import torch import torch.nn.functional as F from siamese_model import SiameseNet # 复用第3章的结构 # 加载模板库特征模板库里每个字符放2-3张干净图 template_names [A, B, C, D, E, F, ...] template_feats torch.load(template_feats.pt) # shape [num_templates, 128] siamese SiameseNet() siamese.load_state_dict(torch.load(siamese_best.pt)) siamese.eval() result_text with torch.no_grad(): for crop in chars: # 统一缩放到模型输入尺寸 crop cv2.resize(crop, (64, 64)) crop cv2.cvtColor(crop, cv2.COLOR_GRAY2RGB) if len(crop.shape) 2 else crop x torch.tensor(crop / 255.0, dtypetorch.float32).unsqueeze(0).unsqueeze(0) feat siamese.forward_one(x) # 和所有模板特征算距离取最近的 dists torch.cdist(feat, template_feats) idx torch.argmin(dists).item() result_text template_names[idx]识别结果拼接成完整字符串后可以直接当验证码答案用。这里有一个容易被忽略的点阈值判断。argmin取最近的模板没错但如果最近距离都大于阈值说明这个字符不在模板库里强行用argmin只会输出错误结果。在线上一旦出现这种识别置信度低的字符最稳妥的做法是让业务层直接判定本次识别失败重新拉取验证码再试一轮。5. 通过API接口查询域名ICP备案状态请求构造、响应解析与联动重试5.1 查询接口的请求参数设计超时、请求头与查询字段验证码识别结果拿到后下一步就是通过API接口查询域名ICP备案状态。这部分的接口一般有两种官方公开的备案查询服务以及各类数据服务商提供的封装接口。不管接哪种请求设计套路一致import requests import time def query_icp(domain, verify_code): url https://api.example.com/icp/query # 以实际接口文档为准 params { domain: domain, code: verify_code, # 验证码识别结果有些接口做人机校验用 page: 1, pageSize: 10, } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://example.com/, } resp requests.get(url, paramsparams, headersheaders, timeout10) return resp.json()timeout务必设置默认的requests不设超时会一直挂着批量查询时一个接口卡死整个任务。请求头里User-Agent和Referer是很多接口校验的基础字段不带的很容易直接被拒。接口对接前先通读文档确认查询参数名是domain还是host是GET还是POST这步不做就靠试错会浪费大量时间。5.2 响应数据结构与备案状态字段解析备案查询接口的响应通常返回顶层code和msg业务数据放在data字段里。data里的核心字段一般是备案号、备案主体名称、备案类型、审核时间、当前状态data resp.get(data, {}) if not data: print(无备案记录或查询失败:, resp.get(msg)) return None record data.get(record, {}) icp_number record.get(icpNumber) # 如 京ICP备12345678号 subject record.get(subjectName) # 备案主体名称 status record.get(status) # 常见值normal / expired / pending解析时别用data[record]这种硬索引要习惯data.get(record, {})接口偶尔会缺字段或者返回空结构硬索引会在某个凌晨任务里突然抛KeyError。返回结构里的字段名各家不一定一致但语义差不多备案号、主体、状态是核心三要素。核对一下这三项跟预期是否匹配就能判断域名备案是否有效。5.3 与验证码识别模块联动查不到结果时自动重识别重查这整条链路的闭环价值就在这一节验证码识别失败不代表任务失败而是触发重试机制。实际跑的时候验证码识别不可能每次都对尤其遇到字体变形的字符。与其等人工介入不如设计一层联动重试for attempt in range(3): captcha_resp fetch_captcha() # 拉取新的验证码 code recognize_captcha(captcha_resp) # YOLOSiamese识别 if not code or len(code) ! 4: time.sleep(random.uniform(1, 2)) continue result query_icp(target_domain, code) if result.get(code) 0: return parse_icp(result) # 查询接口提示验证码错误重新拉图重识 time.sleep(random.uniform(1.5, 3)) raise RuntimeError(三次尝试后仍未查询成功)这里两个细节值得说每次重试之间加随机延时不只是sleep固定值而是用random.uniform生成1到2秒的随机间隔。固定间隔容易被接口侧风控识别成脚本行为随机延时是业界共识的低成本反误判手段。另一个细节是“拉新验证码”而不是“用同一张图反复试”验证码识别接口对同一张图频繁提交大概率直接拉黑换新图才是正道。6. 避坑与排查训练、推理、接口调用最容易翻车的四个位置6.1 YOLO坐标归一化的坑loss正常但框全偏现象训练loss曲线漂亮跑到验证集上一看检测框全部偏移半张图。原因标注工具导出的是绝对像素坐标直接写进txt没除以原图宽高。解决写脚本检查txt把每行的cx原图宽、cy原图高反向还原对比原图核对坐标是否落在字符中心这一步5分钟能做完但能救下后续几十小时的训练。6.2 孪生网络loss不收敛先查正负样本比例再调margin现象loss训练到第五十个epoch还挂在0.7上下抖。原因负样本对太多或margin设得太大正样本对的梯度被淹没。解决先把batch里正负样本比例调到1比1margin从1.0改到0.8再训一轮还不收敛就检查输入归一化——模板图用归一化预处理训练图也得做同样处理预处理不一致是“loss不降”排名第一的隐藏原因。6.3 预处理用力过猛模板库高分、线上低分的经典翻车现象模板库回测识别率99%换到真实验证码图片直接掉到一半。原因模板库是干净裁剪图线上验证码带干扰线二值化把字符笔画腐蚀断特征直接崩掉。解决Siamese的输入改成灰度图而不是二值图灰度图保留笔画细节干扰线交给模型自己适应。这条也是我做这方向时最深刻的踩坑记忆。6.4 查询接口被限流随机延时与指数退避现象连续查询十几条域名后接口开始返回“请求过于频繁”或验证码错误。原因请求频率超过接口阈值IP被临时风控。解决每条查询之间sleep一到两秒失败时用指数退避重试第一次等3秒第二次等9秒再失败就停手换时段。做批量查询前先估算总量算一下最慢需要多久再决定是否要分批。这四类问题基本覆盖了从图像识别到接口调用的整条链路。我自己做这类系统时养成一个习惯每一版改动只动一个变量比如只调margin、只动预处理、只改重试策略不允许多个变量同时变化。原因是出了问题你根本说不清是哪个调整导致的排查成本比调试成本高得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表