154 lines
6.9 KiB
Python
154 lines
6.9 KiB
Python
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageOps
|
|
import random
|
|
import string
|
|
import math
|
|
|
|
def generate_complex_click_captcha(
|
|
width=500,
|
|
height=350,
|
|
total_chars=15, # 总字符数增加
|
|
target_count=4,
|
|
font_size_range=(25, 40), # 字符大小随机
|
|
bg_text_count=8 # 背景干扰文字数量
|
|
):
|
|
"""生成更复杂的点击式文字验证码"""
|
|
# 1. 创建背景(带纹理的渐变色)
|
|
image = Image.new('RGB', (width, height), (255, 255, 255))
|
|
draw = ImageDraw.Draw(image)
|
|
|
|
# 绘制背景纹理(随机噪点+轻微模糊)
|
|
for _ in range(width * height // 20): # 密集噪点
|
|
x = random.randint(0, width)
|
|
y = random.randint(0, height)
|
|
color = (random.randint(220, 240), random.randint(220, 240), random.randint(220, 240))
|
|
draw.point((x, y), fill=color)
|
|
image = image.filter(ImageFilter.GaussianBlur(radius=1.5))
|
|
|
|
# 2. 添加背景干扰文字(大字号、半透明、覆盖范围广)
|
|
bg_chars = '天地日月风云雨雪春夏秋冬金木水火土'
|
|
try:
|
|
bg_font = ImageFont.truetype('simhei.ttf', random.randint(60, 100))
|
|
except:
|
|
bg_font = ImageFont.load_default()
|
|
for _ in range(bg_text_count):
|
|
char = random.choice(bg_chars)
|
|
x = random.randint(-50, width + 50) # 允许超出边界
|
|
y = random.randint(-50, height + 50)
|
|
color = (random.randint(200, 230), random.randint(200, 230), random.randint(200, 230), 100) # 半透明灰
|
|
angle = random.randint(-45, 45)
|
|
# 绘制半透明背景文字
|
|
bg_img = Image.new('RGBA', (width, height), (255, 255, 255, 0))
|
|
bg_draw = ImageDraw.Draw(bg_img)
|
|
bg_draw.text((x, y), char, font=bg_font, fill=color)
|
|
bg_img_rotated = bg_img.rotate(angle, expand=True)
|
|
image.paste(bg_img_rotated, (0, 0), bg_img_rotated) # 覆盖粘贴
|
|
|
|
# 3. 生成核心字符集(中文+字母+数字混合)
|
|
chinese_chars = '一二三四五六七八九十甲乙丙丁戊己庚辛壬癸子丑寅卯辰巳午未申酉戌亥'
|
|
all_chars = list(chinese_chars + string.ascii_letters + string.digits)
|
|
random.shuffle(all_chars)
|
|
used_chars = all_chars[:total_chars]
|
|
target_chars = random.sample(used_chars, target_count)
|
|
|
|
# 4. 加载主字体(多种字体随机选择,增强复杂度)
|
|
fonts = ['simhei.ttf', 'simsun.ttc', 'microsoftyahei.ttf'] # 可添加更多字体路径
|
|
try:
|
|
selected_font = random.choice(fonts)
|
|
except:
|
|
selected_font = None
|
|
|
|
# 5. 随机分布核心字符(带复杂干扰)
|
|
char_positions = {} # {字符: (中心x, 中心y)}
|
|
for char in used_chars:
|
|
# 随机字体大小
|
|
font_size = random.randint(*font_size_range)
|
|
# 加载字体
|
|
try:
|
|
font = ImageFont.truetype(selected_font, font_size) if selected_font else ImageFont.load_default()
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
# 随机位置(边缘留出更大空间,避免重叠)
|
|
x = random.randint(font_size * 2, width - font_size * 2)
|
|
y = random.randint(font_size * 2, height - font_size * 2)
|
|
|
|
# 随机颜色(目标与干扰字符颜色可能接近)
|
|
if char in target_chars:
|
|
# 目标字符颜色:深色系,可能与干扰色接近
|
|
color = (random.randint(30, 120), random.randint(30, 120), random.randint(30, 120))
|
|
else:
|
|
# 干扰字符颜色:与目标色有重叠区间
|
|
color = (random.randint(50, 150), random.randint(50, 150), random.randint(50, 150))
|
|
|
|
# 更大范围的旋转角度(-60到60度)
|
|
angle = random.randint(-60, 60)
|
|
|
|
# 绘制带旋转和轻微扭曲的字符
|
|
char_img = Image.new('RGBA', (font_size * 3, font_size * 3), (255, 255, 255, 0))
|
|
char_draw = ImageDraw.Draw(char_img)
|
|
char_draw.text((font_size, font_size), char, font=font, fill=color)
|
|
|
|
# 轻微扭曲处理(仿射变换)
|
|
if random.random() > 0.3: # 30%概率扭曲
|
|
# 简单扭曲参数
|
|
dx = random.randint(-5, 5)
|
|
dy = random.randint(-5, 5)
|
|
char_img = char_img.transform(
|
|
char_img.size,
|
|
Image.AFFINE,
|
|
(1, 0.1 * dx, 0, 0.1 * dy, 1, 0)
|
|
)
|
|
|
|
char_rotated = char_img.rotate(angle, expand=True)
|
|
rx, ry = char_rotated.size
|
|
paste_x = x - rx // 2
|
|
paste_y = y - ry // 2
|
|
image.paste(char_rotated, (paste_x, paste_y), char_rotated)
|
|
|
|
# 记录中心坐标(修正旋转偏移)
|
|
char_positions[char] = (x, y)
|
|
|
|
# 6. 添加干扰曲线(2-4条贝塞尔曲线)
|
|
def bezier_curve(start, end, control_points, steps=50):
|
|
"""生成贝塞尔曲线路径"""
|
|
points = [start]
|
|
for t in range(1, steps + 1):
|
|
t /= steps
|
|
# 贝塞尔曲线公式(简化版,2个控制点)
|
|
x = (1-t)**3 * start[0] + 3*(1-t)**2*t*control_points[0][0] + 3*(1-t)*t**2*control_points[1][0] + t**3*end[0]
|
|
y = (1-t)**3 * start[1] + 3*(1-t)**2*t*control_points[0][1] + 3*(1-t)*t**2*control_points[1][1] + t**3*end[1]
|
|
points.append((x, y))
|
|
return points
|
|
|
|
for _ in range(random.randint(2, 4)):
|
|
# 随机曲线参数
|
|
start = (random.randint(0, width), random.randint(0, height))
|
|
end = (random.randint(0, width), random.randint(0, height))
|
|
control1 = (random.randint(0, width), random.randint(0, height))
|
|
control2 = (random.randint(0, width), random.randint(0, height))
|
|
curve = bezier_curve(start, end, [control1, control2])
|
|
# 曲线颜色(与字符颜色有重叠)
|
|
line_color = (random.randint(80, 180), random.randint(80, 180), random.randint(80, 180))
|
|
draw.line(curve, fill=line_color, width=random.randint(2, 4))
|
|
|
|
# 7. 添加颗粒噪点(覆盖字符,增加识别难度)
|
|
for _ in range(width * height // 50):
|
|
x = random.randint(0, width)
|
|
y = random.randint(0, height)
|
|
noise_color = (random.randint(100, 200), random.randint(100, 200), random.randint(100, 200))
|
|
# 随机大小的噪点(1-3像素)
|
|
draw.rectangle([(x, y), (x + random.randint(1, 3), y + random.randint(1, 3))], fill=noise_color)
|
|
|
|
# 8. 轻微锐化,增强边缘(反模糊)
|
|
image = image.filter(ImageFilter.SHARPEN)
|
|
|
|
# 9. 获取目标字符坐标
|
|
target_positions = {char: char_positions[char] for char in target_chars}
|
|
|
|
return image, target_chars, target_positions
|
|
|
|
# 生成复杂点击式验证码
|
|
complex_click_img, complex_targets, complex_coords = generate_complex_click_captcha()
|
|
complex_click_img.save('complex_click_captcha.png')
|
|
print(f"需点击的文字:{complex_targets}")
|
|
print(f"对应坐标:{complex_coords}") |