您的位置:首页 > 其它

【成长笔记】图片验证码识别

2018-01-08 20:52 106 查看
记得很早以前,我对如携程飞猪等第三方平台购买火车票不用输入验证码感到很……牛!百度后发现其可能是实现了自动打码,或者说机器自动识别验证码,我很好奇。
后来,当我觉得我必须要给自己找些有趣的知识来学习的时候,脑海里萌生了去实现一个简单的验证码识别程序的想法。在一切未知以前,我觉得很难,甚至望而却步。但,真正一点点地行动下来,我发现,其实慢慢深入后,无论多么困难的问题,都能一点点解决。
不要着急,不要慌张,一点点地啃下来,一个方式不通,换个思路解决,不要轻易放弃。这个过程中,世上无难事,只怕有心人,古人诚不欺我。任何事情,流于表面而不做,是永远不会成长的——谨以告诫自己。 
好了,废话不多说。
-----------------------------------------------------我是萌萌的分割线--------------------------------------------------------准备:
语言:python
python库:opencv,sklearn,requests等
流程:整体大致流程为四部分,预处理,字符分割,字符标记分类,字符识别
预处理:对原始验证码图片进行如灰度处理,二值化,降噪等处理,为下一步的字符分割提供一个好的素材
字符分割:将预处理后的图片中的每个字符单独分割出来,本次分割的图片比较取巧,当预处理后,图片中各个字符的位置是相对固定的,所以,采用的分割方法为等距分割。
字符标记分类:当字符分割好后,需要人为为字符进行分类标记,这个过程属于体力活,比较繁琐。我尝试过直接调用谷歌的ocr(光学字符识别)工具库pytesseract来代替人工识别,但是结果非常不好,无奈,还是只能先人工仔细分类。
字符识别:分类好后,图片经过二值化转换为01的数据,写入一个txt格式文件中,作为训练集,其结果类似如下所示:


图片转换为可训练的数字文本数据
再利用机器学习算法对之进行训练,本文使用的是经典的svm(支持向量机)算法对图片验证码进行训练。支持向量机的原理以及优势百度谷歌均可详细查到。
实现:
获取图片验证码
巧妇难为无米之炊,首先,我们要有原材料。在网上,我们可以很轻易地找到图片验证码。本文找的是csdn上的图片验证码。当然了,我们需要的验证码素材至少也要百张以上吧。如果傻乎乎地鼠标刷新保存图片,那效率太低了。要充分利用好可以利用的工具,python中的url库,requests库等都是很强大的工具库。用来爬取网络资源数据可以说是无往不利,哈哈,莫笑我略装*哈~

# coding=utf8
import requests

res = requests.get('img_code_url') # 这是一个get请求,获取图片资源
with open(file_path + "/%d.jpg" % num, "wb") as f: # 将图片保存在本地
f.write(res.content)
print("%d" % num + "获取成功")


如何获取图片验证码呢,我们可以利用浏览器的开发者模式(一般F12快捷键即可打开)或者利用专业的抓包软件如Charles(收费)等来查看获取。以上是个简单地将网上的图片验证码保存到本地的程序代码。
如果我们选择的图片验证码本身是十分清晰的没有太多混淆的状态,我们可以考虑用pytesseract直接对它进行识别。比如此类图片:



可以看出,以上这张图片验证码并未经过太多复杂处理,文本清晰,所以可以很容易ocr直接识别。但是如此类验证码则不然:



可以看出,这类验证码被经过了很多噪声处理,很多像素点在混淆,如果不经过处理直接用ocr识别会很糟糕。
图片预处理
对图片进行灰度化,二值化,等距切割。

# coding=utf8
import cv2

def pre_img(file_name):  # 图片灰度,二值化处理
img = cv2.imread(file_name, 0)
img = cv2.threshold(img, 180, 255, 1)


以上是对图片进行灰度化以及二值化的处理程序,利用的opencv库,opencv的使用方法可以看它的官方文档,或者百度搜索相应的博客等等。
然后,图片变成这样的了:


我们可以看出,此时图片上依然存在许多混淆的噪点,所以我们需要将这个噪点给去掉。通过观察图片,多数噪点是孤立的,非联通的,所以我们可以根据噪点周边的区域存在点的个数来判断其是否为噪点。

# coding=utf8
import numpy as np

# 计算像素周边的点的个数
def sum_9_region(img, x, y):
cur_pixel =<
12ce1
/span> img.getpixel((x, y))  # 当前像素点的值
width = img.width
height = img.height

if cur_pixel == 255:  # 如果当前点为白色区域,则不统计邻域值
return 10

