新手详细教程:python3基于SVM识别学校登录页面验证码

零、准备工作

1、python3+
2、工具:pycharm
3、模块:os、sklearn、urllib.request、numpy、pandas、PIL(建议下载anaconda)
4、一颗对输入验证码感到厌烦的心
一、批量得到验证码图片
import urllib.request
def download(n):
    for i in range(1, n+1):
        img_src = 'http:xxxxx'
        i = str(i)             
        urllib.request.urlretrieve(img_src, './jpg/' + i + '.jpg') # 图片可能不是jpg格式,请注意
 想要识别验证码,首先要有验证码:使用循环访问n次验证码网址,下载n张二维码到当前同级目录
下的jpg文件夹中。
 得到验证码的生成网址 要在浏览器中使用开发者工具,即按F12。在右边栏中可以找到服务器发给浏
览器的验证码,然后 copy一下验证码网址就行。



下载500例,可以看到验证码是彩色的,而且有奇奇怪怪的线条,我们接下来就要搞定它们



二、转成黑白图片--二值化
def getTable(threshold=140):
    table = []
    for i in range(256):
        table.append(0) if i < threshold else table.append(1)
    return table
 getTable()函数返回一个列表[0,0,0,....,1,1,1],用来分辨像素的工具,比较白的颜色二值化为1,比较黑的颜色二值化为0
from PIL import Image
image = Image.open("jpg/418.jpg")
imgry = image.convert('L')    # 转化为灰度图
table = get_bin_table()       # table是list[]
out = imgry.point(table, '1') # out是image类型
out.show()
out的图片如上所示,可见其变成了黑白二色,这代表out是由0和1构成的,黑色表示0,白色表示1。
这样更容易统计得到图片的特征,比如数字3 由几个0,1构成,进而得到各个数字的规律。

三、去除线条--除噪点


 可以看到,验证码图片中通常会有设置的干扰物,这会影响SVM训练时的效果。但是可以发现的是,
这种验证码图片的 噪点 并不多,而且处于边缘位置。需要说明的是,SVM训练的是一小块字符截图,所以
截图位置外的噪点皆可漠视,因此我 决定暂时不去除噪点,先看看截图效果。

四、得到字符截图



 在pycharm中放大图片,开启网格,可以看到一些细节:z离左边缘4个像素;3离顶部4个像素;字
符之间的距离是2或3个 像素;所有字符离底边缘6个像素。看起来不好办啊!字符之间的距离竟然是浮动
的。然而仔细观察后我发现,字符似乎最大长 度都 是高12px,最大宽度10px。也就是说12*10的区域可
以恰好包含z,而不包含3。 对于字符z,截下(4,4,14,16);对于字符3,截下(14,4,24,16);以此类推。

def get_crop_imgs(img):
        child_img_list = []
        for i in range(4):
            x = 4 + i * 10                   # 第1个字符的截图位置是(4,4,14,16)
            y = 4
            child_img = img.crop((x, y, x + 10, y + 12))
            child_img_list.append(child_img) # child_img是image类型,child_img_list是含image类型元素的列表
    return child_img_list
list=get_crop_imgs(img)
list[0].show()


这就是截下来的字符,看得出噪点依然存在,但是不用急,大部分的字符截图都没有噪点,

五、保存字符截图

 每一张验证码都会截下四张字符截图,n张验证码会产生4n张字符截图,需要注意的一点是,字符截图
数不可太多, 因为 这是监督学习,是需要人为标注的,也就是你要手动分类,数字1归一类,数字2归一类,
等等。分别用文件夹储存。 经过后来的尝试我发现每类20个就可以训练得不错,这侧面反映这确实是很简单
的验证码。
def saveSpiltPicture(self, n):
        for i in range(0, n):
            i = i + 1                   # 循环为0时,i=1;循环为1时,i=2;方便计算
            image = Image.open('./jpg/' + str(i) + '.jpg')
            imgry = image.convert('L')  # 转化为灰度图

            out = imgry.point(self.table, '1')
            img_list = get_crop_imgs(out)

            img_list[0].save('./bmp/' + str(4 * i + 1) + '.bmp')
            img_list[1].save('./bmp/' + str(4 * i + 2) + '.bmp')
            img_list[2].save('./bmp/' + str(4 * i + 3) + '.bmp')
            img_list[3].save('./bmp/' + str(4 * i + 4) + '.bmp')

六、手动分类字符截图
首先要统计字符频率:共11类:123abcmnxvz。创建文件夹,命名为char,里面再创建11个文件夹,
分别以类名命名。
手动将bmp文件夹下的截图分类, 每类二三十个就可以了。

这里7.bmp对应2.jpg内的第三个字符。

七、获得图片特征
(1)文件操作
 为什么又命名jpg文件夹,又命名bmp文件,甚至还有char文件夹呢?
 因为我要使用文件操作,自动对char下的每个文件夹下的每个图片使用“获取特征函数”,省时省力。
import os
def sortTable():
    list = []
    files_list = os.listdir('char')     # files_list->['1','2',...'x','z']
    num = 0                             # 第num张截图
    for i in files_list:                # i是第几个文件夹
        files = os.listdir('char/' + i) # 返回的files->['7.bmp','xx.bmp',...]
        for j in range(0, len(files)):  # j是第几张图片
            image = Image.open('char/' + i + '/' + files[j]) # files[0]->'7.bmp'
            list.append(get_feature(image)) # 使用获取特征函数返回list类型加在list后,所以list是二维数组
            list[num].append(i)         # 向list中的某list的尾加上文件名 *1
            num += 1                    #最后大list中含n个小list,每个小list都含有截图特征信息
    return list
     *1:为什么要加上文件名?打比方说,获得‘1’文件夹下某个字符‘1’的特征后,得再加一个特征,说前22个特征指向的
是字符‘1’,不然谁知道22个特征指向的是谁,是x还是z?所以新加的特征就是文件夹名。

(2)获取特征函数
def get_feature(img):
    """
    获取指定图片的特征值,
    一张截图为高12,宽10。统计各行的黑点,得到12个信息,统计各列的黑点,得到10个信息,用列表返回22个信息
    """
    pixel_cnt_list = []
    height = 12
    width = 10
    for y in range(height):
        pix_cnt_x = 0
        for x in range(width):
            if img.getpixel((x, y)) == 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.getpixel((x, y)) == 0:  # 黑色点
                pix_cnt_y += 1
        pixel_cnt_list.append(pix_cnt_y)

    return pixel_cnt_list
(3)保存数据集
import pandas as pd
def save():
 names = [x for x in range(1, 24)]
 list=sortTable()
 test = pd.DataFrame(list, columns=names) # test类型为Dataframe
 test.to_csv('data.txt')  # 保存数据集


 储存的好处是直接保存计算结果,调试时可以节省重复计算的时间,上图为data.txt的部分内
有23列, 表示23个特征, 末位特征表示此行指向什么字符,每行首位是行号。看得出虽然是4个字
符‘1’, 但是某些特征还是不同的。

八、SVM训练
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC
from sklearn.cross_validation import train_test_split
from sklearn.externals import joblib
def train():
    data = pd.read_csv("data.txt")
    clf = OneVsRestClassifier(SVC(kernel='linear'))

    X = data.ix[:, 1:23]                     # 选取所有行的1-22列
    y = np.array(data.ix[:, 23]).astype(str) # 选取所有行的第23列,类型转换

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3) #数据集分四类
    clf.fit(X_train, y_train)                # 训练ing
    y_pred = clf.predict(X_test)             # 测试ing

    rf = pd.DataFrame(list(zip(y_pred, y_test)), columns=['predicted', 'actual']) # rf类型是dataframe
    rf['correct'] = rf.apply(lambda r: 1 if r['predicted'] == r['actual'] else 0, axis=1)
    print(len(rf[rf['correct'] == 1]) / len(rf)) 
    joblib.dump(clf, "train_model.m")        # 保存训练模型


 test_size参数表示分组规模,0.3表示70%的data拿去训练,30%的data拿去测试。
 我的data大概有900多行的特征向量,test_size=0.99时,准确率不到60%,test_size=0.9时,准确率就接近100%了。
而且我的字符中m宽度有15个px,截图只截下半个多m,然而训练后可以准确识别出m,且不怎么受噪点影响,所以除噪
也没什么必要了。
 如果训练效果不好,可能是算法有问题,或是训练量不够。

九、使用训练模型识别验证码
def seePicture():
    clf = joblib.load("train_model.m")
    image = Image.open("jpg/340.jpg")
    imgry = image.convert('L')       # 转化为灰度图
    table = get_bin_table()
    out = imgry.point(table, '1')
    img_list = get_crop_imgs(out)
    for x in range(0, 4):
        list = get_feature(img_list[x])
        y_pred = clf.predict([list]) # predict接受二维数组参数
        print(len(y_pred))



识别成功,任务完成。

