Tiktok - 旋转图片识别

旋转验证码识别:原理剖析与实现

大家好!今天我们来深入探讨TKCaptcha框架中的旋转验证码识别模块。

旋转验证码是一种要求用户将图像旋转到正确位置的验证码类型,它比传统验证码更难被机器自动识别。

然而,TKCaptcha框架通过精妙的图像处理算法,成功实现了对这类验证码的自动识别。

接下来,我们就一起探索rotate_captcha.py模块的核心原理和实现细节。

一、旋转验证码的基本原理

旋转验证码通常包含两个关键元素:

  1. 内部图像:需要被旋转到正确位置的中心图像
  2. 外部图像:作为参考的外围图像或背景

用户需要旋转内部图像,使其与外部图像正确对齐,验证才能通过。

这种验证码类型的安全性基于以下假设:

  • 机器很难判断图像的正确方向
  • 人类可以依靠视觉直觉快速识别正确的旋转角度

二、RotateCaptcha类结构概览

让我们首先看看RotateCaptcha类的基本结构:

class RotateCaptcha:
    def __init__(self) -> None:
        self.req = httpx.Client(proxies=None)

    def request(self, url):
        """请求图片"""
        for _ in range(3):
            try:
                r = self.req.get(url, timeout=5)
                return r.content
            except Exception as e:
                logger.error(f"请求图片失败: {e}")

    def get_img_content(self, img):
        """获取图片内容"""
        resimg = base64.b64decode(img)
        return BytesIO(resimg)
        
    # 其他方法...
    
    def get_result(self, inner_image_brg_path, outer_image_brg_path, result_img=None, iv=5):
        # 核心识别算法
        # ...

这个类包含了几个关键方法:

  • request:用于从URL获取图片
  • get_img_content:解析Base64编码的图片数据
  • get_result:核心识别方法,接受内部和外部图像,返回正确的旋转角度

三、图像预处理技术

在进行旋转匹配之前,需要对图像进行适当的预处理:

def img_inner_white(self, pic_base64, inner_base64):
    """
    将pic_path中间大小的原形填充白色,中心大小原形直径为inner_path的宽度
    """
    inner = Image.open(self.get_img_content(inner_base64))
    inner_width = inner.size[0]
    pic = Image.open(self.get_img_content(pic_base64))
    mask = Image.new("L", inner.size, 0)
    draw = ImageDraw.Draw(mask)
    draw.ellipse((0, 0, inner_width, inner_width), fill=255)
    pic.paste(mask, (int((pic.size[0] - inner_width) / 2), int((pic.size[1] - inner_width) / 2)), mask)
    # ...返回处理后的图像

这个方法创建了一个圆形的透明区域,使得内部图像能够与外部图像适当融合。

类似地,img_outer_white方法处理外部图像的遮罩效果:

def img_outer_white(self, inner_base64):
    """将图片外围填充白色"""
    inner = Image.open(self.get_img_content(inner_base64))
    mask = Image.new("L", inner.size, 0)
    draw = ImageDraw.Draw(mask)
    draw.ellipse((0, 0, inner.size[0], inner.size[1]), fill=255)
    inner.putalpha(mask)
    # ...返回处理后的图像

这些预处理步骤确保了后续角度匹配的准确性。

四、圆周像素提取

旋转验证码识别的关键技术是圆周像素提取。这个方法从图像的圆周上按一定角度间隔提取像素点:

def circle_point_px(self, img, accuracy_angle, r=None):
    rows, cols, _ = img.shape
    assert 360 % accuracy_angle == 0
    x0, y0 = r0, _ = (rows // 2, cols // 2)
    if r:
        r0 = r
    angles = np.arange(0, 360, accuracy_angle)
    cos_angles = np.cos(np.deg2rad(angles))
    sin_angles = np.sin(np.deg2rad(angles))
    x = x0 + r0 * cos_angles
    y = y0 + r0 * sin_angles
    x = np.round(x).astype(int)
    y = np.round(y).astype(int)
    circle_px_list = img[x, y]
    return circle_px_list

这段代码做了以下事情:

  1. 计算图像中心点
  2. 生成从0°到360°的角度序列,间隔为accuracy_angle
  3. 根据三角函数计算圆周上每个点的坐标
  4. 提取这些坐标点的像素值

这种方法为后续的旋转匹配提供了基础特征集。

五、图像旋转处理

为了找到最佳匹配角度,我们需要尝试不同的旋转角度。rotate方法实现了图像旋转功能:

def rotate(self, image, angle, center=None, scale=1.0):
    h, w = image.shape[:2]
    if center is None:
        center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, angle, scale)
    rotated = cv2.warpAffine(image, M, (w, h))
    return rotated

这个方法使用OpenCV的getRotationMatrix2DwarpAffine函数,以图像中心为旋转点,按指定角度旋转图像。

六、颜色空间转换与距离计算

旋转验证码的匹配过程中,需要比较两个图像在颜色上的相似度。为此,我们使用了HSV颜色空间和YUV距离计算:

def HSVDistance(self, c1, c2):
    y1 = 0.299 * c1[0] + 0.587 * c1[1] + 0.114 * c1[2]
    u1 = -0.14713 * c1[0] - 0.28886 * c1[1] + 0.436 * c1[2]
    v1 = 0.615 * c1[0] - 0.51498 * c1[1] - 0.10001 * c1[2]
    y2 = 0.299 * c2[0] + 0.587 * c2[1] + 0.114 * c2[2]
    u2 = -0.14713 * c2[0] - 0.28886 * c2[1] + 0.436 * c2[2]
    v2 = 0.615 * c2[0] - 0.51498 * c2[1] - 0.10001 * c2[2]
    rlt = math.sqrt((y1 - y2) * (y1 - y2) + (u1 - u2) * (u1 - u2) + (v1 - v2) * (v1 - v2))
    return rlt

这个方法将RGB颜色值转换为YUV颜色空间,然后计算欧氏距离。YUV颜色空间比RGB更接近人类感知,因此比较结果更符合人眼直觉。

七、核心算法:旋转角度识别

现在,让我们深入RotateCaptcha的核心方法:get_result。这个方法通过尝试不同的旋转角度,找出内外图像最佳匹配的角度:

def get_result(self, inner_image_brg_path, outer_image_brg_path, result_img=None, iv=5):
    try:
        # 加载图像
        inner_image_brg = cv2.imdecode(np.frombuffer(self.get_img_content(inner_image_brg_path).getvalue(), np.uint8), cv2.IMREAD_COLOR)
        outer_image_brg = cv2.imdecode(np.frombuffer(self.get_img_content(outer_image_brg_path).getvalue(), np.uint8), cv2.IMREAD_COLOR)
        
        # 转换颜色空间
        inner_image = cv2.cvtColor(inner_image_brg, cv2.COLOR_BGR2HSV)
        outer_image = cv2.cvtColor(outer_image_brg, cv2.COLOR_BGR2HSV)
        
        # 裁剪外部图像为正方形
        outer_image = self.crop_to_square(outer_image)
        
        all_deviation = []
        pic_circle_radius = inner_image.shape[0] // 2
        
        # 尝试不同的旋转角度
        for result in range(0, 180):
            # 旋转内外图像
            inner = self.rotate(inner_image, -result)
            outer = self.rotate(outer_image, result)
            
            # 提取圆周像素
            outer_circle_point_px = self.circle_point_px(outer, 1, pic_circle_radius + iv)
            inner_circle_point_px = self.circle_point_px(inner, 1, pic_circle_radius - iv)
            
            # 计算总偏差
            total_deviation = np.sum([self.HSVDistance(in_px, out_px) for in_px, out_px in zip(inner_circle_point_px, outer_circle_point_px)])
            all_deviation.append(total_deviation)
        
        # 找出偏差最小的角度
        result = all_deviation.index(min(all_deviation))
        
        # 可选:生成结果图像
        if result_img:
            # 生成结果可视化图像...
            
        return result
    except Exception as e:
        import traceback
        logger.error(traceback.format_exc())

