验证码识别是一个适合入门机器学习的项目,本人经过几天学习,做了一套验证码识别,分享给大家:
这一批验证码较为简单,是基本的数字,没有字母,有字母方法一样 就是建立训练库的时候麻烦点.
首先我们有以下这几个主要的库:
import urllib.request
import pytesseract
from PIL import Image
import time
import cv2
import os
from svmutil import *
from sklearn.cross_validation import train_test_split
from fnmatch import fnmatch
OpenCV 用于图片一些处理 比如灰度处理和二值化
pytesseract 用于验证码识别,这个需要tesseract的支持,下载地址:http://tesseract-ocr.googlecode.com/files/tesseract-ocr-setup-3.02.02.exe or https://sourceforge.net/projects/tesseract-ocr-alt/files/可能需要更改环境变量
svmutil 是由台湾大学林智仁教授开发的一个简单、易用的SVM模式识别与回归的软件包,下载地址:https://www.csie.ntu.edu.tw/~cjlin/libsvm/, 具体安装方法百度一下.
首先我们从网上拷一份验证码
fp=urllib.request.urlopen('Your webpage')#示例网址的验证码图片地址
name="./out_img/1.jpg"
f=open(name,'wb')
f.write(fp.read())#读取验证码图片并保存为1.jpg
f.close()
我们首先分析一下思路,分为以下几个步骤:
1.灰度处理用OpenCV
2.清除边缘噪点
3.线降噪与点降噪
4.切割
5.获得特征
6.学习与预测
其中4 5 6 是非必须的,进行1 2 3并用pytesseract 基本可以解决识别验证码了,有兴趣的可以自己用SVM建立库 进行识别(4 5 6)
1.灰度处理用OpenCV
'''
Author: Fu_Connor
'''
def _get_dynamic_binary_image(img_name):
filename ='./out_img/' + img_name.split('.')[0] + '-binary.jpg'
img_name = './out_img' + '/' + img_name
print('.....' + img_name)
image = cv2.imread(img_name)
image2 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰度处理
th1 = cv2.adaptiveThreshold(image2, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)
cv2.imwrite(filename,th1)
return th1
处理之后的图片
我们灰度处理的目的是让电脑更方便进一步处理与识别图片,如果图片颜色为彩色 则太过于不方便,处理完之后我们发现主要干扰的地方是右下方 有一大部分噪点,我们下一步就是将这部分噪点去掉.
2.清除边缘噪点
清除边缘噪点的原理非常简单,白色RGB颜色为255,黑色为0,那么我们只需要将右下角的坐标点全部变为255 即可消除.
'''
Author: Fu_Connor
'''
def clear_border(img,img_name):
filename = './out_img/' + img_name.split('.')[0] + '-clearBorder.jpg'
h, w = img.shape[:2]
for y in range(0, w):
for x in range(0, h):
if y > w -20 or y < 1:#右方与左方一列
img[x, y] = 255
if x > h - 10:#下方
img[x, y] = 255
cv2.imwrite(filename,img)
return img
处理之后的图片:我们发现处理之后的图片右下角噪点已经消失,其实这个时候让电脑识别也已经可以了,但这样错误率会比较高因为数字之间仍有噪点,我们第三步就是处理中间这些散乱分布的噪点.
3.线降噪与点降噪
点降噪:
点降噪的基本原理是 在一个九宫格内 如果有6个点或以上为白色则认为中间的点为白色,当然具体数目不一定必须为7 你也可以根据自己的验证码进行更改,然后四个顶点和四个周边的线同理.
我放张图大家看一下:
画的比较low 大家凑合理解一下:)
这6个红点中心的那个点颜色为黑色,但因以其中心的九宫格内有6葛白点,那么这个黑点会被认为是噪点进而被消除.个人感觉点降噪的效果要优于线降噪,线降噪的局限性会在下面说.
def interference_point(img,img_name, x = 0, y = 0):
"""
点降噪
Autor:Fu_Connor
"""
filename = './out_img/' + img_name.split('.')[0] + '-interferencePoint.jpg'
cur_pixel = img[x,y]# 当前像素点的值
height,width = img.shape[:2]
for y in range(0, width - 1):
for x in range(0, height - 1):
if y == 0: # 第一行
if x == 0: # 左上顶点,4邻域
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 2 * 255:
img[x, y] = 255
elif x == height - 1: # 右上顶点
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum >= 2 * 255:
img[x, y] = 255
else: # 最上非顶点,6邻域
sum = int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 3 * 255:
img[x, y] = 255
elif y == width - 1: # 最下面一行
if x == 0: # 左下顶点
sum = int(cur_pixel) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x, y - 1])
if sum >= 2 * 255:
img[x, y] = 255
elif x == height - 1: # 右下顶点
sum = int(cur_pixel) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y - 1])
if sum >= 2 * 255:
img[x, y] = 255
else: # 最下非顶点,6邻域
sum = int(cur_pixel) \
+ int(img[x - 1, y]) \
+ int(img[x + 1, y]) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x + 1, y - 1])
if sum >= 3 * 255:
img[x, y] = 255
else: # y不在边界
if x == 0: # 左边非顶点
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 3 * 255:
img[x, y] = 255
elif x == height - 1: # 右边非顶点
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum >= 3 * 255:
img[x, y] = 255
else: # 具备9领域条件的
sum = int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 7 * 255:
img[x, y] = 255
cv2.imwrite(filename,img)
return img
处理完后的图片:
线降噪:
线降噪基本原理是 如果一个点的上下左右均为白色则认为这个点是白色,这样有什么用呢,这样可以去除一些细的斜线,我画一张图大家理解一下:
这四个红点是白色的,那么中间这个点就会认为是白色进而被消除,同理这四个点都可以被消除 ,这里我设置的参数是3个点,也就是说只要上下左右有三个点为白色就会认为这点是白色. 这样有个缺点就是遇到粗的线 和水平的线就凉了(水平的线只能消掉两头).
def interference_line(img, img_name):
'''
Autor:Fu_Connor
线降噪
'''
filename = './out_img/' + img_name.split('.')[0] + '-interferenceline.jpg'
h, w = img.shape[:2]
for y in range(1, w - 1):
for x in range(1, h - 1):
count = 0
if img[x, y + 1] > 245:
count = count + 1
if img[x, y - 1] > 245:
count = count + 1
if img[x - 1, y] > 245:
count = count + 1
if img[x + 1, y] > 245:
count = count + 1
if count > 2:
img[x, y] = 255
cv2.imwrite(filename,img)
return img
处理完之后的图片:
这里我们的处理方法是先点降噪后进行线降噪,当然你要根据自己的验证码进行选择.
进行到这里。如果使用pytesseract 就已经结束了,直接调用:
pytesseract.image_to_string()
准确率已经很高了,但是如果想自己建立库,针对这种验证码进行特殊学习了话 就要更深一步了
4.切割
如果想用SVM进行学习,那么就必须要自己进行切割并单独学习每个数字or字母
切割数字的原理很简单,经过观察我们处理后的图片我们发现,每个数字基本宽9,高16个像素,每个间隔为1像素,那么我们就可以每宽9+1个像素切割一次,并且高恒定为16.
这个时候我们要用PIL库的crop了
def get_crop_imgs(img):
"""
按照图片的特点,进行切割,这个要根据具体的验证码来进行工作.
Autor:Fu_Connor
"""
child_img_list = []
for i in range(4):#4个数字
x = 0 + i * (9 + 1)
y = 3
child_img = img.crop((x, y, x + 9, y + 16))
child_img.save('./out_img/cutting/cutting_%s.jpg' %(i))
child_img_list.append(child_img)
这种方法属于比较直接的,也可以从第一个黑色像素识别作为起点在切割,都是可以的
切割之后:
5.获得特征
获得特征是很简单的,只需要遍历整个图片记录下每行每列有几个黑色(RGB=0)的数目即可,这样作为一个9*16的图片就有25个特征值:
我这里的参量比较多,是因为这部分我没完全写完,不过基本就是这样了,大家稍微改一下就行
'''
Autor:Fu_Connor
'''
def get_feature(address,dir, file):
adress='./out_img/train_data/'
f = open('./train.txt', 'w')
f.write(dir)
im = Image.open(address + dir + file)
count = 0
width, height = im.size
for i in range(height):
c = 0
for j in range(width):
if im.getpixel((j, i)) == 0: c += 1
f.write(' %d:%d'%(count, c))
count += 1
for i in range(width):
c = 0
for j in range(height):
if im.getpixel((i, j)) == 0: c += 1
f.write(' %d:%d'%(count, c))
count += 1
f.write('\n')
0 0:0 1:3 2:2 3:3 4:1 5:3 6:0 7:4 8:2 9:2 10:2 11:2 12:2 13:0 14:0 15:0 16:0 17:3 18:4 19:3 20:1 21:3 22:3 23:5 24:4
0 0:0 1:0 2:3 3:2 4:3 5:2 6:2 7:1 8:3 9:2 10:2 11:2 12:3 13:4 14:0 15:0 16:0 17:2 18:3 19:6 20:2 21:5 22:2 23:3 24:6
0 0:0 1:2 2:2 3:2 4:2 5:2 6:3 7:2 8:2 9:2 10:2 11:2 12:3 13:0 14:0 15:0 16:0 17:1 18:4 19:6 20:2 21:2 22:1 23:4 24:6
0 0:0 1:2 2:1 3:1 4:1 5:1 6:2 7:2 8:2 9:3 10:2 11:2 12:3 13:1 14:0 15:0 16:0 17:2 18:4 19:6 20:1 21:2 22:2 23:1 24:5
第一列为图片表示的数字,后面为特征,有几张图片就有几行.
6.学习并预测
到这里基本就完成了,我们只需要调用SVM函数学习即可:
def train_test_svm_model():
y, x = svm_read_problem('./train.txt')
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
model = svm_train( y_train, x_train)
svm_save_model('./model_file', model)#保存模型方便使用
p_label, p_acc, p_val = svm_predict(y_test, x_test, model)
最终使用留出法测试模型,留出的测试数据结果为73%准确率,还是可以用的
下次我们只需要调用SVM即可完成任意图片预测:
model = svm_load_model('./model_file')
y, x = svm_read_problem('./out_img/predict.txt')#predict.txt=需要再获取一次你需要预测的图片的特征.
p_label, p_acc, p_val = svm_predict(y, x, model)
这样我们就大功告成啦,我们看一下结果
SVM该验证码为:6046
Pytesseract识别为:6046
还是可以的,但有些情况下,可能还是没有Pytesseract预测的准确,这可能是因为我们的训练数据不够多造成的,我现在只有100+个训练集,随着训练集的增加 准确率会越来越高.
运行方法:建立一个out_img文件夹,在这个文件夹下面在建立cutting和train-data文件夹,train-data下面放训练集文件及其cutting图片.如图所示:
import urllib.request
import pytesseract
from PIL import Image
import time
import cv2
import os
from svmutil import *
from fnmatch import fnmatch
path = './out_img'
for i in os.listdir(path):
path_file = os.path.join(path,i)
if os.path.isfile(path_file):
os.remove(path_file)
address = './out_img/train_data/'
f1 = open('./train.txt', 'w')
f2 = open('./out_img/predict.txt', 'w')
address2='./out_img/cutting/'
fp=urllib.request.urlopen('http://www.ems.com.cn/servlet/ImageCaptchaServlet')#Verification Code
name="./out_img/1.jpg"
f=open(name,'wb')
f.write(fp.read())#save Verification Code as 1.jpg
f.close()
def clear_border(img,img_name):
filename = './out_img/' + img_name.split('.')[0] + '-clearBorder.jpg'
h, w = img.shape[:2]
for y in range(0, w):
for x in range(0, h):
if y > w -20 or y < 1:
img[x, y] = 255
if x > h - 10:
img[x, y] = 255
cv2.imwrite(filename,img)
return img
def _get_dynamic_binary_image(img_name):
filename ='./out_img/' + img_name.split('.')[0] + '-binary.jpg'
img_name = './out_img' + '/' + img_name
print('.....' + img_name)
image = cv2.imread(img_name)
image2 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
th1 = cv2.adaptiveThreshold(image2, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)
cv2.imwrite(filename,th1)
return th1
def interference_line(img, img_name):
filename = './out_img/' + img_name.split('.')[0] + '-interferenceline.jpg'
h, w = img.shape[:2]
# !!!opencv矩阵点是反的
# img[1,2] 1:图片的高度,2:图片的宽度
for y in range(1, w - 1):
for x in range(1, h - 1):
count = 0
if img[x, y + 1] > 245:
count = count + 1
if img[x, y - 1] > 245:
count = count + 1
if img[x - 1, y] > 245:
count = count + 1
if img[x + 1, y] > 245:
count = count + 1
if count > 2:
img[x, y] = 255
cv2.imwrite(filename,img)
return img
def interference_point(img,img_name, x = 0, y = 0):
filename = './out_img/' + img_name.split('.')[0] + '-interferencePoint.jpg'
cur_pixel = img[x,y]
height,width = img.shape[:2]
for y in range(0, width - 1):
for x in range(0, height - 1):
if y == 0: # first row
if x == 0:
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 2 * 255:
img[x, y] = 255
elif x == height - 1:
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum >= 2 * 255:
img[x, y] = 255
else:
sum = int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 3 * 255:
img[x, y] = 255
elif y == width - 1:
if x == 0:
sum = int(cur_pixel) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x, y - 1])
if sum >= 2 * 255:
img[x, y] = 255
elif x == height - 1:
sum = int(cur_pixel) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y - 1])
if sum >= 2 * 255:
img[x, y] = 255
else:
sum = int(cur_pixel) \
+ int(img[x - 1, y]) \
+ int(img[x + 1, y]) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x + 1, y - 1])
if sum >= 3 * 255:
img[x, y] = 255
else:
if x == 0:
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 3 * 255:
img[x, y] = 255
elif x == height - 1:
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum >= 3 * 255:
img[x, y] = 255
else:
sum = int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum >= 7 * 255:
img[x, y] = 255
cv2.imwrite(filename,img)
return img
def get_crop_imgs(img):
child_img_list = []
for i in range(4):
x = 0 + i * (7 + 4)
y = 3
child_img = img.crop((x, y, x + 9, y + 16))
child_img.save('./out_img/cutting/cutting_%s.jpg' %(i))
child_img_list.append(child_img)
def get_feature(address,assume,dir, file,f,a):
# assume is assume predict value
f.write(assume)
im = Image.open(address + dir + a + file)
count = 0
width, height = im.size
for i in range(height):
c = 0
for j in range(width):
if im.getpixel((j, i)) == 0: c += 1
f.write(' %d:%d'%(count, c))
count += 1
for i in range(width):
c = 0
for j in range(height):
if im.getpixel((i, j)) == 0: c += 1
f.write(' %d:%d'%(count, c))
count += 1
f.write('\n')
def train_test_svm_model():
y, x = svm_read_problem('./train.txt')
'''
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
model = svm_train( y_train, x_train)
svm_save_model('./model_file', model)
p_label, p_acc, p_val = svm_predict(y_test, x_test, model)
p_label, p_acc, p_val = svm_predict(y, x, model)
测试模型
'''
model = svm_train(y, x)
svm_save_model('./model_file', model)
def main():
filedir = './out_img'
for file in os.listdir(filedir):
if fnmatch(file, '*.jpg'):
img_name = file
im = _get_dynamic_binary_image(img_name)
im = clear_border(im, img_name)
im = interference_point(im, img_name)
interference_line(im, img_name)
time.sleep(2)
global image
image = Image.open('./out_img/1-interferenceline.jpg')
get_crop_imgs(image)
if __name__ == '__main__':
main()
dirs = os.listdir(address)
for dir in dirs:
files = os.listdir(address + dir)
for file in files:
get_feature(address, dir, dir, file, f1, '/')
f1.close()
train_test_svm_model()
model = svm_load_model('./model_file')
files = os.listdir(address2)
for file in files:
get_feature(address2, '1', '', file, f2, '')
f2.close()
y, x = svm_read_problem('./out_img/predict.txt')
p_label, p_acc, p_val = svm_predict(y, x, model)
print('SVM identify:', end='')
for label in p_label:
print(int(label), end='')
vcode = pytesseract.image_to_string(image, config='digits')
print('\nPytesseract identify:%s' % vcode)
vcode = ''
for i in range(4):
try:
file = './out_img/cutting/cutting_%s.jpg' % (i)
vcode = vcode + pytesseract.image_to_string(Image.open(file), config='-psm 10 outputbase digits')
except Exception as err:
pass
print('Pytesseract_cutting identify:%s' % vcode)
一个MEP建筑理工男