教务系统网站的验证码升级,此次验证码链接包含有SafeKey标识码,并且与cookie想绑定,而且验证码链接地址只能打开一次,第二次打开默认返回空白。此外,验证码图像也进行了升级,噪点数目明显变多,虽然用上个版本的模型依旧能够识别,但准确率相对较低,因此重新抓取数据集并进行训练。
如果验证码形下图左,则使用Version 1.0目录下的模型,否则使用本目录下的模型。
python 3.7.3
Pycharm
requests库
Pillow库
sklearn库
其他库应为python自带库,若无法运行,请安装对应的库
图片预处理:主要消除验证码图片的噪点,让图片的验证码清晰地显示以便提取相关特征。而正方教务系统的验证码主要包括一些噪点,没有其他曲线,而且字母和数字为深蓝色,并且没有很严重的变形,如下图所示。
预处理与分割操作主要包括灰度处理、二值化、去噪和裁剪(相较于V1.0版本多了一个噪的环节)
灰度处理:主要将彩色图片变为灰度图片。彩色图片每个像素点包括rgb三层颜色,而灰度图片每个像素点只有一层颜色。直接使用Pillow库的相关函数即可将彩色图片变为灰度图片,代码如下:
from PIL import Image
...
image = Image.open("./traindataset/" + f)
image = image.convert('L') # 灰度处理
...
二值化:将灰度图片的每个像素点设置为0(黑)或者255(白)。此步骤主要是消除噪点,由上图的灰度图可以看到数字与字母部分颜色较深,其像素值更接近与0,而噪点的像素值与255更接近,因此存在一个阈值,用以划分该像素是噪点还是数字与字母部分。经过反复的测试,尽可能使得噪点变少,得到阈值为50。即将像素值大于50的直接设置为255(白),而小于等于50的直接设置为0(黑色),此所谓二值化(二值:0/255)。代码如下:
...
image = image.point(lambda x: 255 if x > 50 else 0) # 二值化
...
裁剪:即进行分割,将每个数字/字符分割开来以便于特征提取。通过比较不同的验证码,最终确定提取的大小为12*22。代码如下:
...
crop_range = [(4, 0, 16, 22), (16, 0, 28, 22), (28, 0, 40, 22), (42, 0, 54, 22)] # 分割范围
for i in range(4):
image_t = image.crop(crop_range[i]) # 裁剪
...
去噪:将验证码字符外围孤立的点删除,唯一的不足是对边界处理的较为粗糙,并且对成块的噪点也没法剔除。
def RemoveNoise(img):
width = img.width
height = img.height
for i in range(width):
for j in range(height):
pixel = img.getpixel((i, j))
if pixel == 255:
continue
else:
if img.getpixel(((i-1) % width, j)) == 255 and img.getpixel((i, (j-1) % height)) == 255 and \
img.getpixel(((i+1) % width, j)) == 255 and img.getpixel((i, (j+1) % height)) == 255:
img.putpixel((i, j), 255)
return img
该步骤最重要的就是特征提取
特征提取就是将图片的特征提取出来,而不是将整个图片都作为数组放入训练。如果将整个图片作为数组进行训练,则feature包括12*22=264,特征数过多,不利于机器学习的训练,因此采用特征提取的办法来降低feature。而常用的算法是提取每一行、每一列的黑色,也就是像素值为0的个数,然后作为feature,则需要的特征数为12+22=34,特征数大大降低。具体代码如下:
def extract_characters(image):
img = np.asarray(image)
characters = []
for i in range(image.height):
total = 0
for j in range(image.width):
if img[i][j] == 0:
total += 1
characters.append(total)
for i in range(image.width):
total = 0
for j in range(image.height):
if img[j][i] == 0:
total += 1
characters.append(total)
return characters # 返回特征向量
然后用机器学习的方法训练训练集,这里采用的是随机森林的方法,代码如下:
def train_captcha():
list1 = list(range(48, 57))
list1 = list1 + list(range(97, 122))
list1.remove(ord('o')) # 'o'没有出现
train_x = []
train_y = []
for i in range(len(list1)):
work_dir = "./train_classify/" + chr(list1[i]) + "/"
for f in os.listdir(work_dir):
filepath = work_dir + f
train_x.append(extract_characters(Image.open(filepath)))
train_y.append(list1[i])
train_x = np.array(train_x)
train_y = np.array(train_y)
rf = RandomForestClassifier(n_estimators=45, max_features='sqrt', oob_score=True)
rf.fit(train_x, train_y)
joblib.dump(rf, './randomforest.m')
对测试集200个验证码进行测试,得到的正确率如下:
(1).不去除噪点
Total: 200
correct 164
Accuracy: 0.82
(2).去除噪点
Total: 200
correct 170
Accuracy: 0.85
由上述结果可以看出,孤立噪点的剔除有利于验证码的识别,噪点对于特征提取是不利的,因为噪点会使得不同的字符存在相同特征的可能,这对于最后的识别是不利的,因此尽可能的对验证码图片进行处理是很有必要的。
当然,该测试结果相较于V1.0的模型来说,准确率方面不占优势,因为验证码图像升级了,因此识别难度加大了,但对于自动化的应用来说影响不大,因为验证码出错可以继续获取新的验证码并识别,直到验证码正确为止。
目录下的randomforest.m文件为训练的模型文件,使用joblib库(较老版本的sklearn可能带有该库,目前该库是分离出sklearn的),使用函数将joblib导入,代码如下:
import joblib
...
rf = joblib.load(model_path) # 导入模型
...
result = rf.predict(characters_vector) # 预测:输入为特征向量,输出为字符的ASCII码
使用前需要对验证码预处理,对验证码图像切割的每一个字符提取特征,然后用模型进行预测,得到预测的字符的ASCII码。循环四次,对验证码图像的每个字符进行预测,得到整个验证码的值。整体流程参考predict.py文件中的predict_online
函数,函数部分如下:
def predict_online(filepath): # filepath: the path of captcha
model_path = "./randomforest.m" # model path
rf = joblib.load(model_path) # load model
crop_range = [(4, 0, 16, 22), (16, 0, 28, 22), (28, 0, 40, 22), (42, 0, 54, 22)] # crop range
data = [] # characters vector
for i in range(4): # captcha contains four chars
image = Image.open(filepath) # open captcha image info
image = image.convert('L') # 灰度处理
image = image.point(lambda x: 255 if x > 60 else 0) # 二值化
image = image.crop(crop_range[i]) # 裁剪
image = RemoveNoise(image) # 去噪
data.append(extract_characters(image)) # extract characters
data = np.array(data) # list to np.array
result = rf.predict(data) # predict
captcha_pre = ""
for i in range(4):
captcha_pre += chr(result[i])
return captcha_pre
学校教务系统验证码莫名其妙的升级了,该不会是我前期打码过于频繁导致的吧:(,新的验证码数据集也是经典打码,还好有之前的模型,能在一定程度上自动识别一部分的验证码,不过人工打码确实挺累的...