起因
最近用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)
是不是很简单,思路就是,将验证码进行二值化,降噪,然后分割成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)
就可以得到处理好的验证码了。
数字的识别
为了让识别验证码,我们需要训练出一个可用的模型,然后用此模型进行识别。
于是我们需要准备许多张验证码,并把每个经过处理后的验证码放到不同的目录下面供机器进行学习。
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模型,我太菜了,看不懂,也操作不来,就放弃了。
这次经历,让我对机器学习有了一点点的了解,感觉就是输入然后输出。