起因

最近用Go写了个上课啦的签到后台,但是签到过程会有验证码来干扰,实属不爽。
我看了两位大佬写的上课啦签到,但是都要Go语言无关,很不幸,只能自己想办法解决了。
网上搜了一些,很多都是python写的,因为python的库非常的多,支持的很广。所有就采用python的方案。
但是自己python是个没入门的菜鸟,实在有些难。不过照着别人的样子copy一下,居然可以使用?!
于是就准备水一篇博文啦。

环境的搭建

我发现一个很神奇的事情,自从我发现IDE(goland)的好处之后,我就很少用vim直接在服务器上面编写Go代码了。
虽然vim能实现许多IDE特性,但是自己用的确实不是很熟,不如熟悉一款IDE。带着惯性思维,我在win10开启来搭建python3环境之旅。

vs code的插件对python支持的非常友好,基本好python3和vs code的插件就可以愉快的写代码了。

然而基于网上的一种使用tesseract-ocr的方案

我需要安装

  • tesseract (不是py的库)
  • pytesseract (py库)
  • PIL (py处理图像的库)
  • requests
  • numpy

如果只是装下软件,使用pip安装一下库,我就不会...
然而装的时候踩了很多坑,比如说pip install xxx一直报错,还有pip下载东西贼慢。
然后python3.7死活装不上PIL,装了3.6版本python才装上。
装好了,也不是马上就能用,还要配置环境目录。

最后,感觉就是,环境弄了好,人已经不行了。

方案选择

这里要说的是上面的方案几乎不能使用,tesseract-ocr的识别率低的吓人,尽管图片经过处理。

于是方案变成

  • sklearn

好像是机器学习,然而我不懂,我只会抄代码。
我要处理的验证码是4位数字(1-9)
code—sample.jpg
是不是很简单,思路就是,将验证码进行二值化,降噪,然后分割成4份,这样只要识别1-9这几个数字就可以完成目标了。
最后这个1-9的识别就是通过KNeighborsClassifier,这个模型(我也不知是啥),来完成的。

图片处理

这里不说废话,直接贴代码

from PIL import Image, ImageDraw

class Captcha(object):
    def __init__(self,path):
        self.img = Image.open(path)
        self.imgs = []
        self.t2val = {}

    def getImgs(self,len,size):
        self.convert()
        self.twoValue(198)
        self.clearNoise(N=3,Z=1)
        self.saveImg()
        self.sliceImg(len)
        self.resizeImgs(size)
        
        return self.imgs

    def convert(self):
        self.img = self.img.convert('L')

    def twoValue(self,G):
        self.t2val = {}
        for y in range(0, self.img.size[1]):
            for x in range(0, self.img.size[0]):
                g = self.img.getpixel((x, y))
                if g > G:
                    self.t2val[(x, y)] = 1
                else:
                    self.t2val[(x, y)] = 0
    
    # 根据一个点A的RGB值,与周围的8个点的RBG值比较,设定一个值N(0 <N <8),当A的RGB值与周围8个点的RGB相等数小于N时,此点为噪点
    # N: Integer 降噪率 0 <N <8
    # Z: Integer 降噪次数
    # 输出
    #  0:降噪成功
    #  1:降噪失败
    def clearNoise(self, N, Z):
        for i in range(0, Z):
            self.t2val[(0, 0)] = 1
            self.t2val[(self.img.size[0] - 1, self.img.size[1] - 1)] = 1

            for x in range(1, self.img.size[0] - 1):
                for y in range(1, self.img.size[1] - 1):
                    nearDots = 0
                    L = self.t2val[(x, y)]
                    ddx = [-1,0,1]
                    ddy = [-1,0,1]
                    for dx in ddx:
                        for dy in ddy:
                            if dx == 0 and dy == 0:
                                continue
                            if L == self.t2val[(x+dx,y+dy)]:
                                nearDots += 1
                    if nearDots < N:
                        self.t2val[(x, y)] = 1

    def saveImg(self):
        image = Image.new("1",self.img.size)
        draw  = ImageDraw.Draw(image)
        for x in range(0, self.img.size[0]):
            for y in range(0, self.img.size[1]):
                draw.point((x, y), self.t2val[(x, y)])
        self.img = image

    def sliceImg(self,count=4, p_w=3):

        '''
        :param count: 
        :param p_w: 对切割地方多少像素内进行判断
        '''
        w, h = self.img.size
        pixdata = self.img.load()
        eachWidth = int(w / count)
        beforeX = 0
        self.imgs = []
        for i in range(count):

            allBCount = []
            nextXOri = (i + 1) * eachWidth
            for x in range(nextXOri - p_w, nextXOri + p_w):
                if x >= w:
                    x = w - 1
                if x < 0:
                    x = 0
                b_count = 0
                for y in range(h):
                    if pixdata[x, y] == 0:
                        b_count += 1
                allBCount.append({'x_pos': x, 'count': b_count})

            sort = sorted(allBCount, key=lambda e: e.get('count'))
            nextX = sort[0]['x_pos']
            box = (beforeX, 0, nextX, h)
            beforeX = nextX
            self.imgs.append(self.img.crop(box))

    def resizeImgs(self,size):
        for i in range(len(self.imgs)):
            self.imgs[i] = self.imgs[i].resize(size)

