源: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,咱們這些沒有大量計算資源的普通人也能夠用它們實現本身的想法了。
推薦參考連接: