knn識別簡單驗證碼

參考

https://www.biaodianfu.com/knn-captcha-recognition.html
內容大體同樣,只是根據本身的想法加入了一些改動php

KNN(k近鄰算法)

算法原理請看: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中的K值
  • weights: 樣本的權重數組
  • algorithm: 使用的算法,有{'auto','ball_tree','kd_tree','brute'}
  • leaf_size:當使用'ball_tree'和'kd_tree'時的屬性(不懂,先無論他)
  • p:距離選擇,p=2時爲歐式距離,默認爲2
  • metric:距離度量方式,和參數p有什麼關係和區別暫時沒懂
  • metric_params :距離額外參數,如{'w':weights, 'p':2}
  • n_jobs:使用的CPU數,默認-1即所有使用

在這些參數中,識別數字驗證碼只須要關注n_neighbors這個就好了,其餘都保持默認就行。算法

方法數組

  • fit(x, y): 使用樣本x和標籤y做訓練,其實knn的訓練只是保存了數據
  • get_params(deep=True): 獲取模型的全部參數,deep不知道有什麼用
  • kneighbors(test_x=None, n_neighbors=None, return_distance=True): 返回訓練樣本離樣本test_x最近的n_neighbors個樣本的值和距離, return_distance爲是否返回距離
  • kneighbors_graph(x=None, n_neighbors=None, mode='connectivity' ): 返回x中k個臨近點對應的權重或者距離,根據mode選擇'connectivity'或者'distance'
  • predict(test_x): 根據樣本test_x,返回預測y
  • predict_proba(test_x): 返回樣本test_x屬於每一個類別的機率,也就是說返回的是維度爲(樣本數, k)的二維數組,每行一維數組的全部元素和爲1,數組長度爲k。
  • score(test_x, y, sample_weight =None): 根據樣本test_x預測test_y, 而後對比實際的y返回的正確分數,sample_weight爲權重
  • set_params(**args): 從新設置模型參數

固然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的內容。
lUE1wd.jpg

相關文章
相關標籤/搜索