当我们需要处理一张验证码图片时,只需要在创建对象的时候传入图片目录,然后调用getImgs(self,len,size)
就可以得到处理好的验证码了。

数字的识别

为了让识别验证码,我们需要训练出一个可用的模型,然后用此模型进行识别。
于是我们需要准备许多张验证码,并把每个经过处理后的验证码放到不同的目录下面供机器进行学习。
captcha_rec.jpg

from PIL import Image, ImageDraw
import numpy as np 
import os
import time

from sklearn.externals import joblib
from sklearn.neighbors import KNeighborsClassifier

class Model(object):
    def __init__(self):
       pass
    
    def newModel(self):
        self.model = KNeighborsClassifier()
        print("Created a new model")
    def loadModel(self,path):
        if os.path.exists(path):
            self.model = joblib.load(path)
        else:
            print("Model not exit")
            exit(-1)
    def saveModel(self,path):
        if self.model :
            joblib.dump(self.model,path)
            print("model Save at",path)
        else:
            print("saveMode failed, model not exit")

    def predict_imgs(self,imgs,sz):
        result_list = []
        pre_list = []
        imgsLen = len(imgs)
        for i in range(imgsLen):
            pix = np.asarray(imgs[i].convert('L'))
            pix = pix.reshape(sz)
            pre_list.append(pix)

        pre_list = np.asarray(pre_list)
        result_list = self.model.predict(pre_list)
        
        result = ''
        for r in result_list:
            result += r
        return result

    # train
    def trainModel(self,path,items,sz):
        X = []
        y = []
        print("Starting training model...")
        for i in items:
            target_path = os.path.join(path,i)
            print(target_path)
            for name in os.listdir(target_path):
                img = Image.open(os.path.join(target_path,name))
                pix = np.asarray(img.convert('L'))
                X.append(pix.reshape(sz))
                y.append(target_path.split('\\')[-1])

        X = np.asarray(X)
        y = np.asarray(y)
        self.model.fit(X,y)
        print("Train finished")

    #check train result
    def checkModel(self,path,items,sz):
        pre_list = []
        y_list = []
        print("Starting prediction")
        for i in items:
            part_path = os.path.join(path,i)
            for name in os.listdir(part_path):
                img = Image.open(os.path.join(part_path,name))
                pix =  np.asarray(img.convert('L'))
                pix = pix.reshape(sz)
                pre_list.append(pix)
                y_list.append(part_path.split('\\')[-1])

        pre_list = np.asarray(pre_list)
        
        y_list = np.asarray(y_list)
        result_list = self.model.predict(pre_list)
        
        print(result_list,y_list)
        acc = 0
        for i in result_list == y_list:
            if i == np.bool(True):
                acc += 1

        print("Predict result: ")
        print(acc,acc/len(result_list))

总结

经过训练后的model,识别准确率可以达到98%以上,当然这里和验证码的简单是有关系的。
我看到大佬使用的cnn模型,我太菜了,看不懂,也操作不来,就放弃了。

这次经历,让我对机器学习有了一点点的了解,感觉就是输入然后输出。

Last modification:December 22nd, 2019 at 10:14 pm
要饭啦~