源:http://lenciel.com/2016/06/python-captcha-solution/python
昨天饭局上聊起来自动化测试或者是别的奇怪事业里常常须要面对的一个问题:验证码识别。git
其实验证码的识别,技术上来讲能够做为古老的OCR(Optical Character Recognition)问题的一个子集:由于OCR其实就是从图片上把文字认出来嘛。github
但它的有趣之处在于,验证码,也就是CAPTCHA,自己就是’Completely Automated Public Turing test to tell Computers and Humans Apart’的缩写,也就是说在设计上它的目的就是要:算法
因此若是你电脑识别出来了验证码,要么就是它特别容易不符合#2的要求,要么就是你实现了很不错的人工智能算法,这篇文章是讲第一种状况。bash
传统的作法来识别OCR,主要须要处理的是下面三个环节:app
所谓的“二值化”,就是图片上的像素要么灰度是255(白),要么是0(黑)。大体的思路就是把灰度大于或等于阈值的像素判为属于你关注的文字,置成0;其余的像素点灰度置为255。ide
具体的操做,我通常使用下面几种方式:工具
convert
命令因此下面这两个验证码,哪一个的难度大一些?测试
图1. 微林的验证码字体
图2. 饭局后J.Snow提供的验证码
若是你脑子里面没有二值化的概念大概会以为第一个难度大一些,由于以人眼的视线去考虑,好像第一张要“难分辨”一些。
但其实第一张图全部的噪声都是花花绿绿的颜色,而验证码自己是纯粹的黑色,这种图片处理起来是相对容易的。只须要找到验证码像素点的颜色,用这种颜色选取这些像素点,拷贝到一张全白的图片上面便可。
要获取验证码的像素颜色能够参考这里的思路,把图片转成256色的,而后对全部的像素作一个统计而后标出它们在整个图片里面出现的频率。由于以为原文里面的代码写得比较啰嗦(要学会写lamda啊)就作了一些修改:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import sys from PIL import Image def get_top_pixels(file_path, min_pt_num): im = Image.open(file_path) im = im.convert("P") top_pixels = [] for index in enumerate(im.histogram()): if index[1] > int(min_pt_num): top_pixels.append(index) return sorted(top_pixels, key=lambda x: x[1], reverse=True) if __name__ == '__main__': print(get_top_pixels(sys.argv[1], sys.argv[2])) |
这个程序运行的结果以下:
$ python get_histdata.py regcode.png 30
[(0, 1471), (1, 214), (10, 110), (11, 97), (2, 85), (9, 83), (6, 66), (8, 58), (7, 49), (5, 37)]
拿到了颜色,就能够写一个简单的程序从图片里面拷贝这些像素到一张干净的图:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import sys from PIL import Image def clean_image(file_path, key_pix): im = Image.open(file_path) im = im.convert("P") im2 = Image.new("P", im.size, 255) for x in range(im.size[1]): for y in range(im.size[0]): pix = im.getpixel((y, x)) # color of pixel to get if pix == key_pix: im2.putpixel((y, x), 0) im2.save("convert_%s.png" % key_pix) if __name__ == '__main__': clean_image(sys.argv[1], sys.argv[2]) |
出现的最多的0
显然是背景色,因此对1
和10
运行脚本:
$ python convert_grayscale.py regcode.png 1
$ python convert_grayscale.py regcode.png 10
结果以下:
很明显目标像素是1而不是10。
而J. Snow的这张图,首先验证码自己就是幻彩的而不是均匀一致的颜色,而后噪声又都是用这些幻彩颜色来生成的,因此若是只是简单的对颜色排序,会获得下面的结果:
[(225, 349), (139, 170), (182, 161), (219, 95), (224, 64), (189, 54), (175, 47), (218, 40), (90, 36), (96, 33)]
而后咱们对排名靠前的像素进行提取会获得下面的结果:
这种状况下怎么办?直观观察一下验证码,会发现背景噪声点相比验证码像素点来讲不多(这也正常,都是一个颜色若是太多就无法看了), 很适合先作一些切割,而后进行模糊匹配(由于验证码的像素是幻彩的不是单一的,须要匹配相近像素点),而后再作二值化。
直接用IM的convert来处理比写代码简单:
1
|
$ convert 1.pic.jpg -gravity Center -crop 48x16+0+0 +repage -fuzz 50% -fill white -opaque white -fill black +opaque white resultimage.jpg |
效果以下:
其实整个验证码的识别里面,最难的是分割。特别是不少严肃的验证码,字体不是标准字体或者会变形,互相还可能粘连或者重叠,分割起来是很是难的。
但这里拿到的验证码相对简单,这部分不是问题就不展开了。
对于这里拿到的验证码而言,由于都是标准字体,能够直接使用OCR的开源工具读取,好比tesseract:
1
2 3 4 5 6 |
$ tesseract resultimage.jpg -psm 7 output && cat output.txt Tesseract Open Source OCR Engine v3.04.01 with Leptonica Warning in pixReadMemJpeg: work-around: writing to a temp file YLNU |
若是不是标准字体的,由于分割完毕了就拿到了独立的字符,要识别就能够建一个模型,不断的训练它,来识别每一个字符。
可能你会以为围棋电脑都会下了,那么认识验证码为何仍是比较难?
其实随便搜一下就会发现有不少人在作这方面的实验,主要的思路就是把n个字符组成的验证码当成有n个标签的图片来用CNN来解决。加上最近不少大公司开放了本身的人工智能平台,好比Google的Tensorflow,咱们这些没有大量计算资源的普通人也能够用它们实现本身的想法了。
推荐参考连接: