https://www.biaodianfu.com/knn-captcha-recognition.html
內容大體同樣,只是根據本身的想法加入了一些改動php
算法原理請看:https://www.biaodianfu.com/knn.htmlhtml
我來講一下sklearn中knn的屬性和方法
sklearn.neighbors.KNeighborsClassifier(n_neighbors = 5,weights ='uniform',algorithm ='auto',leaf_size = 30,
p = 2,metric ='minkowski',metric_params = None,n_jobs = None)python
在這些參數中,識別數字驗證碼只須要關注n_neighbors這個就好了,其餘都保持默認就行。算法
方法數組
固然knn分類器還有RadiusNeighborsClassifier,區別在於,KNeighborsClassifier找距離最近的K個樣本,而後投票來決定x的類別,而RadiusNeighborsClassifier則是根據x半徑爲r的範圍內的全部樣本投票來決定x的類別。瀏覽器
若是對圖片中的數組表示不清楚的能夠看另外一篇博客微信
https://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325
這個有個地方須要注意,你直接請求這個接口的話獲得的只是個HTML的源碼,可是你在瀏覽器上看的時候又是驗證碼,F12看的時候也是返回的驗證碼。可是我用抓包工具抓包發現它實際上發送了兩次請求,第一次請求更新cookie,第二次纔是真正的返回驗證碼,連接同樣只是cookie不同。咱們只須要保存第二個請求的cookie用requests請求便可。cookie
已經下載的:https://www.lanzous.com/i8enhahapp
im = Image.open(img) im_gray = im.convert('L') # 灰度圖 pix = np.array(im_gray) # 二值化 threshold = 180 #閾值 pix = (pix < threshold) * 255 # 去邊框 new_pix = pix[1:-1,1:-1]
最開始的圖片:
處理後的圖片
機器學習
作完基本操做以後你會發現,圖片會有一些多餘的點,這些點可能會影響分類因此須要去除。我使用最簡單的方法,只去除孤立點。判斷一個黑點九宮格內的黑點的個數,若是少於某個值則將這個黑點置爲白點(實際測試這個值只能爲2,大於2會刪除正常的點)。代碼以下:
for i in range(18): for j in range(46): k = 0 if new_pix[i, j] == 0: k = np.sum(new_pix[i-1:i+2, j-1:j+2] == 0) if k < 2: new_pix[i, j] = 255
去完噪點的圖片:
開始我想以投影法切割,而後發現其實這個的思想就是將圖片進行橫向壓縮,若是哪一列都是白點,則認爲這一列就是分割邊界。代碼以下:
L = [] # 查找分割邊界 for i in range(46): k = np.sum(new_pix[:,i]==0) if k == 0: L.append(i) # 分割圖片 for i in range(1, len(L)): k = L[i] - L[i-1] if k > 2: print(k) split_pix = new_pix[:,L[i-1]:L[i]+1] print(split_pix.shape) # 7是根據實際的值判斷的,大部分爲9,因此須要統一大小 if k == 7: tmp = np.zeros((18, 10)) tmp += 255 tmp[:,1:-1] = split_pix out = Image.fromarray(tmp).convert('L') out.save(f'1/{uuid.uuid4()}.jpg') if split_pix.shape != (18,10): continue out = Image.fromarray(split_pix).convert('L') out.save(f'1/{uuid.uuid4()}.jpg')
可是當我將這個方法應用於全部圖片時,會出現少部分連在一塊兒的字符。最後我直接選擇了指定區間來切割字符,數值爲實際測試獲得,代碼以下:
img1 = new_pix[:, 3:13] out = Image.fromarray(img1).convert('L') out.save('1.jpg') img2 = new_pix[:, 12:22] out = Image.fromarray(img2).convert('L') out.save('2.jpg') img3 = new_pix[:, 21:31] out = Image.fromarray(img3).convert('L') out.save('3.jpg') img4 = new_pix[:, 30:40] out = Image.fromarray(img4).convert('L') out.save('4.jpg')
這是最煩的一部分了,很浪費時間。我每一個字符標註了120張圖片,花了一個小時。因此這種快樂我怎麼能一我的獨享呢。
from sklearn import neighbors import os from PIL import Image import numpy as np import shutil x = [] y = [] for label in os.listdir('train'): for file in os.listdir(f'train/{label}'): im = Image.open(f'train/{label}/{file}') pix = np.array(im) pix = (pix > 180) * 1 pix = pix.ravel() x.append(list(pix)) y.append(int(label)) train_x = np.array(x) train_y = np.array(y) model = neighbors.KNeighborsClassifier(n_neighbors=10) model.fit(train_x, train_y) x = [] y = [] for label in os.listdir('test'): for file in os.listdir(f'test/{label}'): im = Image.open(f'test/{label}/{file}') pix = np.array(im) pix = (pix > 180) * 1 pix = pix.ravel() x.append(list(pix)) y.append(int(label)) predict_y = model.predict(np.array(x)) print(predict_y == np.array(y))
這裏我使用了全部的像素值做爲圖片的特徵,總共18x10=180個特徵值。根據開頭的那個博客所說的,咱們能夠取每行上黑色像素的個數,能夠獲得10個特徵,每列上黑色像素的個數,能夠獲得6個特徵。這樣就只有16個特徵。在計算時間上會獲得必定的改善。不過由於圖片較小,數量也很少,實際測試所花的時間差也就幾秒差別。而180個特徵訓練出來的基本100%正確率,16個特徵則會出現個別判斷出錯的狀況不過正確率也有98%以上了。固然在實際應用中確定選擇16個特徵,這點錯誤率是能夠接受的。如下是16個特徵的代碼:
from sklearn import neighbors import os from PIL import Image import numpy as np x = [] y = [] for label in os.listdir('train'): for file in os.listdir(f'train/{label}'): x_ = [] im = Image.open(f'train/{label}/{file}') pix = np.array(im) pix = (pix > 180) * 1 for i in range(18): x_.append(np.sum(pix[i] == 0)) for j in range(10): x_.append(np.sum(pix[:,j] == 0)) x.append(x_) y.append(int(label)) train_x = np.array(x) train_y = np.array(y) model = neighbors.KNeighborsClassifier(n_neighbors=10) model.fit(train_x, train_y) test_x = [] test_y = [] for label in os.listdir('test'): for file in os.listdir(f'test/{label}'): x_ = [] im = Image.open(f'test/{label}/{file}') pix = np.array(im) pix = (pix > 180) * 1 for i in range(18): x_.append(np.sum(pix[i] == 0)) for j in range(10): x_.append(np.sum(pix[:,j] == 0)) test_x.append(x_) test_y.append(int(label)) predict_y = model.predict(x) print(predict_y == test_y)
我一開始每一個字符標註了120個樣本,那麼若是減小樣本數,會不會影響正確率,減小到多少纔不會影響?
咱們看一下隨着樣本數的增大,score的變化(左邊數字表示每一個字符的樣本數,右邊表示正確率):
1 0.07086614173228346
2 0.4015748031496063
3 0.6850393700787402
4 0.8031496062992126
5 0.8582677165354331
6 1.0
7 1.0
8 1.0
9 1.0
什麼?也就是說只要每一個類別6個樣本就能夠保證100%的正確率,那我這一個小時不是白花了。。。
咱們在看一下KNN的k對正確率的影響:
1 1.0
2 1.0
3 1.0
4 1.0
5 1.0
6 1.0
7 1.0
8 1.0
9 1.0
額,好像k選什麼一點都不重要。這是由於樣本類別太少,特徵很明顯致使的。固然這種驗證碼的識別只是練習,而KNN也僅僅能用於簡單的驗證碼,真正複雜的驗證碼仍是須要CNN來識別。
這是已標註的數據:https://www.lanzous.com/i8epywd
最後,我正在學習一些機器學習的算法,對於一些我須要記錄的內容我都會分享到博客和微信公衆號(python成長路),歡迎關注。平時的話通常分享一些爬蟲或者Python的內容。