两种方式识别“传统”图片验证码

简介: 目前,很多网站为了反爬都会采取各种各样的策略,比较简单粗暴的一种做法就是图片验证码,随着爬虫技术与反爬技术的演变,目前验证码也越来越复杂,比较高端的如Google的I‘m not a robot,极验等等。这些新的反爬方式大多都基于用户行为分析用户点击前的鼠标轨迹来判断是访问者是程序还是人。

滚动.gif

作者|余俊(连舟)
编辑|橙子君
出品|阿里巴巴新零售淘系技术

目前,很多网站为了反爬都会采取各种各样的策略,比较简单粗暴的一种做法就是图片验证码,随着爬虫技术与反爬技术的演变,目前验证码也越来越复杂,比较高端的如Google的I‘m not a robot,极验等等。这些新的反爬方式大多都基于用户行为分析用户点击前的鼠标轨迹来判断是访问者是程序还是人。

基于图像处理的图片验证码识别

这篇文章介绍的是破解一般“传统”的图片验证码的步骤。上面提到的极验(目前应用比较广)也已经可以被破解,知乎上有相关的专栏,这里就不重复了。

即便是传统的图片验证码,也是有难度区分的(图一是我母校研究生院官网上的验证码,基本形同虚设;图二则是某网站的会员登录时的验证码增加了一些干扰信息,字符也有所粘连),但是破解的流程大致是一样的。

image.png
图1

image.png
图2

▐ 识别步骤

获取样本

从目标网站获取了5000个验证码图片到本地,作为样本。因为后期需要进行监督学习样本量要足够大。

样本去噪

✎ 先二值化图片

这一步是为了增强图片的对比度,利于后期图片图像处理,代码如下:

# 二值化图片
    @staticmethod
    def two_value_img(img_path, threshold):
        img = Image.open(img_path).convert('L')
        #  setup a converting table with constant threshold
        tables = []
        for i in range(256):
            if i < threshold:
                tables.append(0)
            else:
                tables.append(1)

        # convert to binary image by the table
        bim = img.point(tables, '1')
        return bim

效果如下:
image.png

✎ 图片去噪

该案例中就是去除两条干扰线,常规的去噪算法有很多(洪水法等等),这里根据图片的特点采用了两种去噪算法,一种是自己根据图片的特征实现的算法,另一种是“八值法”。去噪后的效果如下,可以看到去除了大部分的干扰线(剩下的根据字宽可以直接过滤掉),但是部分字符也变细了,所以这一步的去噪阀值需要不断调整,在去噪的基础上要尽量保持原图的完整和可读性。

代码如下:

# 根据图片特点,自己写的降噪算法
def clean_img(img, threshold):
    width, height = img.size
    for j in range(height):
        for i in range(width):
            point = img.getpixel((i, j))
            if point == 0:
                for x in range(threshold):
                    if j + x >= height:
                        break
                    else:
                        if point != img.getpixel((i, j + x)):
                            img.putpixel((i, j), 1)
                            break
    return img


# 八值法降噪
def clean_img_eight(img, threshold):
    width, height = img.size
    arr = [[0 for col in range(width)] for row in range(height)]
    arr = array(arr)
    for j in range(height):
        for i in range(width):
            point = img.getpixel((i, j))
            if point == 0:
                sum = 0
                for x in range(-1, 2):
                    for y in range(-1, 2):
                        if i + x > width - 1 or j + y > height - 1 or \
                                i + x < 0 or j + y < 0:
                            sum += 1
                        else:
                            sum += img.getpixel((i + x, j + y))
                if sum >= threshold:
                    arr[j, i] = 1

    for i in range(len(arr)):
        for j in range(len(arr[i])):
            if arr[i, j] == 1:
                img.putpixel((j, i), 1)
    return img

效果如下:

image.png

图片切割

图片切割有很多算法如投影法、CFS以及滴水法等。投影法适用于字符垂直方向上没有粘连和重合的情况,CFS能够很好的切割垂直方向有粘连但是没有粘连的字符,水滴法可以分割粘连字符。目前采用的CFS切割法。切割效果如下图,对于非粘连字符,效果很不错。CFS联通域切割的实现算法主要用的是图的广度/深度遍历,代码如下:

