基于Python的KNN数字验证码识别

一、主要内容

本项目基于Python爬虫爬取验证码图片,对图片进行去噪、分割,通过KNN算法训练模型,实现验证其准确率。

二、系统流程

首先从指定的网页中爬取验证码图片数据,然后对数据进行一个去噪和分割的处理,再将数据集分割为训练集和测试集,运用训练集去拟合并保存模型,最后将保存的模型应用于测试集,对模型进行一个评估。

基于Python的KNN数字验证码识别_第1张图片

三、模块实现

本项目包括4个模块:数据爬取、去噪与分割、模型训练及保存、准确率验证,下面分别介绍各模块的功能及相关代码。

(一)数据爬取

本部分用resquest库爬取抓取验证码100张,并做好标注。相关代码如下:

from __future__ import unicode_literals
import requests
import time

if __name__ == "__main__":
    #获取图片总数设置
    number = 100
    for num in range(number):
        img_url = 'http://run.hbut.edu.cn/Account/GetValidateCode?time=1644928431690'
        data = {'timestamp':unicode(long(time.time()*1000))}  #获取时间,并且转换成long类型,再进行解码
        #print(img_url)
        res = requests.get(img_url,params = data)
        #一个获取图片资源请求
        with open("./download_image/%d.jpg" % num,"wb") as f:
            #获取图片资源位置并将图片保存在本地
            f.write(res.content)
            print("%d.jpg" % num + "获取成功")

其中这部分的代码需要Python2.7的配置,因为long()函数只有在Python2.7中才有,在Python3.8中不存在这个函数。这里将获取的图片总数设置为100张,书中是1200张,由于数据量太大故先用100张进行一个测试。

爬取图片示例如下图所示:

基于Python的KNN数字验证码识别_第2张图片

 由于后续需要进行分割所以预先对得到的图片名进行一个标注,即对应将图片名标注为其对应的验证码,标注完成如下图所示:

基于Python的KNN数字验证码识别_第3张图片

 (二)去噪与分割

转换成灰度图后对图片进行分割,去掉边框和部分噪声,分成4张图,统计每张图的灰度直方图(自己设置bins),找到第二大对应的像素范围(即某一像素范围内像素数第二多,对应的像素范围,像素最多的应该是白色空白处),取像素范围中位数模式,然后保留(mode±biases)的像素,去除大部分噪声。

1.  去除背景噪声

import math
import os
import cv2

#去除背景噪声
def del_noise(im_cut):
    bins = 16
    # 函数返回大于或等于一个给定数字的最小整数
    num_gray = math.ceil(256/bins)
    # [im_cut]为输入的图像,[0]表示计算直方图的通道,None表示处理整幅图像,[bins]表示这个直方图分成多少份(这里分为了16个直方柱),[0, 256]表示直方图能表示像素值从0到256的像素
    hist = cv2.calcHist([im_cut],[0],None,[bins],[0,256])
    lists = []
    for i in range(len(hist)):   #len(hist)都为16
        #print hist[i][0]
        lists.append(hist[i][0])  #lists里面有16个数
    second_max = sorted(lists)[-2]    #先从小到大排序再取倒数第二个数
    #查找第2多像素(对应的是验证码),最多的是空白处
    bins_second_max = lists.index(second_max)
    #取像素范围中位数模式,保留(mode±biases)的像素
    mode = (bins_second_max + 0.5) * num_gray
    for i in range(len(im_cut)):   #len(im_cut)都为23,对应图片的宽
        for j in range(len(im_cut[0])):    #len(im_cut[0])为48,对应图片的长
            #print im_cut[i][j]
            if im_cut[i][j] < mode-15 or im_cut[i][j] > mode+15:
                #不在中位数附近的设为白(255)
                im_cut[i][j] = 255
    return im_cut    #得到去噪后的图片

以数据集中的验证码图片0290.jpg为例:

  • cv2.calcHist()函数得到的直方图为
im_cut = cv2.imread('./img/0290.jpg')
hist = cv2.calcHist([im_cut], [0], None, [16], [0, 256])
# plt.plot(hist)
# plt.show()
print(len(hist))
print(hist)

得到hist的长度是16,hist是一个16行1列的数组,所以hist[i][0]分别对应数组里1列的每个数

基于Python的KNN数字验证码识别_第4张图片

 2.  图片分割

分割100张已标注好的图片,得到400张子图片(因为每一张验证码中有4个数字)。相关代码如下:

def cut_image(image,num,img_name):
    #image = cv2.imread('./img/8.jpg')
    #将BGR格式图片转换成灰度图片
    im = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    im_cut_0 = im[4:18, 7:16]     #每个数字在图片中占的宽和长
    im_cut_1 = im[4:18, 16:25]
    im_cut_2 = im[4:18, 25:34]
    im_cut_3 = im[4:18, 34:43]
    im_cut = [im_cut_0,im_cut_1,im_cut_2,im_cut_3]
    for i in range(4):
        im_temp = del_noise(im_cut[i])  #对分割后的每个数字图片进行去噪处理
        #将图片分割为4个
        cv2.imwrite('./img_train_cut/'+str(num)+'_'+str(i)+'_'+img_name[i]+'.jpg',im_temp)

if __name__ == '__main__':
    img_dir = './img'
    img_name = os.listdir(img_dir)   #列出文件夹下所有的目录与文件
    #print(img_name[1])
    for i in range(len(img_name)):  #[0,100)
        path = os.path.join(img_dir,img_name[i])  #合成得到每张图片的路径
        path = path.replace('\\', '/')  #将path中的'\'替换成'/'
        image = cv2.imread(path)   #根据路径读取图片
        name_list = list(img_name[i])[:4]   #获取该图片名的前四个字符(即图片对应的验证码)
        #name = ''.join(name_list)
        cut_image(image,i,name_list)   #进行图片分割
        print("图片%s分割完成" %i)
        print(u'*****图片分割预处理完成!*****')

分割图片成功示例如下图所示,其中图片名称中的3个数字分别表示第几+1张图的第几个数是什么,例如0_3_4表示第1张图的第3个数是4。

基于Python的KNN数字验证码识别_第5张图片

 (三)模型训练及保存

处理数据后拆分训练集和测试集,训练并保存,模型被保存后可以被重新使用,也可以移植到其他环境中使用。相关代码如下:

import numpy as np
from sklearn import neighbors
import os
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import joblib
import cv2

if __name__ == '__main__':
    #读入数据
    data = []
    labels = []
    img_dir = './img_train_cut'
    img_name = os.listdir(img_dir) #列出文件夹下所有目录与文件
    # number = ['0','1','2','3','4','5','6','7','8','9']
    for i in range(len(img_name)):  #[0,400)
        path = os.path.join(img_dir,img_name[i])
        path = path.replace('\\', '/')
        #cv2读入的图片是RGB三维的,转换成灰度图,将图片转换成一维
        image = cv2.imread(path)
        im = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
        image = im.reshape(-1)
        data.append(image)      #添加到data列表中
        y_temp = img_name[i][-5]   #获取图片名中倒数第5个字符,对应图片中的数
        labels.append(y_temp)    #添加到labels列表中
    #标签标准化
    y = LabelBinarizer().fit_transform(labels)
    x = np.array(data)    #将列表转化为数组
    y = np.array(y)
    # print(x.shape,y.shape)
    #拆分训练数据与测试数据,其中训练集320个数据,测试集80个数据
    x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.2)
    #训练KNN分类器
    clf = neighbors.KNeighborsClassifier()
    clf.fit(x_train,y_train)
    #保存分类器模型
    joblib.dump(clf,'./knn.pkl')
    #测试结果打印
    pre_y_train = clf.predict(x_train)
    pre_y_test = clf.predict(x_test)
    class_name = ['class0','class1','class2','class3','class4','class5','class6','class7','class8','class9']
    print(classification_report(y_train,pre_y_train,target_names=class_name))
    print(classification_report(y_test, pre_y_test, target_names=class_name))

 训练集的结果如下图所示:

基于Python的KNN数字验证码识别_第6张图片

 测试集的结果如下图所示:

基于Python的KNN数字验证码识别_第7张图片

 可以看到识别的准确度还是挺高的。

(四)准确率验证

