那些年,我爬過的北科(八)——反反爬蟲之驗證碼識別

寫在前面

本章將要介紹一下如何識別簡單的驗證碼。會涉及到一些圖像的概念以及機器學習的知識。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個。直接經過文件名稱進行標識。

機器學習之KNN算法

算法描述

K近鄰算法的定義十分簡單,在百度百科上有這樣的解釋:若是一個樣本在特徵空間中的k個最類似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。

也就是說,須要找到要識別的字母在訓練樣本中K個最近的字母,而後找出這K個字母中最多的是某個類的?要識別的圖片也就是該類的。

算法舉例

上面那麼描述可能稍微有點兒晦澀,那咱們舉個例子。

這裏以電影分類做爲例子,電影題材可分爲愛情片,動做片等。這裏假定將電影分爲愛情片和動做片兩類,直觀感覺的話,若是一部電影中接吻鏡頭不少,打鬥鏡頭較少,顯然是屬於愛情片,反之爲動做片。

這裏咱們的數據有兩個特徵:一個是接吻鏡頭的數目,一個是打鬥鏡頭的數目。下面咱們有一組已知的數據。

咱們這裏的目標是利用已知的四個電影數據,預測未知電影的類型。

咱們把愛情電影定義爲紅色的叉子,動做電影定義爲綠色的圓圈,未知電影爲問號。這裏能夠畫個圖直觀感覺一下。

假設咱們K取3的話,那麼從圖中能夠很清晰的看到,離未知電影最近的三個電影分別是:愛情電影、愛情電影、動做電影。愛情電影占比大,因此咱們未知的電影是愛情片。

scikit-learn中使用KNN

咱們將使用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))
複製代碼

咱們運行一下,能夠看到如下識別的結果,都識別出來了。

內容補充

以上的驗證碼識別只是一個基本的操做流程。如今只要有足夠多的數據,利用深度學習基本上全部的驗證碼都能識別出來。

深度學習因爲須要讀者有數學基礎以及相關的背景知識,這裏筆者就提供一些我本身寫過的驗證碼相關的資料,若是感興趣能夠本身去學習。

若是讀者感興趣能夠進行深刻學習。

筆者的驗證碼相關的一個項目:

筆者的驗證碼相關的博客:

相關文章
相關標籤/搜索