用python通過機器學習的方式實現驗證碼識別

前言:

欲練此功,必先自宮;

就算自宮,未必成功;

若不自宮,也能成功。

python+機器學習+驗證碼識別+源碼

作者:hlpureboy

簡單介紹

最近在測試某網站的自動註冊,在註冊的過程中遇到一些問題,如js的執行、驗證碼的識別等等,今天給大家分析一下如何用python通過機器學習的方式實現驗證碼的識別,揮刀自宮,以i春秋驗證碼為例進行搞事,儘可能的用簡單的方式表述。

使用技術

  • [x] python 2.7 32 位 所需的庫 PIL sklearn numpy pandas matplotlib
  • [x] 數字圖像處理基礎(中值濾波等)
  • [x] 機器學習KNN演算法

首先獲取驗證碼

發現i春秋的驗證碼的url:user.ichunqiu.com/login,如果你點擊的話,發現驗證碼無法獲取到,抓一下包,怎麼抓包,可以用burpsuite或者瀏覽器按f12就成,在這裡不再贅述。發現在headers中要有Referer,構造headers的。

headers={ "Cookie":"XXXXXXXXXXXXXX", "Referer":"https://user.ichunqiu.com/register/index", "Upgrade-Insecure-Requests":"1", "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"}

簡單寫一下下載驗證碼的代碼

url="https://user.ichunqiu.com/login/verify_image?d=1523697519026"for i in range(0,51): with open("./pic/{}.png".format(i),"wb") as f: print("{} pic downloading...".format(i)) f.write(requests.get(url,headers=headers).content)

在pic目錄下獲得50張驗證碼照片了

驗證碼處理

ok!既然獲取到驗證碼了,肯定要對驗證碼進行一定程度的處理!

像上圖這樣的二維碼,怎麼做訓練么?!最起碼要給它去掉臉上的雀斑(點點),修復臉上的刀疤(條紋),怎麼辦呢?難道要寄出神器PS?不存在的,兄die! 我們要做的是批量處理,用ps一張張的人工處理,很明顯不符合懶人的風格,怎麼辦?

下面肯定是祭出數字圖像處理了,有些同學看到這裡就發怵,沒關係,這裡沒有什麼技術難度的!相信我,也相信自己。我突然,發現我有給人灌雞湯的天賦!下面正式開始講解!

  1. 首先,觀察原圖像(t2.png)!即

是不是發現它的色彩十分多呀!在數字圖像領域中,我們一般研究灰度圖像,那麼?問題來了什麼是灰度圖像呢?簡單地說,整幅圖片都是灰色的!原圖片經過灰度處理是這樣的。

是不是都是灰色的呢?但是我們得到的灰度圖,有些地方比較淺,有些地方顏色比較深,這是為什麼呢?下面是重點內容,都給我聽好啦!圖像在計算機中是用矩陣進行存儲的,可能,你不知道矩陣是什麼東西。那少年我推薦你一本《工程數學 線性代數 》看完一遍保你收穫頗豐!當然啦,實在不想看,可以去百度百科呀,矩陣定義,怎麼樣我貼心吧?!矩陣中每一個數代表著一個像素!數值的大小反映在圖像中就是圖像的深淺!值越大,顏色越深! 這就說明了,灰度圖中顏色深淺的問題,好吧!道理我都懂,怎麼轉化成灰度圖呢?代碼如下

# encoding=utf-8from PIL import Image,ImageFilterimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport matplotlib.cm as cm# 導入相應的庫img=Image.open("t2.png")# 載入t2.pngim=img.convert("L")# 這裡重點!img圖像轉化為灰度圖像im、plt.subplot(4,2,1)# 關於plt這個用法在網上有很多教程,我在這裡就不一一贅述了plt.imshow(np.array(im),cmap=cm.gray)

  1. 現在我們得到灰度圖像:

觀察發現,驗證碼都是深顏色的,要不把淺顏色的「雀斑」去掉,怎麼去掉呢?肯定在矩陣中找一個值(一般稱之為閾值),小於這個值的數都變成零(白色),大於這個數的值都變成最大值(黑),咦!灰度圖像變成了黑白圖像!怎麼找這個閾值呢?一般用矩陣的平均數或者中位數,納尼?什麼是中位數,找一個小學課本翻一番。好!那我們分別取平均數和中位數,做一下處理看一下效果!

平均數效果如下:

中位數效果如下:

那麼肯定是用平均數做呀!代碼如下!

# encoding=utf-8from PIL import Image,ImageFilterimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport matplotlib.cm as cmimg=Image.open("t2.png")im=img.convert("L")#plt.imshow(img)im_z=np.array(im)# im轉化為im_z矩陣print(im_z.mean())# im_z的平均值print(np.median(im_z))# im_z的中位數plt.subplot(4,2,1)plt.imshow(np.array(im),cmap=cm.gray)plt.subplot(4,2,2)plt.imshow(img)im_b=im.point(lambda i:i>197,mode=1)# 用im_z的平均值197轉化為黑白圖像im_bplt.subplot(4,2,3)plt.imshow(im_b)# 顯示im_bim_a=im.point(lambda i:i>220,mode=1)# 用im_z的平均值220轉化為黑白圖像im_aplt.subplot(4,2,4)plt.imshow(im_a)# 顯示im_a

  1. 得到黑白圖像了,可還是有「雀斑」怎麼辦,用上神奇的中值濾波啦!中值濾波定義,一般都是在灰度圖上進行中值濾波,我也是突然想看一下,在黑白圖像的處理結果怎麼樣(當然,這裡的中值只能是零或一啦),結果還不錯!

在這裡要說的是,中值濾波這個概念,比較容易理解,我這個小菜雞,概括的不怎麼樣!只能委屈大家看一下優秀的博客啦。處理代碼如下

# encoding=utf-8from PIL import Image,ImageFilterimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport matplotlib.cm as cmimg=Image.open("t2.png")im=img.convert("L")#plt.imshow(img)im_z=np.array(im)print(im_z.mean())print(np.median(im_z))plt.subplot(4,2,1)plt.imshow(np.array(im),cmap=cm.gray)plt.subplot(4,2,2)plt.imshow(img)im_b=im.point(lambda i:i>197,mode=1)plt.subplot(4,2,3)plt.imshow(im_b)im_a=im.point(lambda i:i>220,mode=1)plt.subplot(4,2,4)plt.imshow(im_a)#################下面是核心代碼###############im_f=im_b.filter(ImageFilter.MedianFilter(size=3))# 用 im_b圖像進行中值濾波,掩模用的3×3的模板plt.subplot(4,2,5)plt.imshow(im_f)# 顯示 im_fplt.show()

  1. 得到乾淨的驗證碼,這樣就結束了么?no!no!no! 緊接接著,要把單個的字元分割出來!怎麼分割來?下面我會給出代碼,在代碼中給出解釋

# encoding=utf-8from PIL import Image,ImageFilterimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport matplotlib.cm as cmimg=Image.open("t2.png")im=img.convert("L")#plt.imshow(img)im_z=np.array(im)print(im_z.mean())print(np.median(im_z))plt.subplot(4,2,1)plt.imshow(np.array(im),cmap=cm.gray)plt.subplot(4,2,2)plt.imshow(img)im_b=im.point(lambda i:i>197,mode=1)plt.subplot(4,2,3)plt.imshow(im_b)im_a=im.point(lambda i:i>220,mode=1)plt.subplot(4,2,4)plt.imshow(im_a)im_f=im_b.filter(ImageFilter.MedianFilter(size=3))plt.subplot(4,2,5)plt.imshow(im_f)plt.show()##########下面是核心代碼###############im=im_fprint(im.size)a = np.array(im)# im轉化為a矩陣pd.DataFrame(a.sum(axis=0)).plot.line() # 畫出每列的像素累計值plt.imshow(a,cmap=gray) # 畫出圖像split_lines = [7,25,44,60,78]# 經過調整過的分割線的合理間距vlines = [plt.axvline(i, color=r) for i in split_lines] # 畫出分割線plt.show()

得到圖像如下

嘖嘖嘖!我都有點佩服我自己呢!下面要把字元一個個的分割出來!同時要把上邊和下邊的黑線去掉!

怎麼辦呢?talk is cheap,show my code!

# encoding=utf-8from PIL import Image,ImageFilterimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport matplotlib.cm as cmimg=Image.open("t2.png")im=img.convert("L")#plt.imshow(img)im_z=np.array(im)print(im_z.mean())print(np.median(im_z))plt.subplot(4,2,1)plt.imshow(np.array(im),cmap=cm.gray)plt.subplot(4,2,2)plt.imshow(img)im_b=im.point(lambda i:i>197,mode=1)plt.subplot(4,2,3)plt.imshow(im_b)im_a=im.point(lambda i:i>220,mode=1)plt.subplot(4,2,4)plt.imshow(im_a)im_f=im_b.filter(ImageFilter.MedianFilter(size=3))plt.subplot(4,2,5)plt.imshow(im_f)plt.show()im=im_fprint(im.size)a = np.array(im)pd.DataFrame(a.sum(axis=0)).plot.line() # 畫出每列的像素累計值plt.imshow(a,cmap=gray) # 畫出圖像split_lines = [7,25,44,60,78]vlines = [plt.axvline(i, color=r) for i in split_lines] # 畫出分割線plt.show()#im.crop()#################核心代碼##########################y_min=5y_max=35#設置獲取圖像的高和寬ims=[]c=1for x_min,x_max in zip(split_lines[:-1],split_lines[1:]): im.crop([x_min,y_min,x_max,y_max] ).save(str(c)+.png) # crop()函數是截取指定圖像! # save保存圖像! c=c+1for i in range(1,5): file_name="{}.png".format(i) plt.subplot(4,2,i) im=Image.open(file_name).convert("1") #im=img.filter(ImageFilter.MedianFilter(size=3)) plt.imshow(im) # 顯示截取的圖像!plt.show()

得到的圖像結果如下!

5.還記得咱們在第一步中獲取的50張圖片么?沒錯!把他們都分割出來吧!整理一下代碼!

# encoding=utf-8 from sklearn.neighbors import KNeighborsClassifier as KNN from sklearn.externals import joblib from PIL import Image,ImageFilter import numpy as np def imgprocess(name): # 圖片分割成單個字元! path="./pic/{}.png".format(str(name)) img=Image.open(path).convert("L") th=np.array(img).mean() im_b = img.point(lambda i: i > th, mode=1) im_f= im_b.filter(ImageFilter.MedianFilter(size=3)) split_lines = [7, 25, 43, 61, 79] y_min = 5 y_max = 35 ims = [] c = 1 for x_min, x_max in zip(split_lines[:-1], split_lines[1:]): im_f.crop([x_min, y_min, x_max, y_max]).save(./pic2later/{}-{}.png.format(str(name),str(c))) c = c + 1 for i in range(1,51): print("process {} pic".format(i)) imgprocess(i)

機器學習之KNN

1.關於knn演算法呢,我以前做過一些筆記,我po上去了

鏈接: pan.baidu.com/s/1g0_fRL 密碼: gizr

大家記得看一下!

2.相應的代碼及註解

# encoding=utf-8from sklearn.neighbors import KNeighborsClassifier as KNNfrom sklearn.externals import joblibfrom PIL import Image,ImageFilterimport numpy as npdef imgprocess(name): path="./pic/{}.png".format(str(name)) img=Image.open(path).convert("L") th=np.array(img).mean() im_b = img.point(lambda i: i > th, mode=1) im_f= im_b.filter(ImageFilter.MedianFilter(size=3)) split_lines = [7, 25, 43, 61, 79] y_min = 5 y_max = 35 ims = [] c = 1 for x_min, x_max in zip(split_lines[:-1], split_lines[1:]): im_f.crop([x_min, y_min, x_max, y_max]).save(./pic2later/{}-{}.png.format(str(name),str(c))) c = c + 1for i in range(1,51): print("process {} pic".format(i)) imgprocess(i)###########下面是核心代碼#####################def Y(): # 獲取字元的值!這些驗證碼是我一個一個肉眼寫出來的! with open("./pic/reslut.txt") as f: Y=list(f.read().replace("
","")) return Ydef getX(): # 獲取X的值! path="./pic2later/{}-{}.png" X=[] for i in range(1,51): for c in range(1,5): img=Image.open(path.format(str(i),str(c))) ls = np.array(img).tolist() xx = [] for l in ls: for x in l: xx.append(x) X.append(xx) return Xdef train(): # 用knn模型進行訓練 knn = KNN() knn.fit(getX(), Y()) joblib.dump(knn,"./model.pkl") # 保存結果!train()

寫到這裡了,乾脆把預測代碼也寫一下吧!跟訓練代碼差不多!

# encoding=utf8from sklearn.neighbors import KNeighborsClassifier as KNNfrom sklearn.externals import joblibfrom PIL import Image,ImageFilterimport numpy as npMODEL_PATH="./model.pkl"def getX(file_name): X=[] img=Image.open(file_name).convert("L") th=np.array(img).mean() im_b = img.point(lambda i: i > th, mode=1) im_f = im_b.filter(ImageFilter.MedianFilter(size=3)) split_lines = [7, 25, 43, 61, 79] y_min = 5 y_max = 35 for x_min, x_max in zip(split_lines[:-1], split_lines[1:]): ls=np.array(im_f.crop([x_min, y_min, x_max, y_max])).tolist() xx=[] for l in ls: for x in l: xx.append(x) X.append(xx) return Xdef predict(file_path): # 預測 knn=joblib.load(MODEL_PATH) X=getX(file_path) Y=knn.predict(X) return "".join(Y)print(predict("./pic/1.png"))

總結

1.預測結果可能不準確,原因是因為只有50張驗證碼進行訓練

2.懶得對它進行評估了,你們隨意就好

3.驗證碼識別的一般套路: 灰度化、圖像處理、二值化、選演算法、訓練、評估調整參數、預測,當然,我在這裡二值化與處理的順序換了一下,靈活處理哈!

代碼:

在下載代碼之前先說幾句!自己先運行一下train()函數重新生成model.pkl,在進行預測!至於為什麼,32 位與 64 位是不兼容的!

最後!厚著臉皮!腆著臉!說要!要點贊!要回復喲!嚶嚶嚶! 有什麼問題可以,在評論區提問喲!

鏈接: pan.baidu.com/s/1Ym7oc1

密碼: xunq

推薦閱讀:

挖掘網站漏洞的時候你遇到過哪些奇葩或坑爹的事情?

一個日本代購的血淚史

微信賣小電影反被抄了家


推薦閱讀:

Kaggle比賽教你最快速度入門文本分類(經典方法篇)
機器學習中的數學基礎(線性代數)
Convex Formulation for Learning from Positive and Unlabeled Data
機器學習項目流程清單
機器學習基石筆記10:邏輯斯蒂(Logistic)回歸 上

TAG:機器學習 | 黑客Hacker | 驗證碼識別 |