将所有代码整合,用验证码原图(4个数字)来测试准确率,相关代码如下:

from __future__ import division
import cv2
import math
import numpy as np
import os
import joblib

def del_noise(im_cut):
    bins = 16
    num_gray = math.ceil(256/bins)
    #函数返回大于或等于一个给定数字的最小整数
    hist = cv2.calcHist([im_cut],[0],None,[bins],[0,256])
    lists = []
    for i in range(len(hist)):   #len(hist)都为16
        #print hist[i][0]
        lists.append(hist[i][0])  #lists里面有16个数
    second_max = sorted(lists)[-2]
    #查找第二多像素,最多的是验证码空白
    bins_second_max = lists.index(second_max)
    #取像素范围中位数模式,保留(mode±biases)的像素
    mode = (bins_second_max + 0.5) * num_gray
    for i in range(len(im_cut)):   #len(im_cut)都为23(宽)
        for j in range(len(im_cut[0])):    #len(im_cut[0])为48(长)
            #print im_cut[i][j]
            if im_cut[i][j] < mode-15 or im_cut[i][j] > mode+15:
                #不在中位数附近的设为白(255)
                im_cut[i][j] = 255
    return im_cut

def predict(image,img_name):
    # image = cv2.imread('./img/8.jpg')
    # 将BGR格式图片转换成灰度图片
    im = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    im_cut_0 = im[4:18, 7:16]  # 每个数字在图片中占的宽和长
    im_cut_1 = im[4:18, 16:25]
    im_cut_2 = im[4:18, 25:34]
    im_cut_3 = im[4:18, 34:43]
    im_cut = [im_cut_0, im_cut_1, im_cut_2, im_cut_3]
    pre_text = []
    for i in range(4):
        #图片转换成一维后,再转换成二维的输入变量x
        im_temp = del_noise(im_cut[i])
        # print(type(im_temp))
        image = im_temp.reshape(-1)   #转化成1行
        # print(image.shape)
        tmp = []
        tmp.append(list(image))
        x = np.array(tmp)
        pre_y = clf.predict(x)       #使用训练的模型进行预测
        pre_y = np.argmax(pre_y[0])   #得到预测出的验证码数
        pre_text.append(str(pre_y))
        # print(pre_text)
    pre_text = ''.join(pre_text)    #将预测得到的4个验证码数连起来与验证码图片名对比
    if pre_text != img_name:
        print('lable: %s' %(img_name),'predict: %s' %(pre_text),'\t','false')
        return 0
    else:
        print('lable: %s' %(img_name),'predict: %s' %(pre_text))
        return 1

if __name__ == '__main__':
    img_dir = './img_test'
    img_name = os.listdir(img_dir)
    right = 0
    global clf
    clf = joblib.load('knn.pkl')  #获取训练的KNN模型
    for i in range(len(img_name)):
        path = os.path.join(img_dir,img_name[i])
        path = path.replace('\\', '/')
        image = cv2.imread(path)
        name_list = list(img_name[i])[:4]   #将图片名的前4个数组成一个列表,例['0','2','9','0']
        name = ''.join(name_list)    #再将列表中的4个数组成一个字符串,例如'0290'
        pre = predict(image,name)    #进行验证,验证正确返回1,验证错误返回0
        right += pre
    accuracy = (right/len(img_name)) * 100   #计算准确率,(验证成功的图片数/所有图片数)*100
    print(u'准确率为:%s%%,一共%s张验证码,正确:%s,错误:%s'%(accuracy,len(img_name),right,len(img_name)-right))

这里选择了20张验证码图片进行验证,输出结果如下图所示:

基于Python的KNN数字验证码识别_第8张图片

可以看到,可能是验证的图片基数太小,得到的准确率并不理想,只有50%,若选择更多的验证码图片进行验证,得到的结果可能会更加令我们满意。

本次实验项目是基于李永华老师编著的《Al源码解读——机器学习案例》进行学习编写的,书中有一些错误的地方也在此进行了修改,收获良多,其他项目我也会仔细学习研究,若有想法也会继续分享给大家~

你可能感兴趣的:(Python实战项目,python,数据挖掘,机器学习)