vlambda博客
学习文章列表

使用SCF快速部署验证码识别接口

一、本文介绍

验证码识别是搞爬虫

实现自动化脚本避不开的一个问题,


通常验证码识别程序要么部署在本地,

要么部署在服务器端,


如果部署在服务器端就需要

自己去搭建配置网络环境并编写调用接口,

这是一个极其繁琐耗时的过程。


但是现在我们通过无服务器云函数(SCF),

就可以快速将本地验证码识别程序发布上线,

极大地提高了开发效率。


废话少说,上图 ↓


使用SCF快速部署验证码识别接口



一种比较简单的验证码



使用SCF快速部署验证码识别接口



识别扭曲变形的验证码


可以看到,识别效果还是蛮好的,

甚至超过了肉眼识别率。


二、操作步骤


传统的验证码识别流程是


  1. 图像预处理(灰化,去噪,切割,二值化,去干扰线等)

  2. 验证码字符特征提取(SVM,CNN等)

  3. 验证码识别


下面我就带大家一起来创建、编写

并发布上线一个验证识别云函数


第一步:新建python云函数


参见我之前的系列文章

《万物皆可Serverless之使用SCF+COS快速开发全栈应用》


第二步:编写验证识别云函数




一个简单的验证码


Life is short, show me the code.

这里我就以一个

最简单的验证码识别程序为例,

直接上代码


 
   
   
 

import io

import os

import time

from PIL import Image as image

import json


#字符特征

chars = {

    '1': [11101, ...],

    '2': [10010, ...],

    '3': [01001, ...],

    # 其他字符特征...

}



# 灰度处理

def covergrey(img):

    return img.convert('L')


# 去除验证码边框

def clearedge(img):

    for y in range(img.size[1]):

        img.putpixel((0, y), 255)

        img.putpixel((1, y), 255)

        img.putpixel((2, y), 255)

        img.putpixel((img.size[0]-1, y), 255)

        img.putpixel((img.size[0]-2, y), 255)

        img.putpixel((img.size[0]-3, y), 255)

    for x in range(img.size[0]):

        img.putpixel((x, 0), 255)

        img.putpixel((x, 1), 255)

        img.putpixel((x, 2), 255)

        img.putpixel((x, img.size[1]-1), 255)

        img.putpixel((x, img.size[1]-2), 255)

        img.putpixel((x, img.size[1]-3), 255)

    return img


# 去除干扰线并转换为黑白照片

def clearline(img):

    for y in range(img.size[1]):

        for x in range(img.size[0]):

            if int(img.getpixel((x, y))) >= 110:

                img.putpixel((x, y), 0xff)

            else:

                img.putpixel((x, y), 0x0)

    return img


# 去噪/pnum-去噪效率

def del_noise(impnum=3):

    w, h = im.size

    white = 255

    black = 0

    for i in range(0, w):

        im.putpixel((i, 0), white)

        im.putpixel((i, h - 1), white)

    for i in range(0, h):

        im.putpixel((0, i), white)

        im.putpixel((w - 1, i), white)

    for i in range(1, w - 1):

        for j in range(1, h - 1):

            val = im.getpixel((i, j))

            if val == black:

                cnt = 0

                for ii in range(-12):

                    for jj in range(-12):

                        if im.getpixel((i + ii, j + jj)) == black:

                            cnt += 1

                if cnt < pnum:

                    im.putpixel((i, j), white)

            else:

                cnt = 0

                for ii in range(-12):

                    for jj in range(-12):

                        if im.getpixel((i + ii, j + jj)) == black:

                            cnt += 1

                if cnt >= 7:

                    im.putpixel((i, j), black)

    return im


# 图片数据二值化

def two_value(code_data):

    table = []

    for i in code_data:

        if i < 140:  # 二值化分界线140

            table.append(0)

        else:

            table.append(1)

    return table


# 图片预处理



def pre_img(img):

    img = covergrey(img)  # 去色

    img = clearedge(img)  # 去边

    img = clearline(img)  # 去线

    img = del_noise(img)  # 去噪

    return img


# 处理图片数据

def data_img(img):

    code_data = []  # 验证码数据列表

    for i in range(4):  # 切割验证码

        x = 5 + i * 18  # 可用PS确定图片切割位置

        code_data.append(img.crop((x, 9, x + 1833)).getdata())

        code_data[i] = two_value(code_data[i])  # 二值化数据

    return code_data


# 验证码识别

def identify(data):

    code = ['']*4  # 验证码字符列表

    diff_min = [432]*4  # 初始化最小距离--不符合的数据点个数(共120数据点)

    for char in chars:  # 遍历验证码字符(每个字符比较一次4个验证码)

        diff = [0]*4  # 各验证码差距值(每个字符判断前重置此距离)

        for i in range(4):  # 计算四个验证码

            for j in range(432):  # 逐个像素比较验证码特征

                if data[i][j] != chars[char][j]:

                    diff[i] += 1  # 距离+1

        for i in range(4):

            if diff[i] < diff_min[i]:  # 比已有距离还要小(更加符合)

                diff_min[i] = diff[i]  # 刷新最小距离

                code[i] = char  # 刷新最佳验证码

    return ''.join(code)  # 输出结果



