前言
CAPTCHA全稱Completely Automated Public Turing Test to Tell Computers and Humans Apart,即全自動區分人機的圖靈測試。這也是驗證碼誕生的主要任務。但是隨著近年來大數據運算和機器視覺的發展,用機器視覺識別圖像已經變得非常容易,過去用于區分人機的驗證碼也開始變得不再安全。
接下來就讓我們從零開始,深入圖像處理和算法構建,來看看使用機器視覺來識別過時的驗證碼( 如下所示 )究竟可以有多簡單。
載入需要的程序包 & 設置全局變量
import requests import time from io import BytesIO from PIL import Image import os import numpy as np # 獲取驗證碼的網址 CAPT_URL = "http://xxxxxxxxxxxx.cn/servlet/ImageServlet" # 驗證碼的保存路徑 CAPT_PATH = "capt/" if not os.path.exists(CAPT_PATH): os.mkdir(CAPT_PATH) # 將驗證碼轉為灰度圖時用到的"lookup table" THRESHOLD = 165 LUT = [0]*THRESHOLD + [1]*(256 - THRESHOLD)
從網站獲取驗證碼
capt_fetch()方法非常簡單,我們直接從網站獲取驗證碼,將其轉換為Image對象,等待被訓練和測試等環節調用。
def capt_fetch(): """ 從網站獲取驗證碼,將驗證碼轉為Image對象 :require requests: import requests :require time: import time :require BytesIO: from io import BytesIO :require Image: from PIL import Image :param: :return capt: 一個Image對象 """ # 從網站獲取驗證碼 capt_raw = requests.get(CAPT_URL) # 將二進制的驗證碼圖片寫入IO流 f = BytesIO(capt_raw.content) # 將驗證碼轉換為Image對象 capt = Image.open(f) return capt
保存驗證碼到本地
- 一個強大的機器學習模型,是離不開強大的訓練集作支持的。這里我們也必須先有一個預先打好標簽(預分類)的驗證碼圖片集,才能開始訓練模型。
- capt_download()方法就是我們用來建立訓練圖像集的方法。它會調用capt_fetch()方法,將獲得的Image對象展示給用戶,等待用戶輸入驗證碼中的字符,然后將圖片命名為用戶輸入的字符存儲起來。
- 當然,為了避免文件名重復(比如獲取到了兩張字符完全相同的驗證碼),capt_download()方法將系統時間也加入到了文件名中。
def capt_download(): """ 將Image類型的驗證碼對象保存到本地 :require Image: from PIL import Image :require os: import os :require capt_fetch(): 從nbsc網站獲取驗證碼 :require CAPT_PATH: 驗證碼保存路徑 :param: :return: """ capt = capt_fetch() capt.show() text = raw_input("請輸入驗證碼中的字符:") suffix = str(int(time.time() * 1e3)) capt.save(CAPT_PATH + text + "_" + suffix + ".jpg")
?圖像預處理
- capt_process()方法會先將驗證碼轉為灰度圖,然后再根據全局變量中定義的LUT將灰度圖轉化為黑白圖片。并按照驗證碼中四個字符所在的位置進行切割。
- 從彩色圖片到灰度圖,再到黑白圖,看似驗證碼中的信息損失了很多,實際上這樣做的目的是為了使字符的特征更加明顯。
- 其實我們最終得到的黑白圖像會有一些噪點存在,這主要是由于前景色與背景色不存在嚴格的區分度,我們可以使用濾波器過濾掉這些噪點,但少量的噪點會被訓練模型當作誤差處理,并不影響我們分類。至于過濾噪點的方法,我會專門寫一篇帖子。
def capt_process(capt): """ 圖像預處理:將驗證碼圖片轉為二值型圖片,按字符切割 :require Image: from PIL import Image :require LUT: A lookup table, 包含256個值 :param capt: 驗證碼Image對象 :return capt_per_char_list: 一個數組包含四個元素,每個元素是一張包含單個字符的二值型圖片 """ capt_gray = capt.convert("L") capt_bw = capt_gray.point(LUT, "1") capt_per_char_list = [] for i in range(4): x = 5 + i * 15 y = 2 capt_per_char = capt_bw.crop((x, y, x + 15, y + 18)) capt_per_char_list.append(capt_per_char) return capt_per_char_list
圖像預處理的效果如下:
原始圖像
灰度圖
黑白圖
按字符切分后的黑白圖像
由于字符寬窄有差異,這里我們按字符切分后,有些字符會多出來一部分,有些字符會丟失一部分。比如7多了一筆看起來像個三角形,M少了一豎看起來像N。但只要符號之間有區分度,依然能夠準確分類。
提取圖像中的特征值
- 到了這一步,我們得到的圖像都是由單個字符組成的黑白圖片(0為黑色像素點,1為白色像素點)。此時,如果我們把圖片轉為數組,就會得到一個由0,1組成的矩陣,其長寬恰與圖片的大小相同, 每一個數字代表一個像素點。
- 接下來我們需要考慮如何提取能夠區分不同字符的特征值。我們可以直接用圖像中的每一個像素點作為一個特征值,也可以匯總圖像中共有多少黑色像素點(當然,這樣每張圖片只能提取一個特征值),還可以按區域匯總圖像中的像素點,比如先將圖片四等分,匯總四張“子圖片”中的像素點,如果覺得特征不夠多,還可以繼續下分,直至精確到每一個像素點。
-
為了使代碼更加簡潔,我們這里直接匯總、分行列匯總了圖像像素點的個數,共提取了1張圖片 + 15列 + 18行 ==> 34個特征值。至于按區域匯總的方法,還是等我們有空了單獨寫一篇帖子。
def capt_inference(capt_per_char): """ 提取圖像特征 :require numpy: import numpy as np :param capt_per_char: 由單個字符組成的二值型圖片 :return char_features:一個數組,包含 capt_per_char中字符的特征 """ char_array = np.array(capt_per_char) total_pixels = np.sum(char_array) cols_pixels = np.sum(char_array, 0) rows_pixels = np.sum(char_array, 1) char_features = np.append(cols_pixels, rows_pixels) char_features = np.append(total_pixels, char_features) return char_features.tolist()
生成訓練集
這里我們會將預分類的每張驗證碼分別讀入內存,從它們的圖像中提取特征值,從它們的名稱中提取驗證碼所對應的文字(標簽),并將特征值與標簽分別匯總成列表,注意train_labels中的每個元素是與train_table中的每一行相對應的。
def train(): """ 將預分類的驗證碼圖片集轉化為字符特征訓練集 :require Image: from PIL import Image :require os: import os :require capt_process(): 圖像預處理 :require capt_inference(): 提取圖像特征 :param: :return train_table: 驗證碼字符特征訓練集 :return train_labels: 驗證碼字符預分類結果 """ files = os.listdir(CAPT_PATH) train_table = [] train_labels = [] for f in files: train_labels += list(f.split("_")[0]) capt = Image.open(CAPT_PATH + f) capt_per_char_list = capt_process(capt) for capt_per_char in capt_per_char_list: char_features = capt_inference(capt_per_char) train_table.append(char_features) return train_table, train_labels
定義分類模型
- 只要我們提取的特征值具有足夠的區分度(能夠區分不同字符),理論上我們可以使用任何機器學習的模型建立特征值與標簽的相關。
- 我嘗試過使用knn,svm,Decision Tree,ANN等各種主流機器學習模型對提取的訓練數據進行分類,盡管不同模型表現各異,但識別準確率都可以很輕松的達到90%以上。
- 這里我們不打算使用復雜的第三方模型完成學習過程,所以我們在這里自己寫了一個分類模型。模型實現的nnc算法是knn的一種,通過比對未知分類的一組特征值測試集與那一組已知分類的特征值訓練集最接近,判定測試集的分類情況應該和與其最接近的訓練集的分類情況訓練集標簽相同。
-
當然,我們也可以稍加改造直接實現knn算法,找到3組、5組或k組與未知分類的特征向量最接近的訓練集中的特征向量,并通過票選與其對應的標簽,預測未知分類的特征向量在大概率上應該屬于哪一類。
def nnc(train_table, test_vec, train_labels): """ Nearest Neighbour Classification(近鄰分類法), 根據已知特征矩陣的分類情況,預測未分類的特征向量所屬類別 :require numpy: import numpy as np :param train_table: 預分類的特征矩陣 :param test_vec: 特征向量, 長度必須與矩陣的列數相等 :param labels: 特征矩陣的類別向量 :return : 預測特征向量所屬的類別 """ dist_mat = np.square(np.subtract(train_table, test_vec)) dist_vec = np.sum(dist_mat, axis = 1) pos = np.argmin(dist_vec) return train_labels[pos]
?測試模型分類效果
最后,我們需要測試我們的理論是否有效,通過調用test()方法,我們會先從網站獲取驗證碼圖像,對圖像進行處理、特征提取,然后調用nnc()方法對提取到的四組特征值做近鄰分類,分別得到驗證碼中的四個字符。最后將驗證碼圖像和識別到的字符傳出,方便我們比對識別結果。
def test(): """ 測試模型分類效果 :require Image: from PIL import Image :require capt_fetch(): 從nbsc網站獲取驗證碼 :require capt_process(): 圖像預處理 :require capt_inference(): 提取圖像特征 :train_table, train_labels: train_table, train_labels = train() :param: :return capt: 驗證碼圖片 :return test_labels: 驗證碼識別結果 """ test_labels = [] capt = capt_fetch() capt_per_char_list = capt_process(capt) for capt_per_char in capt_per_char_list: char_features = capt_inference(capt_per_char) label = nnc(train_table, char_features, train_labels) test_labels.append(label) test_labels = "".join(test_labels) return capt, test_labels
訓練數據,識別驗證碼
- 方法具備,接下來就是我們實踐的環節了。
- 首先,我們需要建立一個機器學習庫,即一個預分類的驗證碼圖片集。這里我們僅僅獲取了120張驗證碼作為訓練集,相比tensorflow動輒成千上萬次的迭代,我們建立模型所需的樣本量非常之少。當然,這也要感謝我們使用的nnc算法并不需要十分龐大的訓練集支持,才使得我們能夠節省很多預分類時人工識別驗證碼的精力。
-
接下來,我們會調用train()方法生成訓練集和訓練集標簽,這兩個數組會被test()方法用到,但我們把這兩個數組存儲在全局變量里,所以不需要特意傳遞給test()方法。
# 下載120張圖片到本地 for i in range(120): capt_download() # 模型的訓練與測試 train_table, train_labels = train() test_capt, test_labels = test()
最后我們調用test()方法驗證我們的理論是否成立,識別效果如下:
獲取到的驗證碼
識別結果
結語
至此,我們通過機器視覺識別驗證碼的任務算是完成了。至于正確率,大概每10張驗證碼,40各字符中會預測失誤一個字符。這已經比較接近我們人類的識別準確率了,當然,我們還可以通過建立起更龐大的學習庫,使用knn或更復雜的模型,使用卷積核處理圖片等方式,使識別準確率更高。
當然,我們這里所用的方法,只適用于識別比較簡單的驗證碼。對于更加復雜的驗證碼(如下圖),以上方法是不起作用的,但這并不代表這樣的驗證碼不能通過機器視覺進行識別。
我們已經看到,隨著機器視覺的發展,通過傳統的驗證碼來區分人機已經越來越難了。當然,為了網絡的安全性,我們也可使用更復雜的驗證碼,或者新型的驗證方式,比如拖動滑塊、短信驗證、掃碼登陸等。
以上所述是小編給大家介紹的python自動識別驗證碼詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
