本章將要介紹一下如何識別簡單的驗證碼。會涉及到一些圖像的概念以及機器學習的知識。python
咱們本次識別的驗證碼來自csdn,長相以下: git
在學習以前,咱們先安裝本章須要的三個庫:圖像庫Pillow、機器學習庫Scikit-Learn、科學計算庫Numpy。經過pip命令就能夠進行安裝。github
pip install pillow scikit-learn numpy
複製代碼
本章節的案例稍微複雜,見:USTBCrawlers/lesson8算法
這裏主要有三個部分:下載器、分割器、與識別器。咱們能夠先把代碼clone下來,而後進入到lesson8這個目錄下。數組
下面咱們先來對圖像有個基本的介紹。圖像是由一個一個像素點構成的,其內部結構是一個二維的矩陣,或者理解成一個二維數組。bash
例如,一個 M x N 的圖像,能夠表示成如下的格式: 網絡
在python中,咱們可使用PIL(Pillow)對圖像進行操做。以下,咱們打開了咱們的驗證碼,並調用convert("L")
方法把圖片轉爲灰度圖像。app
from PIL import Image
im = Image.open("csdn.png").convert("L")
im.show() # 顯示圖像
複製代碼
然而咱們真正操做並非圖像對象,而是一個矩陣,或者說是二維數組,咱們能夠把圖像轉成numpy數組。 less
能夠看到咱們的驗證碼是20*48的。機器學習
圖像是由一個個的像素構成的,像素有灰度值,從0-255,一共256個灰度級。直方圖的做用是觀察每一個灰度級所佔像素的多少。
能夠調用Image.histogram()
獲取Image對象的直方圖。
好比說對於咱們的驗證碼圖片,一共有20*48=960個像素點,其中灰度級爲94的像素有754個,而灰度級爲255的有129個。
再次觀察一下咱們灰度化的驗證碼,能夠看到驗證碼的字母是白色的,也就是灰度級爲255。周圍的背景是灰色的,灰度級爲94。
在處理驗證碼的時候,背景不少時候並非同一個灰度級的,爲了減小背景對數據的影響。通常都會講驗證碼進行二值化。
所謂二值化,其實就是把灰度圖像變成只由純黑、或純白兩種像素組成的圖像。
方法很簡單,咱們能夠設定一個閾值,灰度大於100的像素都變成純白(255),而灰度小於100的像素都變成純黑(0)。
由於驗證碼包含四個數字,因此須要把每一個字母分割開。筆者爲讀者準備的驗證碼是很好分割的類型,只須要對指定區域進行篩選便可。
這裏的圖片數字的寬度都是8,起點分別位於五、1四、2三、32。分割代碼以下:
def split_and_save(path):
path = "../downloader/captchas/" + path
pix = np.array(Image.open(path).convert("L"))
# threshold image
pix = (pix > 100) * 255
col_ranges = [
[5, 5 + 8],
[14, 14 + 8],
[23, 23 + 8],
[32, 32 + 8]
]
# split and save
for col_range in col_ranges:
letter = pix[:, col_range[0]: col_range[1]]
im = Image.fromarray(np.uint8(letter))
save_path = "./letters/" + str(uuid.uuid4()) + ".png"
im.save(save_path)
複製代碼
咱們每一個驗證碼字符的大小爲:20*8。
對驗證碼分割後,咱們就會獲得一堆字母的圖片了。
可是這些圖片都沒有標註,下面咱們使用的機器學習算法是數據驅動的,因此須要一些已經標註好的驗證碼數據。我這裏標註的方法比較簡單,由於畢竟只有0-9十種字母,我就每種數據標註6個。直接經過文件名稱進行標識。
K近鄰算法的定義十分簡單,在百度百科上有這樣的解釋:若是一個樣本在特徵空間中的k個最類似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。
也就是說,須要找到要識別的字母在訓練樣本中K個最近的字母,而後找出這K個字母中最多的是某個類的?要識別的圖片也就是該類的。
上面那麼描述可能稍微有點兒晦澀,那咱們舉個例子。
這裏以電影分類做爲例子,電影題材可分爲愛情片,動做片等。這裏假定將電影分爲愛情片和動做片兩類,直觀感覺的話,若是一部電影中接吻鏡頭不少,打鬥鏡頭較少,顯然是屬於愛情片,反之爲動做片。
這裏咱們的數據有兩個特徵:一個是接吻鏡頭的數目,一個是打鬥鏡頭的數目。下面咱們有一組已知的數據。
咱們這裏的目標是利用已知的四個電影數據,預測未知電影的類型。
咱們把愛情電影定義爲紅色的叉子,動做電影定義爲綠色的圓圈,未知電影爲問號。這裏能夠畫個圖直觀感覺一下。
假設咱們K取3的話,那麼從圖中能夠很清晰的看到,離未知電影最近的三個電影分別是:愛情電影、愛情電影、動做電影。愛情電影占比大,因此咱們未知的電影是愛情片。
咱們將使用scikit-learn來實現KNN,因此不須要關注算法的實現(雖然實現也很簡單),只要有數據和標籤就行了。咱們來看看怎麼實現上面的預測電影類型的功能。
這裏X是咱們已知類型的四部電影,y是四部電影的標籤。0表明愛情電影,1表明動做電影。而後調用scikit-learn中的KNeighborsClassifier
先經過fit
擬合數據,再調用predict
預測就行了。
能夠看到咱們最後預測出未知電影的標籤爲0,也就是愛情電影,和想法一致。
瞭解了以上知識後,咱們能夠編寫驗證碼識別腳本了。
咱們這裏首先編寫加載數據的函數,加載以前標註好的驗證碼字母數據。咱們驗證碼字母數據是20*8的,也就是至關於有160個特徵。
def load_dataset():
X = []
y = []
for i in range(60):
path = "./dataset/%d%d.png" % (i / 6, i % 6 + 1)
pix = np.array(Image.open(path).convert("L"))
X.append(pix.reshape(8*20))
y.append(i/6)
return np.array(X), np.array(y)
複製代碼
而後對數據進行擬合:
X, y = load_dataset()
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X, y.astype('uint8'))
複製代碼
最後先分割圖片,再使用擬合好數據的knn進行預測。
def split_letters(path):
pix = np.array(Image.open(path).convert("L"))
# threshold image
pix = (pix > 100) * 255
col_ranges = [
[5, 5 + 8],
[14, 14 + 8],
[23, 23 + 8],
[32, 32 + 8]
]
letters = []
for col_range in col_ranges:
letter = pix[:, col_range[0]: col_range[1]]
letters.append(letter.reshape(8*20))
return letters
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python recognizer.py <image_filename>")
letters = split_letters(sys.argv[1])
print(knn.predict(letters))
複製代碼
咱們運行一下,能夠看到如下識別的結果,都識別出來了。
以上的驗證碼識別只是一個基本的操做流程。如今只要有足夠多的數據,利用深度學習基本上全部的驗證碼都能識別出來。
深度學習因爲須要讀者有數學基礎以及相關的背景知識,這裏筆者就提供一些我本身寫過的驗證碼相關的資料,若是感興趣能夠本身去學習。
若是讀者感興趣能夠進行深刻學習。
筆者的驗證碼相關的一個項目:
筆者的驗證碼相關的博客: