验证码对抗(一):文字点选——乱序图还原与坐标定位

张 张站长 · 2026-09-14 15:16 · 技术-验证码 · 42 次浏览 · 0 条评论

从零跑通:脚本自己画图、打乱、还原,复制粘贴就能运行看到结果,再讲 ddddocr 定位与排序。

这篇讲文字点选验证码里最关键的一步——乱序图还原。我把它写成从零开始、复制粘贴就能跑的教程,不需要任何验证码图、也不碰任何网站:脚本自己画一张图、自己打乱、再自己还原给你看。跟着做一遍,你就彻底懂了。

真实的点选验证码长这样,蓝底一根羽毛,上面散着几个彩色的字,要你按提示顺序去点:

真实点选验证码

很多站点为了防止你直接把图喂给识别模型,会先把整张图切成网格、把小块打乱了再发给你(浏览器前端再用一份"打乱规则"拼回去给你看)。所以自动识别的第一步,就是拿到规则、把图还原成正常的。这篇就把这一步讲到你能自己跑通。

第一步:装环境

先确认装了 Python 3(命令行输入 python --version 能看到版本号就行)。然后装两个库:

pip install pillow numpy

pillow 是处理图片的,numpy 是做数组运算的。就这两个。

第二步:完整代码,直接复制

新建一个文件 demo_shuffle.py,把下面整段贴进去。这段代码干三件事:自己画一张 5×2 的编号彩格图当素材、按一个 pos 规则把它打乱、再还原回来。

# demo_shuffle.py   依赖:pip install pillow numpy
import numpy as np
from PIL import Image, ImageDraw

