KNN算法实战:验证码的识别

标签: 数据 机器学习 算法 | 发表时间:2019-02-15 12:25 | 作者:标点符
出处:https://www.biaodianfu.com

识别验证码的方式很多,如tesseract、SVM等。前面的几篇文章介绍了 KNN算法,今天主要学习的是如何使用KNN进行验证码的识别。

数据准备

本次实验采用的是CSDN的验证码做演练,相关的接口:https://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325

目前接口返回的验证码共2种:

  •   纯数字、干扰小的验证码,简单进行图片去除背景、二值化和阈值处理后,使用kNN算法即可识别。
  •  字母加数字、背景有干扰、图形字符位置有轻微变形,进行图片去除背景、二值化和阈值处理后,使用kNN算法识别

这里选择第二种进行破解。由于两种验证码的图片大小不一样,所以可以使用图片大小来判断哪个是第一种验证码,哪个是第二种验证码。

下载验证码

import requests
import uuid
from PIL import Image
import os
url = "http://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325"
for i in range(1000):
    resp = requests.get(url)
    filename = "./captchas/" + str(uuid.uuid4()) + ".png"
    with open(filename, 'wb') as f:
        for chunk in resp.iter_content(chunk_size=1024):
            if chunk:  # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
        f.close()
    im = Image.open(filename)
    if im.size != (70, 25):
        im.close()
        os.remove(filename)
    else:
        print(filename)

分割字符

下载过后,就需要对字母进行分割。分割字符还是一件比较麻烦的工作。

灰度化

将彩色的图片转化为灰度图片,便于后面的二值化处理,示例代码:

from PIL import Image

file = ".\\captchas\\0a4a22cd-f16b-4ae4-bc52-cdf4c081301d.png"
im = Image.open(file)
im_gray = im.convert('L')
im_gray.show()

处理前:

处理后:

二值化

灰度化以后,有颜色的像素点为0-255之间的值。二值化就是将大于某个值的像素点都修改为255,小于该值的修改为0,示例代码:

from PIL import Image
import numpy as np
file = ".\\captchas\\0a4a22cd-f16b-4ae4-bc52-cdf4c081301d.png"
im = Image.open(file)
im_gray = im.convert('L')
# im_gray.show()

pix = np.array(im_gray)
print(pix.shape)
print(pix)

threshold = 100 #阈值

pix = (pix > threshold) * 255
print(pix)

out = Image.fromarray(pix)
out.show()

二值化输出的结果:

去除边框

从二值化输出的结果可以看到除了字符,还存在边框,在切割字符前还需要先将边框去除。

border_width = 1
new_pix = pix[border_width:-border_width,border_width:-border_width]

字符切割

由于字符与字符间没有存在连接,可以使用比较简单的“投影法”进行字符的切割。原理就是将二值化后的图片先在垂直方向进行投影,根据投影后的极值来判断分割边界。分割后的小图片再在水平方向进行投影。

代码实现:

def vertical_image(image):
    height, width = image.shape
    h = [0] * width
    for x in range(width):
        for y in range(height):
            s = image[y, x]
            if s == 255:
                h[x] += 1
    new_image = np.zeros(image.shape, np.uint8)
    for x in range(width):
        cv2.line(new_image, (x, 0), (x, h[x]), 255, 1)
    cv2.imshow('vert_image', new_image)
    cv2.waitKey()
cv2.destroyAllWindows()

整体代码

from PIL import Image
import cv2
import numpy as np
import os
import uuid


def clean_bg(filename):
    im = Image.open(filename)
    im_gray = im.convert('L')
    image = np.array(im_gray)
    threshold = 100  # 阈值
    pix = (image > threshold) * 255
    border_width = 1
    new_image = pix[border_width:-border_width, border_width:-border_width]
    return new_image


def get_col_rect(image):
    height, width = image.shape
    h = [0] * width
    for x in range(width):
        for y in range(height):
            s = image[y, x]
            if s == 0:
                h[x] += 1
    col_rect = []
    in_line = False
    start_line = 0
    blank_distance = 1
    for i in range(len(h)):
        if not in_line and h[i] >= blank_distance:
            in_line = True
            start_line = i
        elif in_line and h[i] < blank_distance:
            rect = (start_line, i)
            col_rect.append(rect)
            in_line = False
            start_line = 0
    return col_rect


def get_row_rect(image):
    height, width = image.shape
    h = [0] * height
    for y in range(height):
        for x in range(width):
            s = image[y, x]
            if s == 0:
                h[y] += 1
    in_line = False
    start_line = 0
    blank_distance = 1
    row_rect = (0, 0)
    for i in range(len(h)):
        if not in_line and h[i] >= blank_distance:
            in_line = True
            start_line = i
        elif in_line and i == len(h)-1:
            row_rect = (start_line, i)
        elif in_line and h[i] < blank_distance:
            row_rect = (start_line, i)
            break
    return row_rect


def get_block_image(image, col_rect):
    col_image = image[0:image.shape[0], col_rect[0]:col_rect[1]]
    row_rect = get_row_rect(col_image)
    if row_rect[1] != 0:
        block_image = image[row_rect[0]:row_rect[1], col_rect[0]:col_rect[1]]
    else:
        block_image = None
    return block_image


def clean_bg(filename):
    im = Image.open(filename)
    im_gray = im.convert('L')
    image = np.array(im_gray)
    threshold = 100  # 阈值
    pix = (image > threshold) * 255
    border_width = 2
    new_image = pix[border_width:-border_width, border_width:-border_width]
    return new_image

def split(filename):
    image = clean_bg(filename)
    col_rect = get_col_rect(image)
    for cols in col_rect:
        block_image = get_block_image(image, cols)
        if block_image is not None:
            new_image_filename = 'letters/' + str(uuid.uuid4()) + '.png'
            cv2.imwrite(new_image_filename, block_image)


if __name__ == '__main__':
    for filename in os.listdir('captchas'):
        current_file = 'captchas/' + filename
        split(current_file)
        print('split file:%s' % current_file)

数据集准备

在完成图像切割后,需要做将切分的字母建立由标签的样本。即将切分后的字符梳理到正确的分类中。比较常见的方式是人工梳理。

由于图像比较多,这里使用使用Tesseract-OCR进行识别。

官方项目地址: https://github.com/tesseract-ocr/tesseract

Windows安装包地址: https://github.com/UB-Mannheim/tesseract/wiki

Tesseract-OCR的安装

下载完安装包后,直接运行安装即可,比较重要的是环境变量的设置。

  • 将安装目录(D:\Program Files (x86)\Tesseract-OCR)添加进PATH
  • 新建TESSDATA_PREFIX系统变量,值为tessdata 文件夹的路径(D:\Program Files (x86)\Tesseract-OCR\tessdata)
  • 安装Python包pytesseract(pip install pytesseract)

Tesseract-OCR的使用

使用起来非常的简单,代码如下:

from PIL import Image
import pytesseract
import os


def copy_to_dir(filename):
    image = Image.open(filename)
    code = pytesseract.image_to_string(image, config="-c tessedit"
                                                     "_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
                                                     " --psm 10"
                                                     " -l osd"
                                                     " ")
    if not os.path.exists("dataset/" + code):
        os.mkdir("dataset/" + code)
    image.save("dataset/" + code + filename.replace("letters", ""))
    image.close()


if __name__ == "__main__":
    for filename in os.listdir('letters'):
        current_file = 'letters/' + filename
        copy_to_dir(current_file)
        print(current_file)

由于Tesseract-OCR识别的准确率非常的低,完全不能使用,放弃~,还是需要手工整理。

图片尺寸统一

在完成人工处理后,发现切割后的图片大小不一。在字符识别前需要对图片进行的尺寸进行统一。

具体实现方法:

import cv2