def predict(imgs):

    code = ''

    img = imgs.read()

    img = image.open(io.BytesIO(img))

    img = pre_img(img)  # 预处理图片

    data = data_img(img)  # 获取图片数据

    code = identify(data)  # 识别验证码

    return code



def apiReply(replycode=200):

    return {

        "isBase64Encoded"False,

        "statusCode": code,

        "headers": {'Content-Type''application/json'"Access-Control-Allow-Origin""*"},

        "body": json.dumps(reply, ensure_ascii=False)

    }



def main_handler(eventcontext):

    main_start = time.time()

    flag = True if 'image' in event['queryString'else False

    code = predict(event['queryString']['image']if 'image' in event['queryString'else '无效的请求'

    return apiReply({

        'ok': flag,

        'code': code,

        'spendTime'str(time.time()-main_start)

    })



老规矩,

先捋一下整个云函数的流程。


 
   
   
 
def main_handler ( event , context ) :
main_start
= time . time ( )
flag
= True if 'image' in event [ 'queryString' ] else False
code
= predict ( event [ 'queryString' ] [ 'image' ] ) if 'image' in event [ 'queryString' ] else '无效的请求'
return apiReply ( {
'ok' : flag ,
'code' : code ,
'spendTime' : str ( time . time ( ) - main_start )
} )


首先,我们通过event事件

拿到api请求的验证码image数据,


然后判断一下image参数是否存在,

若不存在就返回请求无效的提示


 
   
   
 
def predict ( imgs ) :
code
= ''
img
= imgs . read ( )
img
= image . open ( io . BytesIO ( img ) )
img
= pre_img ( img ) # 预处理图片
data
= data_img ( img ) # 获取图片数据
code
= identify ( data ) # 识别验证码
return code


如果image请求参数存在

就调用 predict 函数解析识别验证码,


流程如下


  1. 读取验证码图像

  2. 验证码图像预处理

  3. 识别处理后的验证码

 
   
   
 
# 图片预处理
def
pre_img ( img ) :
img
= covergrey ( img ) # 去色
img
= clearedge ( img ) # 去边
img
= clearline ( img ) # 去线
img
= del_noise ( img ) # 去噪
return img


我们来看一下图像预处理过程


  1. 将验证码去色,转为灰度图

  2. 去除验证码黑色边框

  3. 去除验证码干扰线

  4. 去除验证码噪点

 
   
   
 
#字符特征
chars
= {
'1' : [ 1 , 1 , 1 , 0 , 1 , ... ] ,
'2' : [ 1 , 0 , 0 , 1 , 0 , ... ] ,
'3' : [ 0 , 1 , 0 , 0 , 1 , ... ] ,
# 其他字符特征
... }

# 验证码识别
def
identify ( data ) :
code
= [ '' ] * 4 # 验证码字符列表
diff_min
= [ 432 ] * 4 # 初始化最小距离 -- 不符合的数据点个数(共 120 数据点)
for char in chars : # 遍历验证码字符(每个字符比较一次 4 个验证码)
diff
= [ 0 ] * 4 # 各验证码差距值 ( 每个字符判断前重置此距离 )
for i in range ( 4 ) : # 计算四个验证码
for j in range ( 432 ) : # 逐个像素比较验证码特征
if data [ i ] [ j ] != chars [ char ] [ j ] :
diff
[ i ] += 1 # 距离 + 1
for i in range ( 4 ) :
if diff [ i ] < diff_min [ i ] : # 比已有距离还要小(更加符合)
diff_min
[ i ] = diff [ i ] # 刷新最小距离
code
[ i ] = char # 刷新最佳验证码
return '' . join ( code ) # 输出结果

PS:

文章中的字符特征 chars 并不完整,

你可能需要自行提取所有特征


最后来看一下验证码的识别过程


这里我们直接简单粗暴地

取处理后图像数据的所有像素点

作为字符的特征


所谓大道至简,哈哈哈


然后将每个待识别字符处理后图像的数据

与所有字符的特征逐个比较,

取最相似的那个字符作为识别结果




正确的识别结果


嗯,没什么问题的话,

你就可以得到正确的识别结果了。


第三步:上线发布云函数、添加Api网关触发器

参见我之前的系列文章

《万物皆可Serverless之使用SCF+COS快速开发全栈应用》


三、文章最后

当然,

以上只是以一个简单的验证码识别为例,

对于一些比较复杂的验证码,


你也可以使用Tensorflow,Pytorch等

深度学习计算框架搭建、训练模型,


然后将训练好的模型借助无服务器云函数

快速上线发布使用。


如果你已经有本地的验证码识别算法,

不妨尝试将它上线发布成一个云函数

方便随时调用。


以上,Have fun.