# CFS图像切割
    @staticmethod
    def cut_img(img, threshold, cut_width, cut_height, width_min, width_max, height_min):
        charters_imgs = []
        width, height = img.size
        charters_pixels = []
        visited_pixels = []
        pixel_arr = ImgTools.get_pixel_arr(img)
        for i in range(width):
            for j in range(height):
                pixel = img.getpixel((i, j))
                if pixel == 0 and [i, j] not in visited_pixels:
                    charter_pixels = Node(i, j, pixel_arr, []).traversal()
                    visited_pixels.extend(charter_pixels)
                    if len(charter_pixels) > threshold:
                        charters_pixels.append(charter_pixels)
        for i in range(len(charters_pixels)):
            x_min = 0
            y_min = 0
            x_max = 0
            y_max = 0
            # 这里是为了处理没有粘连但是垂直方向有重合的字符
            width, height = img.size
            tmp_img = Image.new('1', (width * 2, height * 2), 255)
            for j in range(len(charters_pixels[i])):
                x, y = charters_pixels[i][j]
                tmp_img.putpixel((x, y), 0)
                if x > x_max:
                    x_max = x
                else:
                    if x < x_min or x_min == 0:
                        x_min = x
                if y > y_max:
                    y_max = y
                else:
                    if y < y_min or y_min == 0:
                        y_min = y
            if width_min < x_max - x_min < width_max and y_max - y_min > height_min:
                # charters_imgs.append(tmp_img.crop((x_min, y_min, x_max, y_max)))
                # 这里是为了将所有的图片切成一样大,便于后期的特征提取
                charters_imgs.append(tmp_img.crop((x_min, y_min, x_min + cut_width, y_min + cut_height)))
        return charters_imgs
class Node:
    x = 0
    y = 0
    graph_arr = []
    visited_neighbors = []

    def __init__(self, x, y, graph_arr, visited_neighbors):
        self.x = x
        self.y = y
        self.graph_arr = graph_arr
        self.visited_neighbors = visited_neighbors

    def traversal(self):
        for i in range(-1, 2):
            for j in range(-1, 2):
                p = self.x + i
                q = self.y + j
                if (0 <= p < len(self.graph_arr)) and (0 <= q < len(self.graph_arr[0])):
                    if array(self.graph_arr)[p, q] == 0 and [p, q] not in self.visited_neighbors:
                        self.visited_neighbors.append([p, q])
                        next_node = Node(p, q, self.graph_arr, self.visited_neighbors)
                        next_node.traversal()
        return self.visited_neighbors

效果如下:

image.png

提取 feature 并训练特征模型

✎ 提取 feature

每个字符用了40个样本(每个字符都切成了60×60)进行打标签,如果效果不好后续可以增加样本量(由于M大多数粘连严重,所以切出来的M很少,没有达到40个,直接导致后面M的识别结果也很不好)。

image.png

✎ 训练模型

这里采用了libsvm来训练模型,从个样本中预留了1/10个作为检验集,accuracy达到95%。

识别效果

先手动挑选了“乍一看”粘连不是很严重的30个样本,进行训练,结果如下,在80%左右。

image.png

总结和优化方向

1、目前整个识别流程已经走通,验证码识别服务也初具对外服务的能力;

2、虽然目前对于整体验证码的识别效果不是很好,但是,验证码服务拼的是识别率,比如说一个验证码需要识别,我在对其进行预处理和切割之后发现字符粘连效果不好,则完全可以抛弃,这并不影响识别率。换句话讲我只是别切出来是四个字符的验证码即可(如果遇到一个网站每个图片的粘连都比较严重,这条路就走不通了);

3、优化方向有两个:

(1)优化切割字符的算法,目前的机器学习算法在图片切割比较好的情况下识别率是非常高的,因此目前这类验证码的切割是整个过程中的难点,对于该案例可以采用波斯平滑后通过垂直投影图找到极值点作为水滴法的起点是一个思路;

(2)增加样本量,目前是40个识别率已经可以接受,如果增加训练集的Size识别率应该会有所提升。

基于神经网络的图片验证码识别

上文提到的识别方式效果严重依赖于图像切割的效果。对于一些粘连严重的验证码,需要花很大的精力来进行去噪和分割,即使这样,效果也不一定会达到令人满意的程度。

基于CNN来进行验证码识别的优势在与整体识别,字符的粘连对于识别效果的影响不是特别大。

image.png
图一

▐ 识别过程

以图一验证码为例,在本次识别中,构建了一个4层(一开始是3层,效果并不好)隐层的深度神经网络,网络结构如下(示意图中每一层的图片看起来虽然没有区别但是每一层的size是有区别的,重点看下标):

image.png

参数如下:

卷积核大小为5×5(zero padding),Pooling采用max pooling(block为2×2),激活函数为ReLU,学习率(LR)为0.001,在训练集识别率达到99%的时候输出模型。

训练过程优化的过程包括:

  • 一开始使用三层卷积网络,效果并不理想,随后调整成了四层卷积层,效果有所提升;
  • 训练集样本量的提高;
  • 学习率的降低;

使用Google的Tensorflow机器学习框架进行训练。涉及到公司政策,代码就不上传了,网上可以搜到一些类似的。

▐ 识别效果

识别效果如下,左图是学习样本量为3500的时候识别率,右图是样本量为4500(人工打码约8小时)的时候识别率:
image.png
CNN:训练集样本量为3500时

image.png
CNN:训练集样本量为4500时

对比一下用libsvm来识别的情况,左图是单个字符样本量为40张的时候识别情况,右图为单个字符样本量为100的时候的样本量(识别结果为xxxx的表示切割字符失败):

image.png
切割+SVM:每个字符样本为40时

image.png
切割+SVM:每个字符样本为100时