if y == 0:  # 第一行
if x == 0:  # 左上顶点,4邻域
# 中心点旁边3个点
sum = cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x + 1, y + 1))
return 4 - sum / 255
elif x == width - 1:  # 右上顶点
sum = cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x - 1, y)) \
+ img.getpixel((x - 1, y + 1))

return 4 - sum / 255
else:  # 最上非顶点,6邻域
sum = img.getpixel((x - 1, y)) \
+ img.getpixel((x - 1, y + 1)) \
+ cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x + 1, y + 1))
return 6 - sum / 255
elif y == height - 1:  # 最下面一行
if x == 0:  # 左下顶点
# 中心点旁边3个点
sum = cur_pixel \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x + 1, y - 1)) \
+ img.getpixel((x, y - 1))
return 4 - sum / 255
elif x == width - 1:  # 右下顶点
sum = cur_pixel \
+ img.getpixel((x, y - 1)) \
+ img.getpixel((x - 1, y)) \
+ img.getpixel((x - 1, y - 1))

return 4 - sum / 255
else:  # 最下非顶点,6邻域
sum = cur_pixel \
+ img.getpixel((x - 1, y)) \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x, y - 1)) \
+ img.getpixel((x - 1, y - 1)) \
+ img.getpixel((x + 1, y - 1))
return 6 - sum / 255
else:  # y不在边界
if x == 0:  # 左边非顶点
sum = img.getpixel((x, y - 1)) \
+ cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x + 1, y - 1)) \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x + 1, y + 1))

return 6 - sum / 255
elif x == width - 1:  # 右边非顶点
# print('%s,%s' % (x, y))
sum = img.getpixel((x, y - 1)) \
+ cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x - 1, y - 1)) \
+ img.getpixel((x - 1, y)) \
+ img.getpixel((x - 1, y + 1))

return 6 - sum / 255
else:  # 具备9领域条件的
sum = img.getpixel((x - 1, y - 1)) \
+ img.getpixel((x - 1, y)) \
+ img.getpixel((x - 1, y + 1)) \
+ img.getpixel((x, y - 1)) \
+ cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x + 1, y - 1)) \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x + 1, y + 1))
return 9 - sum / 255

# 去除噪点
def clear_noise(img):
filename = './temp/clear_noise/' + str(random.random()) + '.png'
x, y = img.width, img.height
for i in range(x):
for j in range(y):
if sum_9_region(img, i, j) <= 2:
# print(img.getpixel((i,j)))
img.putpixel((i, j), 255)
img = np.array(img)


以上程序实现的是当一个黑色像素点周边8个像素位置上小于或等于2个黑色像素点的情况,认定其为孤立的噪点,将它去除,则获得以下图片:


图片验证码的噪点被除去了。 
接下来,要对其进行字符分割的处理,在这里,看出图片是一个很规整的,所以,简单地采用等距分割即可分割开。最后分割的图片如下:









以上是图片预处理的全过程,那么,要对验证码进行训练,一张图片是肯定不够的,所以,我们得批量下载几百张图片验证码。对它进行预处理,然后标记分类,最终得到以下结果(因为此类图片验证码只有0-9,所以分类为10类):





以上是其中分类为0的文件夹中的一部分数据,至此,分类完成。
图片训练 
将图片转换为01矩阵的形式。

import cv2

def get_binary_pix(img):
img = cv2.imread(img, 0)
img = cv2.threshold(img, 180, 1, 1)
img_binary = img.tolist()
img_binary.append('label')
pixs = [str(i) for i in img_binary]
content = ','.join(pixs)
with open("train_data.txt", "a+") as f:
f.write(content)


利用sklearn库的svm算法对之进行训练。

from sklearn.svm import SVC

def train():
dataset = load_data()
raw, col = dataset.shape
X = dataset[:, :col - 1]
Y = dataset[:, -1]
clf = SVC()
clf.fit(X, Y)
jl.dump(clf, 'model.pkl')


至此,训练完毕,模型建立好了。此时,再下载几个验证码进行测试,同样,对测试验证码进行预处理等流程。



测试结果很理想。
至此,图片验证码识别完成。
---------------------------------------------------我是萌萌的分割线--------------------------------------------------------
在开始实现之前,心里其实很不确定最后是否可以真的实现出来,但是,不懂就要去查,百度,谷歌,博客,书籍,等等方式,感谢公网上的开放的知识,让我学到了很多很多。
源码已传github上:
portfloat/captcha_recgithub.com


,欢迎大家clone。写得不好,欢迎大家批评指正,谢谢~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息