攻克验证码干扰线,意外收获 RGB 欧几里得新知识

作者:高玉涵
时间:2025.04.11 15:15
博客:blog.csdn.net/cg_i
环境:Windows10、Python 3.11.3、PIL、Tesseract-OCR。

“对呀对呀!茴字有四样写法,你知道么?”——孔乙己

一、缘起:验证码干扰线处理难题

先前写过一篇博文《Python 轻松去除验证码干扰点,让识别不再犯难》。相对于干扰点,干扰线形态各异,有的像错综复杂的蛛网,有的则是歪歪扭扭的曲线,它们与验证码字符相互交织,使得字符的识别变得异常困难(图1-1)。我首先尝试用以往文章提到的方法对图像进行处理,但效果不尽如人意,当存在干扰线穿越验证码时,清除干扰线会造成验证码字符出现断裂(图1-2),严重影响着验证码的识别准确率(图1-3)。
在这里插入图片描述

图 1-1 带干扰线验证码
1.1 清除干扰线代码(详见先前博文):
def retain():
    filename = "captchas/057609707563DBD01744265731000 (1).png"
    image = Image.open(filename)
    image = image.convert("RGB")
    width, height = image.size

    # 遍历图像的每个像素
    for x in range(width):
        for y in range(height):
            r, g, b = image.getpixel((x, y))
            # 检查当前像素的颜色是否不等于要保留的颜色
            # 验证码的颜色可通过 Windows 画图工具获取
            if (r, g, b) != (68,115,145):
                # 如果不等于,则将该像素的颜色设置为替换颜色
                image.putpixel((x, y), (255, 255, 255))

    image.save("captchas/process1.png")  # 保存处理后的图片

在这里插入图片描述

图 1-2 去除带干扰线后的验证码

从人类视觉角度审视,经处理后的图片近乎臻美,清晰的视觉效果,字符完整且辨识度极高,无论是线条的流畅度还是颜色的一致性,都无可挑剔,完全符合人类对清晰验证码图像的直观认知,除了字符中偶尔出现的几处断裂。

然而,在计算机的数字世界里,情况却截然不同。计算机遵循着极为严苛、精确的逻辑规则运行,它没有人类视觉所具备的容错与模糊识别能力。哪怕只是一个像素点的数值,从 0 变为 1 这样极其微小的差异,在计算机严谨的运算体系中,都如同一场 “数字风暴”。这一细微变化,足以改变图像在计算机眼中的整体特征与模式,进而导致后续的识别、分析等一系列操作出现偏差,最终致使整个任务流程走向失败,无法达成预期目标 。

1.2 尝试使用Tesseract-OCR来识别图中的验证码。
tesseract process1.png output
Estimating resolution as 196

从上面提示中,OCR 识别似乎成功。进一步查看输出文件output.txt,结果却令人大失所望。原本应准确无误呈现的验证码内容,实际却与预期大相径庭。仔细端详,发现验证码首位的0字符竟不翼而飞,不仅如此,字符串中还莫名出现了换行符,这一状况直接导致整个字符串支离破碎,完全丧失了连续性。

深入探究其背后的原因,罪魁祸首乃是验证码字符在前期处理过程中出现了断裂情况。这些断裂之处,如同一个个 “陷阱”,严重干扰了 OCR 算法的正常运行。OCR 算法在面对这种不完整、断裂的字符时,难以精准捕捉字符的完整形态与特征,无法依据既定的模式匹配规则准确识别,进而使得识别率大打折扣,最终呈现出如今这般错误百出的输出结果 。
在这里插入图片描述

图 1-3 Tesseract-OCR 识别结果

二、我是不姓孔的孔乙己:RGB 颜色空间与欧几里得距离

在不断探索解决办法的过程中,我偶然接触到了 RGB 颜色空间以及其中的欧几里得距离概念。RGB 颜色空间,作为一种广泛应用的颜色表示方式,通过红(Red)、绿(Green)、蓝(Blue)三种基本颜色的不同强度组合来呈现丰富多彩的世界。每一种颜色都可以用一个三维向量,即 (R, G, B) 来表示,其中 R、G、B 的取值范围通常在 0 到 255 之间。

而欧几里得距离,原本是数学中用于衡量空间中两点之间距离的概念,在 RGB 颜色空间里,它被用来衡量两个颜色之间的差异程度。对于两个 RGB 颜色 C1=(r1,g1,b1) 和 C2=(r2,g2,b2),它们之间的欧几里得距离 d 的计算公式为:

d = ( r 1 − r 2 ) 2 + ( g 1 − g 2 ) 2 + ( b 1 − b 2 ) 2 d = \sqrt{(r_1 - r_2)^2 + (g_1 - g_2)^2 + (b_1 - b_2)^2} d=(r1r2)2+(g1g2)2+(b1b2)2
例如,计算颜色((255, 0, 0))(红色)和((0, 255, 0))(绿色)之间的欧几里得距离:
d = ( 255 − 0 ) 2 + ( 0 − 255 ) 2 + ( 0 − 0 ) 2 = 25 5 2 + 25 5 2 = 2 × 25 5 2 ≈ 360.62 d = \sqrt{(255 - 0)^2 + (0 - 255)^2 + (0 - 0)^2} = \sqrt{255^2 + 255^2} = \sqrt{2\times255^2} \approx 360.62 d=(2550)2+(0255)2+(00)2 =2552+2552 =2×2552 360.62

距离与颜色差异的关系

  • 欧几里得距离越大,说明两个颜色在 RGB 颜色空间中相距越远,它们的视觉差异也就越大。
  • 反之,距离越小,颜色越相似。通过设定一个距离阈值,可以判断一个颜色是否在另一个颜色的一定相似范围内。

RGB 颜色空间中的欧几里得距离为我们提供了一种量化颜色差异的方法,在图像处理、计算机视觉等领域有着广泛的应用,比如颜色识别、图像分割、图像过滤等任务中,都可以通过计算颜色的欧几里得距离来判断颜色之间的关系,进而实现相应的功能。

注:限于篇幅,关于欧几里得空间的概念,这里就此打住,有兴趣的同学,可自习搜索相关资料补充。

三、尝试:应用像素间距离修复验证码断裂

了解到 RGB 颜色空间中的欧几里得距离后,我仿佛看到了一丝曙光,觉得也许可以运用距离和特定的像素搜索策略,修复这些断裂的验证码字符。

3.1 修复验证码断裂的 fill_gap 函数

fill_gap 函数是整个代码的核心,它的使命是修复因去除干扰线而断裂的验证码字符。该函数接收四个参数:image(一个 PIL 图像对象)、retain_color(要保留的颜色,即验证码字符的颜色)、background_color(默认的背景颜色,通常为白色 (255, 255, 255))以及 max_distance(最大搜索距离,用于控制搜索范围)。

def fill_gap(image, retain_color, background_color=(255, 255, 255), max_distance=2):
    """
    修复验证码中因去除干扰线造成的断裂
    :param image: PIL 图像对象
    :param retain_color: 要保留的颜色,格式为 (R, G, B)
    :param background_color: 背景颜色,默认是白色 (255, 255, 255)
    :param max_distance: 最大搜索距离
    :return: 处理后的 PIL 图像对象
    """
    width, height = image.size
    for x in range(width):
        for y in range(height):
            current_color = image.getpixel((x, y))
            if current_color == retain_color:
                # 向右搜索
                for dx in range(1, max_distance + 1):
                    if x + dx < width:
                        next_color = image.getpixel((x + dx, y))
                        if next_color == retain_color:
                            for i in range(1, dx):
                                mid_color = image.getpixel((x + i, y))
                                if mid_color == background_color:
                                    image.putpixel((x + i, y), retain_color)
                            break
                # 向下搜索
                for dy in range(1, max_distance + 1):
                    if y + dy < height:
                        next_color = image.getpixel((x, y + dy))
                        if next_color == retain_color:
                            for i in range(1, dy):
                                mid_color = image.getpixel((x, y + i))
                                if mid_color == background_color:
                                    image.putpixel((x, y + i), retain_color)
                            break
    return image