# ---------- 第 0 步:自己画一张测试图(10 个编号格子,方便肉眼看还原对没)----------
def make_demo_image(path="demo.png", w=500, h=200):
    img = Image.new("RGB", (w, h), "white")
    d = ImageDraw.Draw(img)
    colors = ["#e57373","#f06292","#ba68c8","#7986cb","#4fc3f7",
              "#4db6ac","#aed581","#ffd54f","#ff8a65","#90a4ae"]
    pw, ph = w // 5, h // 2
    for i in range(10):                       # 从左到右、从上到下,编号 0~9
        r, c = i // 5, i % 5
        d.rectangle([c*pw, r*ph, (c+1)*pw, (r+1)*ph], fill=colors[i])
        d.text((c*pw + pw//2 - 8, r*ph + ph//2 - 8), str(i), fill="black")
    img.save(path)
    return img

# ---------- 第 1 步:按 pos 把图打乱(模拟服务端发下来的乱序图)----------
def shuffle_image(img, x_pieces, y_pieces, pos):
    arr = np.array(img); h, w = arr.shape[:2]
    pw, ph = w // x_pieces, h // y_pieces
    out = np.zeros_like(arr)
    for i in range(x_pieces * y_pieces):
        cur = int(pos[i])
        cr, cc = cur // x_pieces, cur % x_pieces
        tr, tc = i // x_pieces, i % x_pieces
        out[cr*ph:(cr+1)*ph, cc*pw:(cc+1)*pw] = arr[tr*ph:(tr+1)*ph, tc*pw:(tc+1)*pw]
    return Image.fromarray(out)

# ---------- 第 2 步:还原(这就是要掌握的核心)----------
def reverse_shuffle_image(img, x_pieces, y_pieces, pos):
    arr = np.array(img); h, w = arr.shape[:2]
    pw, ph = w // x_pieces, h // y_pieces
    out = np.zeros_like(arr)
    for i in range(x_pieces * y_pieces):
        cur = int(pos[i])                            # 还原图第 i 格,内容来自乱序图第 pos[i] 格
        cr, cc = cur // x_pieces, cur % x_pieces      # 它此刻在乱序图的行、列
        tr, tc = i // x_pieces, i % x_pieces          # 它该回到的行、列
        out[tr*ph:(tr+1)*ph, tc*pw:(tc+1)*pw] = arr[cr*ph:(cr+1)*ph, cc*pw:(cc+1)*pw]
    return Image.fromarray(out)

if __name__ == "__main__":
    X, Y = 5, 2                                        # 切成 5 列 2 行,共 10 块
    POS = [8, 0, 1, 4, 9, 5, 7, 6, 2, 3]              # 打乱规则
    original = make_demo_image()
    shuffled = shuffle_image(original, X, Y, POS); shuffled.save("shuffled.png")
    restored = reverse_shuffle_image(shuffled, X, Y, POS); restored.save("restored.png")
    same = np.array_equal(np.array(original), np.array(restored))
    print("已生成 demo.png / shuffled.png / restored.png")
    print("还原结果和原图是否完全一致:", same)

第三步:运行,看结果

命令行里运行:

python demo_shuffle.py

你会看到这样的输出:

已生成 demo.png / shuffled.png / restored.png
还原结果和原图是否完全一致: True

同时目录下多出三张图,打开对比就是下面这样——中间是打乱的(编号乱了),右边是还原的(编号又变回 0~9,和左边一模一样):

原图 / 乱序 / 还原

最后打印 True,说明还原和原图逐像素一致。到这里,还原这件事你已经跑通了。

核心原理:pos 到底是什么意思

pos 是长度为 x*y 的数组,把格子从左到右、从上到下编号 0~9。pos[i] 的含义是:还原图里第 i 格,它的内容此刻在乱序图的第 pos[i] 格里。

切块编号与 pos 映射

比如 pos[0] = 8,就是"还原图左上角第 0 格,内容现在在乱序图第 8 格"。所以还原就是循环每一格:算出它现在在哪(cur → 行 cr 列 cc)、算出它该去哪(i → 行 tr 列 tc)、把这块像素从"现在"搬到"该去"。行列换算是最常见的一维转二维:编号除以列数得行号,对列数取余得列号。

代码里 shuffle_image(打乱)和 reverse_shuffle_image(还原)几乎一样,只有搬运方向反过来——这也是最容易搞错的地方:如果你还原出来还是乱的,多半是方向反了,把这两个函数里赋值号两边对调一下再试。

真实场景怎么用

真实站点里,你不用自己造 pos,它随验证码接口一起下发,长这样:

shuffle_rule = {'shuffle': {'x': 5.0, 'y': 2.0,
                            'pos': [8.0, 0.0, 1.0, 4.0, 9.0, 5.0, 7.0, 6.0, 2.0, 3.0]}}

你把接口返回的这份规则里的 x、y、pos 取出来,喂给上面的 reverse_shuffle_image,就能把下载到的乱序图还原成正常图。

还原之后:认出字在哪、按什么顺序点

图正了,还要知道"每个字的中心坐标"。用现成的 ddddocr 最省事,它自带验证码目标检测模型。先装:

pip install ddddocr

然后这段能直接跑(对任意一张带内容的图片都能框出目标):

import ddddocr

det = ddddocr.DdddOcr(det=True)                       # det=True 是目标检测模式
with open("restored.png", "rb") as f:
    boxes = det.detection(f.read())                   # 返回每个目标的方框 [x1,y1,x2,y2]

for x1, y1, x2, y2 in boxes:
    cx, cy = (x1 + x2) // 2, (y1 + y2) // 2            # 方框中心 = 点击坐标
    print("一个待点目标的中心:", cx, cy)

det.detection 返回每个目标的外接矩形,取中心就是要点的坐标。

最后是排序。点选一般会给一张"顺序提示条",从左到右就是要点的先后:

点选顺序提示图

用普通识别模式把提示条里的字读出来,再拿这个顺序去匹配大图上检测到的字,按提示顺序输出坐标:

ocr = ddddocr.DdddOcr()                               # 普通识别模式
text = ocr.classification(open("hint.png", "rb").read())
print("要按这个顺序点:", text)                          # 例如 "宁正江疆"

到这里,"还原乱序图 → 检测每个字坐标 → 按提示排序"这条链路就完整了。拿到坐标之后,怎么把点击做得像人手(而不是瞬间精准命中被拦),是下一篇轨迹的事。

评论 0

还没有评论
← 全部文章

找站长

站长微信二维码 扫码加微信
售后群二维码 扫码进售后群
站长收款码 建站不易,感谢打赏
微信 18581469390
本站不支持自助充值;需要充值或开通额度,请直接联系站长。
加微信可以找我重置密码、充值、问工具怎么用