▐ 存在的问题

整个过程遇到的问题主要包括两个方面:

  1. 中四层网络的训练明显要比三层收敛的更慢,MBP只能用CPU跑,正常4层网络要输出一个可用的模型(训练集识别率达到99%)需要1-2天;
  2. 样本集对于学习至关重要,目前没有较好的对样本进行打标记的方式,只能人工打码(人工智能之人工),4500的样本量我打了4个晚上。并且人工打码会有很多不确定因素,在本例中,后期发现很多7和T都打错了,势必会对最后的识别效果有所影响;

将来的优化方向包括:

  • 增加训练集;
  • 调低LR学习率;
  • 调低keep_prob的值;
  • 增加卷积层;

▐ 总结

验证码识别哪怕模型的识别率只有20%也是可用的,识别错了换一个就可以,但是在整个爬虫和反爬的过程中,主动权往往掌握在反爬的这一边,切换验证码的成本比破解一个类型的验证码的成本要低太多,更何况验证码只是众多反爬手段之一,也正是如此爬虫和反爬才会显得格外的有意思。

和其他互联网攻防技术一样,这篇文章只是验证码识别技术探讨,旨在为设计验证码防爬提供思路,并不鼓励读者带着炫技或其他目的去破解验证码,疯狂爬取别人的网站。任何技术本身都是中立的,be a reasonable crawler。

淘系技术天猫奢品团队

我们是一支支撑天猫奢侈品、品牌客户、淘宝心选等大店数据化经营解决方案的技术团队,依托于阿里大中台推动品牌经营解决方案升级,不断提升客户经营的效率,持续提升业务价值赋能业务。

如果您有兴趣可讲简历发至:lianzhou.yj@alibaba-inc.com,期待您的加入!

关注「淘系技术」微信公众号,一个有温度有内容的技术社区~
image.png

相关文章
|
消息中间件 文字识别 PHP
批量名片识别解决方案
批量对名片图片进行识别,并保存在数据库中,识别完成后并完成消息通知
280 1
|
8天前
|
人工智能
Coze 识别用户意图
Coze 识别用户意图
9 0
|
1月前
|
文字识别 安全 网络安全
印刷文字识别产品使用合集之一般包含什么信息, 会被认为敏感信息
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。
|
17天前
|
机器学习/深度学习 文字识别 算法
视觉智能开放平台产品使用合集之文字敏感内容识别和文字违禁内容识别有什么区别
视觉智能开放平台是指提供一系列基于视觉识别技术的API和服务的平台,这些服务通常包括图像识别、人脸识别、物体检测、文字识别、场景理解等。企业或开发者可以通过调用这些API,快速将视觉智能功能集成到自己的应用或服务中,而无需从零开始研发相关算法和技术。以下是一些常见的视觉智能开放平台产品及其应用场景的概览。
|
16天前
|
文字识别 开发工具 Android开发
视觉智能开放平台操作报错合集之使用人脸属性检测接口,出现报错:图片无法下载,请检查链接是否可访问和本地网络情况,该如何解决
在使用视觉智能开放平台时,可能会遇到各种错误和问题。虽然具体的错误代码和消息会因平台而异,但以下是一些常见错误类型及其可能的原因和解决策略的概述,包括但不限于:1. 认证错误、2. 请求参数错误、3. 资源超限、4. 图像质量问题、5. 服务不可用、6. 模型不支持的场景、7. 网络连接问题,这有助于快速定位和解决问题。
|
2月前
|
文字识别
印刷文字识别产品使用合集之在自定义模板中,时间总是被错误地识别如何解决
印刷文字识别(Optical Character Recognition, OCR)技术能够将图片、扫描文档或 PDF 中的印刷文字转化为可编辑和可搜索的数据。这项技术广泛应用于多个领域,以提高工作效率、促进信息数字化。以下是一些印刷文字识别产品使用的典型场景合集。
|
2月前
|
JSON 文字识别 数据可视化
印刷文字识别产品使用合集之有识别二维码并将识别二维码的内容通过接口返回的功能吗
印刷文字识别(Optical Character Recognition, OCR)技术能够将图片、扫描文档或 PDF 中的印刷文字转化为可编辑和可搜索的数据。这项技术广泛应用于多个领域,以提高工作效率、促进信息数字化。以下是一些印刷文字识别产品使用的典型场景合集。
|
2月前
|
文字识别 负载均衡 安全
文字识别OCR常见问题之通用识别和手写体识别直接合并调用如何解决
文字识别OCR(Optical Character Recognition)技术能够将图片或者扫描件中的文字转换为电子文本。以下是阿里云OCR技术使用中的一些常见问题以及相应的解答。
|
2月前
|
人工智能 安全 数据安全/隐私保护
AIGC内容检测方案初探
【1月更文挑战第15天】AIGC内容检测方案初探
77 1
AIGC内容检测方案初探
|
2月前
|
机器学习/深度学习 API Android开发
视觉智能平台常见问题之判断摄像头抓拍到包含人脸的照片如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。