遍历图像像素

函数首先获取输入图像的宽度 width 和高度 height,随后通过嵌套的 for 循环遍历图像中的每一个像素点。对于每个像素,获取其颜色 current_color。当发现某个像素颜色与 retain_color(验证码字符颜色)相同时,便开启修复流程。

水平方向搜索与修复

当检测到验证码字符颜色的像素时,从该像素开始向右进行搜索。在 max_distance 范围内,检查每个相邻像素的颜色。若遇到另一个与 retain_color 相同的像素,说明这两个字符像素之间可能存在因干扰线去除而形成的断裂,中间可能夹杂着背景色像素。此时,再次遍历这两个字符像素之间的所有像素,如果发现某个像素颜色为 background_color(背景色),则将其颜色替换为 retain_color,从而实现水平方向上断裂字符的修复。一旦完成修复或搜索超出 max_distance 范围,便停止向右搜索。

垂直方向搜索与修复

类似地,在垂直方向上,从检测到的验证码字符颜色像素开始向下搜索。同样在 max_distance 范围内查找下一个 retain_color 像素,若找到,则检查它们之间的像素,将背景色像素替换为 retain_color,完成垂直方向上的断裂修复。最后,函数返回经过修复的图像对象。

运行示例:

将图 1-2 去除带干扰线后的验证码图片process1.png进行修复。

from PIL import Image
import math


def fill_gap(image, retain_color, background_color=(255, 255, 255), max_distance=2):
    为结省篇幅,此处省略,可参考先前代码

    
# 使用示例
if __name__ == "__main__":
    # 替换为你的图片路径
    filename = "captchas/process1.png"
    image = Image.open(filename)
    image = image.convert("RGB")
    retain_color = (68, 115, 145)
    processed_image = fill_gap(image, retain_color)
    output_filename = filename.rsplit('.', 1)[0] + '_repaired.png'
    processed_image.save(output_filename)
    print(f"处理后的图片已保存为 {output_filename}")

在这里插入图片描述

图 3-1 max_distance=2 修复的图片 process1_repaired.png

如上图 3-1 所示,我的代码能够在一定程度上修复因去除干扰线而断裂的验证码字符。这种方法为验证码识别前的预处理工作提供了一种有效的解决方案。然而,它也并非完美无缺。例如,max_distance 参数的设置需要根据不同验证码的特点进行调整,若设置不当,可能导致修复过度或修复不足(图 3-2 max_distance=5)。此外,该方法仅适用于水平和垂直方向上的断裂修复,对于倾斜或更复杂的断裂情况可能效果不佳。
在这里插入图片描述

图 3-2 max_distance=5 修复的图片

3.2 再次尝试使用Tesseract-OCR来识别图中的验证码。

tesseract process1_repaired.png output
Estimating resolution as 197

在这里插入图片描述

图 3-3 Tesseract-OCR 完美识别

从识别结果来看,我仅仅启用了 Tesseract - OCR 命令的默认功能,未对其进行任何复杂的参数调校,便出人意料地收获了精准无误的识别成果。在图像处理环节,我也只是运用了最为基础、简单的操作,没有涉及任何高深复杂的算法。这一轻松达成的成效,让我不禁感慨,为自己在这次实践中能有如此灵光乍现的时刻深感自豪。这让我愈发坚信想象力比知识重要这一观点。知识固然是宝贵的财富,它为我们的探索提供了坚实的基础与指引,但想象力却能赋予我们超越常规、开拓新领域的力量。在面对诸多未知与挑战时,丰富的想象力能够引领我们跳出既定的思维框架,去尝试那些看似不可能的路径,从而发现全新的可能性。

四、展望:继续前行的动力

总之,这次与 RGB 欧几里得距离的邂逅,虽然没有帮助我解决当下的实际问题,但却如同一盏明灯,照亮了我在图像处理领域继续探索的道路,带来了知识的增长和眼界的开拓,让我对未来的技术探索充满了期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值