验证码识别
经过之前的几篇博文的铺垫,这一次我将会做一个比较麻烦的事情,就是验证码的识别。
在文章开始之前先附上上一次缺少的资源链接:
手写数字识别
是一个能够进行手写数字识别的简单的三层神经网络的代码和几个测试图片。
大家都认识验证码,目前的验证码种类比较固定,目前最常见的就是数字和字母组成的验证码。而验证码的作用就是为了防止自动登录,以防有人通过计算机来做“坏事”。验证码的特性就是对于人类来说轻而易举能够做到,但是对于计算机来说却是不可能的事情。理论上如果计算机的识别率达到1%,那么这个验证码就已经是不安全的了。所以,让我们来尝试破解一下验证码吧。
验证码破解难度的分析:
Yan J,El Ahmad A S. 已经在Breaking visual CAPTCHAs with naive pattern recognition algorithms(Proceedings of 23rd Annual Computer Security Applications Conference,2007:279-291.)中证实,目前有粘连字符的验证码是所有字符型的验证码中最难识别的一种,因为粘连的程度高,很难将粘连的字符完整的分割下来,而只要能够将字符完整的分割下来,剩下的对于一般的神经网络就不是什么难事。
要做就做最好的,我就尝试对粘连字符的验证码进行分割识别,根据我们老师给的数据,实验室的学长学姐能够达到的识别率在10%左右,所以我就把目标定在他们的一半吧。
厚着脸皮,要到了验证码的图片和标签,我就开始了验证码的识别之旅。
可以看到图片中的验证码的粘连效果很强,没有任何的空隙,这就给图片的分割造成了很大的困难。为了达到好的识别率,这一次可没有办法只用一个简单的python程序就达到目的,我必须要有一个步骤。阅读了一些相关论文以后,我就设定了以下几个步骤:
- 二值化
- 图片的分割
- 图片的规格化
- 图片的识别
图片的二值化:
看到图片我有点吃惊,图片中的验证码边缘有锐化的痕迹,可能给的就是已经经过二值化以后的图片。当然必要的步骤还是要有的,图片的二值化过程并不复杂,我就不多做解释,直接的附上代码:
import numpy
from PIL import Image
for i in range(701):
filename = '\\'+str(i)+'.jpg'
img = Image.open('data\神经网络3.0\image_picture' + filename).convert('L')
img = numpy.array(img)
rows, cols = img.shape
for j in range(rows):
for k in range(cols):
if img[j][k] >= 128:
img[j][k] = 255
else:
img[j][k] = 0
pass
pass
image = Image.fromarray(img)
filename = '\\result1_'+str(i)+'.jpg'
image.save('data\神经网络3.0\\result1.0'+filename)
print("图片保存完成")
pass
简单的说就是将图片的像素点用灰度值表示,将大于128的像素点提升至255,小于128的像素点降为0,这样就能够将图片二值化。二值化的目的是为了在有较浅的噪点的情况下,达到去燥的效果,当然对于噪点本身就比较深的图片,可以用别的方法,这一次就不详细的讨论了。
附上二值化以后的效果图:
图片的分割:
到了验证码识别的重点了,图片的分割。图片的分割直接的关系到验证码的识别率,甚至可以说直接的决定着验证码识别的生死大关。
这是我第一次用python直接图片进行操作,没有什么好的办法,我就直接的采用一种比较简单粗暴的分割方法:找到验证码最左的点和最右的点,直接的平均分割。(……别怪我,刚入手的时候我也没有什么好的办法,先看一看效果如何,再逐步的提升吧,这才是我想要的学习的效果。)
import numpy
from PIL import Image
for i in range(701):
filename = '\\result1_' + str(i) + '.jpg'
img = Image.open('data\神经网络3.0\\result1.0'+filename).convert('L')
img = numpy.array(img)
rows, cols = img.shape
maxleft = 100
maxright = 0
# 寻找最左边的点
for j in range(cols):
for k in range(rows):
if img[k][j] == 0:
maxleft = j
break
if maxleft != 100:
break
for j in range(-cols, 0):
for k in range(rows):
if img[k][-j-1] == 0:
maxright = -j
break
if maxright != 0:
break
section = (maxright-maxleft)/4
img = Image.fromarray(img)
img1 = img.crop((maxleft, 0, maxleft+section, 30))
img2 = img.crop((maxleft+section, 0, maxleft+section*2, 30))
img3 = img.crop((maxleft+section*2, 0, maxleft+section*3, 30))
img4 = img.crop((maxleft+section*3, 0, maxright, 30))
filename = '\\result2_' + str(i)
img1.save('data\神经网络3.0\\result2.0'+filename + '_01.jpg')
img2.save('data\神经网络3.0\\result2.0' + filename + '_02.jpg')
img3.save('data\神经网络3.0\\result2.0' + filename + '_03.jpg')
img4.save('data\神经网络3.0\\result2.0' + filename + '_04.jpg')
print("切割成功!")
pass
是不是很简单,没错,就是想要这种效果,没有谁能够一口吃成大胖子,再说了,只是一个简单的课余时间的研究乐趣,若是直接的就附上难的代码,估计自己都看不懂吧。
行了不废话了,看一看效果图:
好像没有想象中的那么差,可能还真的有什么意外收获吧。
图片的规格化:
图片分割以后大小各不相同,有的宽有的窄,如果真的是这样没有什么好的办法进行统一的输入并且识别。
为了更好的输入到程序中用神经网络来进行识别,就必须要对图片的大小进行规格化处理。以下是核心的代码:
def fillblank(img):
image = np.array(Image.new('L', (30, 30), 255))
left_cols = int((30 - len(img[0]))/2)
for i in range(len(img[0])):
for j in range(30):
image[j][i+left_cols] = img[j][i]
img = image
return img
简单来说就是生成一个30*30的空白图片,并且根据输入的图片的宽度找到合适的位置将输入图片的像素点逐个的复制到空白图片中,这样就得到了一个个统一的规格化的图片,为我们后续的验证码的识别打下基础。以下是规格化以后的图片:
图片的识别:
终于到了激动人心的时刻了,我们马上就要对验证码进行识别了。不过我们什么有名的神经网络都没有该怎么办呢。不用担心,我们有一个自己编写的简单的三层神经网络,虽然很简单,但是却是我们自己编写的,其中的所有内容我们都了如指掌,除了问题也很容易的找到。废话少说,我们就直接的进行识别吧。
我一共得到了七百张图片,其中用五百张作为验证码识别的训练集,两百张图片作为验证码识别的测试集,并且得到了所有验证码的图片内容以及标记,我们就尝试一下,看看究竟又怎样的识别率。
首先将神经网络初始化:
test_input = 900
test_hidden = 1000
test_out = 80
learning = 0.1
n = neuralNetwork(test_input, test_hidden, test_out, learning)
再用五百张图片进行训练:
with open('data\神经网络3.0\\0-499.txt', "r") as f:
for i in range(500):
results = get_targets(f, i)
filename = '\\result2_' + str(i)
img1 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_01.jpg'))
img2 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_02.jpg'))
img3 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_03.jpg'))
img4 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_04.jpg'))
img1 = np.array(img1).flatten()
inputs = invert_pivture(img1/255.0)
inputs = inputs * 0.99 + 0.01
targets = numpy.zeros(test_out) + 0.01
targets[int(results[0])] = 0.99
n.training(inputs, targets)
img2 = np.array(img2).flatten()
inputs = invert_pivture(img2/255.0)
inputs = inputs * 0.99 + 0.01
targets = numpy.zeros(test_out) + 0.01
targets[int(results[1])] = 0.99
n.training(inputs, targets)
img3 = np.array(img3).flatten()
inputs = invert_pivture(img3/255.0)
inputs = inputs * 0.99 + 0.01
targets = numpy.zeros(test_out) + 0.01
targets[int(results[2])] = 0.99
n.training(inputs, targets)
img4 = np.array(img4).flatten()
inputs = invert_pivture(img4/255.0)
inputs = inputs * 0.99 + 0.01
targets = numpy.zeros(test_out) + 0.01
targets[int(results[3])] = 0.99
n.training(inputs, targets)
pass
f.close()
print("训练完成!")
pass
ss
简单的解释一下其中的原理,因为神经网络是一种分类手段,他最后的结果是将每一条数据划分到不同的类别中,所以我们就必须要给它一个类别的划分,我这里就采用了ASCII码来作为不同类别的划分,计算出分割以后的每一张图片属于哪一个ASCII码类别。
最后,用两百张图片进行测试,
# 训练以后测试的成绩
score = 0
with open('data\神经网络3.0\\501-700.txt', "r") as f:
for i in range(501, 701):
# 得到测试集的真实数据
results = get_results(f)
filename = '\\result2_' + str(i)
img1 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_01.jpg'))
img2 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_02.jpg'))
img3 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_03.jpg'))
img4 = np.array(Image.open('data\神经网络3.0\\result3.0' + filename + '_04.jpg'))
answer = np.zeros(4)
img1 = np.array(img1).flatten()
inputs = invert_pivture(img1/255.0)
inputs = inputs * 0.99 + 0.01
outputs = n.query(inputs)
answer[0] = numpy.argmax(outputs)
img2 = np.array(img2).flatten()
inputs = invert_pivture(img2/255.0)
inputs = inputs * 0.99 + 0.01
outputs = n.query(inputs)
answer[1] = numpy.argmax(outputs)
img3 = np.array(img3).flatten()
inputs = invert_pivture(img3/255.0)
inputs = inputs * 0.99 + 0.01
outputs = n.query(inputs)
answer[2] = numpy.argmax(outputs)
img4 = np.array(img4).flatten()
inputs = invert_pivture(img4/255.0)
inputs = inputs * 0.99 + 0.01
outputs = n.query(inputs)
answer[3] = numpy.argmax(outputs)
print(results)
print(answer)
if(int(results[0])==int(answer[0])|int(results[1])==int(answer[1])|int(results[2])==int(answer[2])|int(results[3])==int(answer[3])):
score = score+1
pass
pass
f.close()
print(score/200)
pass
在我的笔记本电脑满负荷跑不到一分钟以后,激动人心的时刻到了,让我们看一看最后的分数是多少:
结果为0.0……
好吧,看来最简单的方法没有办法破解这种验证码,我们还要继续努力呀。
总结:
验证码的识别并没有想象中的那么简单,并不是我们会编写一个简单的神经网络就能解决的问题,机器学习是一个很大的类别,其中的知识数不胜数。但是今天的文章就到这里吧,如果还有兴趣继续深入了解的小伙伴们,可以继续的关注文章,如果实在没有兴趣,这里就给一点小小的建议,给出一点提示:
(1)重点在验证码的分割,只要分割做得好,验证码的识别就不是大的问题,简单说如果是一个个简单的字符,一个简单的神经网络就可以破解。有兴趣深入的小伙伴可以了解一下垂直投影法、连通域分割法以及滴水法,都是比较好的分割方法。
(2)关于神经网络:我们的简单的神经网络当然不能再用了,现在很少有人还用自己编写的神经网络了,没有任何的用处。推荐几个有名的神经网络,LeNet,AlexNet,keras,TensorFlow以及pytorch,大家可以考虑一下如何将它们用在其中。当然目前有的验证码识别已经不需要进行图片的分割处理,大家可以了解一下,但是目前来说,我还是会继续的一步步提高识别率,知道达到满意的效果。
今天就到这里吧,代码和数据就在下面的链接中,百度网盘可以直接的下载,
百度网盘链接——神经网络3.0
提取码:ojxn
其中包含五个图片以及每一个图片的内容,(不要怪我不分享,一个是我也是厚着脸皮要来的,都放上去有点对不起学长学姐,另一个知道原理就够了,没必要真的完整的走一波,毕竟识别率也是0.0呀,没用)
优快云的链接以后再推吧,今天有点累了,就到这里为止吧。