十、小结
 这是我第一次写博客,如有错误或不详尽之处请指出。大概50%是在前人的基础上完成的,
在研究的同时走了许多弯路,所以就写下这篇尽量完整详尽的博客(所以才有那么多注释),希
望所有人无论是新手都能看的明白。至于文章中的一些格式错误,我没办法了,编辑时是粗体,
发表时变成斜体,晕。 以下是类代码
from PIL import Image
import pandas as pd
import numpy as np
import urllib.request
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC
from sklearn.cross_validation import train_test_split
from sklearn.externals import joblib
import os


class verification_code:
    data = []
    table = []
    threshold = 140

    def __init__(self):
        for i in range(256):
            self.table.append(0) if i < self.threshold else self.table.append(1)

    def download(self, n, URL):
        for i in range(1, n + 1):
            img_src = URL
            i = str(i)
            urllib.request.urlretrieve(img_src, './jpg/' + i + '.jpg')

    def get_crop_imgs(self, img):
        child_img_list = []
        for i in range(4):
            x = 4 + i * 10  # 见原理图
            y = 4
            child_img = img.crop((x, y, x + 10, y + 12))
            child_img_list.append(child_img)

        return child_img_list

    def get_feature(self, img):
        pixel_cnt_list = []
        height = 12
        width = 10
        for y in range(height):
            pix_cnt_x = 0
            for x in range(width):
                if img.getpixel((x, y)) == 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.getpixel((x, y)) == 0:  # 黑色点
                    pix_cnt_y += 1
            pixel_cnt_list.append(pix_cnt_y)

        return pixel_cnt_list

    def saveSpiltPicture(self, n):
        for i in range(0, n):
            i = i + 1
            image = Image.open('./jpg/' + str(i) + '.jpg')
            imgry = image.convert('L')  # 转化为灰度图

            out = imgry.point(self.table, '1')
            img_list = self.get_crop_imgs(out)

            img_list[0].save('./bmp/' + str(4 * i + 1) + '.bmp')
            img_list[1].save('./bmp/' + str(4 * i + 2) + '.bmp')
            img_list[2].save('./bmp/' + str(4 * i + 3) + '.bmp')
            img_list[3].save('./bmp/' + str(4 * i + 4) + '.bmp')

    def sorttable(self):
        list = []
        files_list = os.listdir('char')
        num = 0
        for i in files_list:  # i是第几个文件夹
            files = os.listdir('char/' + i)
            for j in range(0, len(files)):  # j是第几张图片
                image = Image.open('char/' + i + '/' + files[j])
                list.append(self.get_feature(image))
                list[num].append(i)
                num += 1
        names = [x for x in range(1, 24)]
        test = pd.DataFrame(list, columns=names)  # test类型为Dataframe
        test.to_csv('data.txt')  # 保存数据集

    def train(self):
        data = pd.read_csv("data.txt")
        clf = OneVsRestClassifier(SVC(kernel='linear'))

        X = data.ix[:, 1:23]
        y = np.array(data.ix[:, 23]).astype(str)

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
        clf.fit(X_train, y_train)
        y_pred = clf.predict(X_test)

        rf = pd.DataFrame(list(zip(y_pred, y_test)), columns=['predicted', 'actual'])
        rf['correct'] = rf.apply(lambda r: 1 if r['predicted'] == r['actual'] else 0, axis=1)
        print(len(rf[rf['correct'] == 1]) / len(rf))
        joblib.dump(clf, "train_model.m")
        print(rf)

    def seePicture(self, img):
        clf = joblib.load("train_model.m")
        image = Image.open(img)
        image.show()
        imgry = image.convert('L')  # 转化为灰度图
        out = imgry.point(self.table, '1')
        img_list = self.get_crop_imgs(out)
        for x in range(0, 4):
            list = self.get_feature(img_list[x])
            y_pred = clf.predict([list])
            print(y_pred)

    def workFrist(self, n, URL):
        self.download(n, URL)
        self.saveSpiltPicture(n)
 # 手动分组
    def workSecond(self):
        self.sorttable()
        self.train()

    def workTest(self, img):
        self.seePicture(img)

code=verification_code()
code.workFrist(100, 'http://csujwc.its.csu.edu.cn/verifycode.servlet')
code.workSecond()
code.workTest('jpg/1.jpg')


十一、参考文献

1、字符型图片验证码识别完整过程及Python实现   点击打开链接



猜你喜欢

转载自blog.csdn.net/kirito0104/article/details/79265501