""" 滑块验证码数据生成器 生成滑块验证码训练数据:随机纹理/色块背景 + 方形缺口 + 阴影效果。 标签 = 缺口中心 x 坐标 (整数) 文件名格式: {gap_center_x}_{index:06d}.png """ import random from PIL import Image, ImageDraw, ImageFilter from config import SOLVER_CONFIG from generators.base import BaseCaptchaGenerator class SlideDataGenerator(BaseCaptchaGenerator): """滑块验证码数据生成器。""" def __init__(self, seed: int | None = None): from config import RANDOM_SEED super().__init__(seed=seed if seed is not None else RANDOM_SEED) self.cfg = SOLVER_CONFIG["slide"] self.height, self.width = self.cfg["cnn_input_size"] # (H, W) self.gap_size = 40 # 缺口大小 def generate(self, text: str | None = None) -> tuple[Image.Image, str]: rng = self.rng gs = self.gap_size # 缺口左边界 x 范围: 留出边距,标签统一使用缺口中心 x margin = gs + 10 gap_left = rng.randint(margin, self.width - margin) gap_y = rng.randint(10, self.height - gs - 10) gap_center_x = gap_left + gs // 2 if text is None: text = str(gap_center_x) # 1. 生成纹理背景 img = self._textured_background(rng) # 2. 绘制缺口 (半透明灰色区域 + 阴影) overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) overlay_draw = ImageDraw.Draw(overlay) # 阴影 (稍大一圈) overlay_draw.rectangle( [gap_left + 2, gap_y + 2, gap_left + gs + 2, gap_y + gs + 2], fill=(0, 0, 0, 60), ) # 缺口本体 overlay_draw.rectangle( [gap_left, gap_y, gap_left + gs, gap_y + gs], fill=(80, 80, 80, 160), outline=(60, 60, 60, 200), width=2, ) img = img.convert("RGBA") img = Image.alpha_composite(img, overlay) img = img.convert("RGB") # 3. 轻微模糊 img = img.filter(ImageFilter.GaussianBlur(radius=0.3)) return img, text def _textured_background(self, rng: random.Random) -> Image.Image: """生成带纹理的彩色背景。""" img = Image.new("RGB", (self.width, self.height)) draw = ImageDraw.Draw(img) # 渐变底色 base_r = rng.randint(80, 200) base_g = rng.randint(80, 200) base_b = rng.randint(80, 200) for y in range(self.height): ratio = y / max(self.height - 1, 1) r = int(base_r + 40 * ratio) g = int(base_g - 20 * ratio) b = int(base_b + 20 * ratio) r, g, b = max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b)) draw.line([(0, y), (self.width, y)], fill=(r, g, b)) # 纹理噪声 for _ in range(self.width * self.height // 6): x = rng.randint(0, self.width - 1) y = rng.randint(0, self.height - 1) pixel = img.getpixel((x, y)) noise = tuple( max(0, min(255, c + rng.randint(-30, 30))) for c in pixel ) draw.point((x, y), fill=noise) # 随机色块 (模拟图案) for _ in range(rng.randint(4, 8)): x1, y1 = rng.randint(0, self.width - 30), rng.randint(0, self.height - 20) x2, y2 = x1 + rng.randint(15, 50), y1 + rng.randint(10, 30) color = tuple(rng.randint(50, 230) for _ in range(3)) draw.rectangle([x1, y1, x2, y2], fill=color) # 随机圆形 for _ in range(rng.randint(2, 5)): cx = rng.randint(10, self.width - 10) cy = rng.randint(10, self.height - 10) cr = rng.randint(5, 20) color = tuple(rng.randint(50, 230) for _ in range(3)) draw.ellipse([cx - cr, cy - cr, cx + cr, cy + cr], fill=color) return img