这个方法的工作流程是:

  1. 解码并加载内部和外部图像
  2. 将图像从BGR转换为HSV颜色空间
  3. 裁剪外部图像为正方形
  4. 对0°到180°的每个角度:
    • 以相反方向旋转内部和外部图像
    • 提取两个图像圆周上的像素点
    • 计算像素点间的颜色距离总和
  5. 找出颜色距离最小的角度,即为最佳匹配角度

八、实现中的数学原理

旋转验证码识别涉及几个关键的数学原理:

1. 极坐标变换

圆周上的点坐标计算使用了极坐标公式:

x = x0 + r * cos(θ)
y = y0 + r * sin(θ)

其中(x0, y0)是圆心,r是半径,θ是角度。

2. 图像旋转变换

图像旋转使用了仿射变换矩阵:

[x']   [cos(θ)  -sin(θ)  tx] [x]
[y'] = [sin(θ)   cos(θ)  ty] [y]
[1 ]   [0        0       1 ] [1]

其中θ是旋转角度,(tx, ty)是平移量,确保旋转中心正确。

3. YUV颜色变换

RGB到YUV的变换使用了标准转换矩阵:

Y =  0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V =  0.615R - 0.515G - 0.100B

这种转换更接近人眼对颜色的感知。

九、性能优化策略

RotateCaptcha模块包含了几个性能优化策略:

1. 向量化操作

angles = np.arange(0, 360, accuracy_angle)
cos_angles = np.cos(np.deg2rad(angles))
sin_angles = np.sin(np.deg2rad(angles))
x = x0 + r0 * cos_angles
y = y0 + r0 * sin_angles

使用NumPy的向量化操作,避免了Python循环,大大提高了计算效率。

2. 角度搜索优化

for result in range(0, 180):
    # 旋转和比较操作

只搜索0°到180°的角度,而不是0°到360°,减少了一半的计算量。这是因为旋转180°后的图像会重复前半部分的匹配效果。

3. 像素采样策略

outer_circle_point_px = self.circle_point_px(outer, 1, pic_circle_radius + iv)
inner_circle_point_px = self.circle_point_px(inner, 1, pic_circle_radius - iv)

只采样圆周上的像素,而不是比较整个图像,显著减少了计算量。参数iv(interval)提供了一个缓冲区,允许内外圆之间有一定间隔。

十、实际应用案例

让我们看一个完整的应用案例:

# 初始化识别器
captcha = RotateCaptcha()

# 读取测试图片
with open("inner.png", "rb") as f:
    inner_data = base64.b64encode(f.read()).decode("utf-8")
    
with open("outer.png", "rb") as f:
    outer_data = base64.b64encode(f.read()).decode("utf-8")

# 识别旋转角度
angle = captcha.get_result(inner_data, outer_data, "result.png")
print(f"正确的旋转角度是: {angle}度")

这个示例展示了完整的使用流程:

  1. 初始化RotateCaptcha对象
  2. 加载并编码内部和外部图像
  3. 调用get_result方法识别正确的旋转角度
  4. 可选地,生成结果可视化图像

十一、改进方向与扩展

虽然当前的RotateCaptcha模块已经能够有效识别大多数旋转验证码,但仍有一些可能的改进方向:

1. 多尺度分析

对于复杂验证码,可以在多个半径处提取圆周像素,获取更丰富的特征:

def get_result_multiscale(self, inner_image, outer_image):
    """多尺度分析版本"""
    # ...初始化代码
    
    pic_circle_radius = inner_image.shape[0] // 2
    radii = [
        pic_circle_radius - 10,
        pic_circle_radius - 5,
        pic_circle_radius,
        pic_circle_radius + 5,
        pic_circle_radius + 10
    ]
    
    for result in range(0, 180):
        # 旋转内外图像
        inner = self.rotate(inner_image, -result)
        outer = self.rotate(outer_image, result)
        
        total_deviation = 0
        # 在多个半径处提取并比较像素
        for r_inner, r_outer in zip(radii[:-1], radii[1:]):
            inner_px = self.circle_point_px(inner, 1, r_inner)
            outer_px = self.circle_point_px(outer, 1, r_outer)
            total_deviation += np.sum([self.HSVDistance(in_px, out_px) 
                                       for in_px, out_px in zip(inner_px, outer_px)])
        
        all_deviation.append(total_deviation)
    
    # ...查找最小偏差并返回结果

2. 频率域分析

图像的旋转在频率域中对应于极坐标变换。可以使用傅里叶变换来加速旋转检测:

def get_result_frequency(self, inner_image, outer_image):
    """频率域分析版本"""
    # 转换为灰度图
    inner_gray = cv2.cvtColor(inner_image, cv2.COLOR_BGR2GRAY)
    outer_gray = cv2.cvtColor(outer_image, cv2.COLOR_BGR2GRAY)
    
    # 应用快速傅里叶变换
    inner_fft = np.fft.fft2(inner_gray)
    inner_fft_shifted = np.fft.fftshift(inner_fft)
    
    outer_fft = np.fft.fft2(outer_gray)
    outer_fft_shifted = np.fft.fftshift(outer_fft)
    
    # 转换为幅值谱
    inner_magnitude = np.log(np.abs(inner_fft_shifted) + 1)
    outer_magnitude = np.log(np.abs(outer_fft_shifted) + 1)
    
    # 转换为极坐标
    # ... 剩余的频率域分析代码

3. 并行计算优化

旋转验证码的识别天然适合并行计算,可以使用多线程或GPU加速:

def get_result_parallel(self, inner_image, outer_image):
    """并行计算版本"""
    # ...初始化代码
    
    from concurrent.futures import ThreadPoolExecutor
    
    def compute_deviation(angle):
        inner = self.rotate(inner_image.copy(), -angle)
        outer = self.rotate(outer_image.copy(), angle)
        inner_px = self.circle_point_px(inner, 1, pic_circle_radius - 5)
        outer_px = self.circle_point_px(outer, 1, pic_circle_radius + 5)
        return np.sum([self.HSVDistance(in_px, out_px) 
                       for in_px, out_px in zip(inner_px, outer_px)])
    
    # 并行计算各个角度的偏差
    with ThreadPoolExecutor(max_workers=8) as executor:
        all_deviation = list(executor.map(compute_deviation, range(180)))
    
    # 找出偏差最小的角度
    result = all_deviation.index(min(all_deviation))
    return result

4. 神经网络方法

随着深度学习的发展,可以使用神经网络直接预测旋转角度:

def get_result_cnn(self, inner_image, outer_image):
    """CNN预测版本"""
    # 预处理图像
    combined = np.concatenate([inner_image, outer_image], axis=2)  # 合并通道
    combined = cv2.resize(combined, (224, 224))  # 调整大小
    combined = combined / 255.0  # 归一化
    
    # 使用预训练CNN模型预测角度
    angle_pred = self.cnn_model.predict(np.expand_dims(combined, axis=0))[0][0]
    
    # 将连续预测转换为离散角度
    return round(angle_pred * 180) % 180

总结

TKCaptcha框架的RotateCaptcha模块通过精妙的图像处理算法,成功解决了旋转验证码的识别问题。

它的核心是通过提取圆周像素并比较不同旋转角度下的颜色差异,找出内外图像最佳匹配的角度。

虽然这种方法看起来简单直观,但它结合了多种图像处理技术,如颜色空间转换、图像旋转、像素采样等,形成了一个高效可靠的解决方案。

随着验证码技术的不断发展,我们也需要不断改进和优化识别算法,以应对新的挑战。

希望本文对你理解旋转验证码的工作原理和TKCaptcha的实现方式有所帮助。

在下一篇文章中,我们将探讨TKCaptcha框架的整体架构和API设计,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值