def image_resize(filename):
    img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) #读取图片时采用单通道
    print(img)
    if img.shape[0] != 10 or img.shape[1] != 6:
        img = cv2.resize(img, (6, 10), interpolation=cv2.INTER_CUBIC)
        print(img)
        cv2.imwrite(filename, img)

使用cv2.resize时,参数输入是 宽×高×通道,这里使用的时单通道的,interpolation的选项有:

  • INTER_NEAREST 最近邻插值
  • INTER_LINEAR 双线性插值(默认设置)
  • INTER_AREA 使用像素区域关系进行重采样。 它可能是图像抽取的首选方法,因为它会产生无云纹理的结果。 但是当图像缩放时,它类似于INTER_NEAREST方法。
  • INTER_CUBIC 4×4像素邻域的双三次插值
  • INTER_LANCZOS4 8×8像素邻域的Lanczos插值

另外为了让数据更加便于利用,可以将图片再进行二值化的归一。具体代码如下:

import cv2
import numpy as np

def image_normalize(filename):
    img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) #读取图片时采用单通道
    if img.shape[0] != 10 or img.shape[1] != 6:
        img = cv2.resize(img, (6, 10), interpolation=cv2.INTER_CUBIC)
    normalized_img = np.zeros((6, 10))  # 归一化
    normalized_img = cv2.normalize(img, normalized_img, 0, 1, cv2.NORM_MINMAX)
    cv2.imwrite(filename, normalized_img)

归一化的类型,可以有以下的取值:

  • NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
  • NORM_INF:此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C-范数(绝对值的最大值)
  • NORM_L1 :  归一化数组的L1-范数(绝对值的和)
  • NORM_L2: 归一化数组的(欧几里德)L2-范数

字符识别

字符图片 宽6个像素,高10个像素 ,理论上可以最简单粗暴地可以定义出60个特征:60个像素点上面的像素值。但是显然这样高维度必然会造成过大的计算量,可以适当的降维。比如:

  • 每行上黑色像素的个数,可以得到10个特征
  • 每列上黑色像素的个数,可以得到6个特征

from sklearn.neighbors import KNeighborsClassifier
import os
from sklearn import preprocessing
import cv2
import numpy as np
import warnings
warnings.filterwarnings(module='sklearn*', action='ignore', category=DeprecationWarning)


def get_feature(file_name):
    img = cv2.imread(file_name, cv2.IMREAD_GRAYSCALE)  # 读取图片时采用单通道
    height, width = img.shape

    pixel_cnt_list = []
    for y in range(height):
        pix_cnt_x = 0
        for x in range(width):
            if img[y, x] == 0:  # 黑色点
                pix_cnt_x += 1

        pixel_cnt_list.append(pix_cnt_x)

    for x in range(width):
        pix_cnt_y = 0
        for y in range(height):
            if img[y, x] == 0:  # 黑色点
                pix_cnt_y += 1

        pixel_cnt_list.append(pix_cnt_y)

    return pixel_cnt_list


if __name__ == "__main__":
    test = get_feature("dataset/K/04a0844c-12f2-4344-9b78-ac1d28d746c0.png")
    category = []
    features = []
    for dir_name in os.listdir('dataset'):
        for filename in os.listdir('dataset/' + dir_name):
            category.append(dir_name)
            current_file = 'dataset/' + dir_name + '/' + filename
            feature = get_feature(current_file)
            features.append(feature)
            # print(current_file)
    le = preprocessing.LabelEncoder()
    label = le.fit_transform(category)

    model = KNeighborsClassifier(n_neighbors=1)
    model.fit(features, label)
    predicted= model.predict(np.array(test).reshape(1, -1))
    print(predicted)
    print(le.inverse_transform(predicted))

这里直接使用了sklearn中的KNN方法,如需了解更多见: 使用 Scikit-learn 进行 KNN 分类

The post KNN算法实战:验证码的识别 appeared first on 标点符.

相关 [knn 算法 验证码] 推荐:

KNN算法实战:验证码的识别

- - 标点符
识别验证码的方式很多,如tesseract、SVM等. 前面的几篇文章介绍了 KNN算法,今天主要学习的是如何使用KNN进行验证码的识别. 本次实验采用的是CSDN的验证码做演练,相关的接口:https://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325.

数据挖掘-基于贝叶斯算法及KNN算法的newsgroup18828文档分类器的JAVA实现(上)

- - CSDN博客推荐文章
本文主要研究基于贝叶斯算法及KNN算法的newsgroup18828文档分类器的设计及实现,数据预处理、贝叶斯算法及KNN算法实现工程源码下载见:. 对newsgroup文档集进行预处理,提取出30095 个特征词. 计算每篇文档中的特征词的TF*IDF值,实现文档向量化,在KNN算法中使用. 用JAVA实现了KNN算法及朴素贝叶斯算法的newsgroup文本分类器.

基于KNN的文本分类实战

- - 樂天笔记
本文讲述如何使用scikit-learn的KNN工具对文本进行分类. K-近邻算法,简称KNN(k-Nearest Neighbor),是一个相当简单的分类/预测算法. 其主要思想就是,选取与待分类/预测数据的最相似的K个训练数据,通过对这K个数据的结果或者分类标号取平均、取众数等方法得到待分类/预测数据的结果或者分类标号.

使用 Scikit-learn 的进行 KNN 分类

- - 标点符
最近邻(KNN)是一种非常简单、易于理解、通用性强的机器学习算法,广泛应用于金融、医疗、政治、手写检测、图像识别、视频识别等领域. 在信用评级中,金融机构会预测客户的信用评级. 在贷款支付中,银行机构将预测贷款是否安全或有风险. 在政治学中,将潜在选民分为两类,要么投票,要么不投票. 上一篇《 K-近邻算法KNN学习笔记》主要讲解的是KNN的理论内容,今天主要学习怎么用KNN进行实战.

java 验证码

- - ITeye博客
// 创建字体,字体的大小应该根据图片的高度来定. // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到. // randomCode用于保存随机产生的验证码,以便用户登录后进行验证. // 随机产生codeCount数字的验证码. // 得到随机产生的验证码数字. // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同.

验证码新趋势:NuCaptcha推出动态验证码

- Mac.Baby - 36氪
曾开发过提供视频验证码服务的加拿大创业公司NuCaptcha刚刚发布了动态验证码,可为网站提供新的安全保证,有望为验证码市场带来新气象. 验证码是人们在网站上最常见的安全问题,只要是上网的人都会遇到被要求辨认和输入数字或者单词的情况. 网站通过验证码来区分人和恶意程序. 而我们所见的大部分的验证码都是基于文字的.

10种用户友好的验证码

- Liang - cnBeta.COM
众所周知,验证码就是帮助我们确定答案不是由电脑自己生成的. 今天我推荐给大家10种用户体验绝佳、令人意想不到的验证码设计风格.

使用 PIL 生成验证码图片

- HarryHan - python.cn(jobs, news)
现在的网页中,为了防止机器人提交表单,图片验证码是很常见的应对手段之一. 这里就不详细介绍了,相信大家都遇到过. 现在就给出用Python的PIL库实现验证码图片的代码. _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z. _upper_cases = _letter_cases.upper() # 大写字母.

response生成图片验证码

- - ITeye博客
服务器对客户端浏览器做出的响应被封装成一个HttpResponse对象. 要对浏览器进行操作,只需要操作HttpRespones对象,通过HttpServletResponse.getWriter()获得PrintWriter,该对象为outputStream的子类.     本例将使用Servlet输出图片验证码.

验证码的现在与未来

- Yishen - 爱范儿 · Beats of Bits
”验证码“( CAPTCHA )其实并不是各位网友总是在不同网站上看到的难以辨认的字母组合的代名词,而是“全自动区分计算机和人类的图灵测试”的俗称,顾名思义,它的作用是区分计算机和人类. 在 CAPTCHA 测试中,作为服务器的计算机会自动生成一个问题由用户来解答. 这个问题可以由计算机生成并评判,但是必须只有人类才能解答.