KNN實現(xiàn)圖像驗證碼識別

1. KNN原理

K最近鄰(kNN窗骑,k-NearestNeighbor)分類算法是一個理論上比較成熟的方法廉侧,也是最簡單的機器學(xué)習算法之一遭垛。所謂K最近鄰,就是k個最近的鄰居的意思姓惑,說的是每個樣本都可以用它最接近的k個鄰居來代表。

該方法的具體思路是:如果一個樣本在特征空間中的k個最相似(即特征空間中最鄰近)的樣本中的大多數(shù)屬于某一個類別按脚,則該樣本也屬于這個類別于毙。KNN算法中,所選擇的鄰居都是已經(jīng)正確分類的對象辅搬。該方法在定類決策上只依據(jù)最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別唯沮。 KNN方法雖然從原理上也依賴于極限定理,但在類別決策時堪遂,只與極少量的相鄰樣本有關(guān)介蛉。由于KNN方法主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來確定所屬類別的溶褪,因此對于類域的交叉或重疊較多的待分樣本集來說币旧,KNN方法較其他方法更為適合。

簡單點說就是猿妈,有一個向量化的樣本庫吹菱,每個樣本都有一個標簽標識類別,輸入一個未知分類的樣本彭则,向量化之后跟樣本庫一一比對計算方差鳍刷,取方差最小的topN的樣本庫的樣本的分類標簽,用這N個標簽的類別代表輸入樣本的類別贰剥。

下面以一個驗證碼識別的案例詳細說明一下KNN的使用倾剿。

2.案例

Fig.1 驗證碼圖片示例

我們選取Fig.1中所示的樣例圖片進行識別筷频。該驗證碼噪聲比較多蚌成,識別有很大的難度,吉德林法則凛捏,把難題清清楚楚地寫出來担忧,便已經(jīng)解決了一半。我們現(xiàn)在列出需要解決的問題:

  • 1)橫穿圖像的條形線條大部分貫穿字符坯癣;
  • 2)圖像中心位置有高頻噪聲(近似椒鹽噪聲)瓶盛;
  • 3)不同圖像字符起始位置不一致,需要動態(tài)定位示罗;
  • 4)字符之前黏連惩猫、傾斜,不好分割蚜点;
  • 5)相同字符字體不一致轧房,類似于手寫字體

要想利用KNN對Fig.1中的字符進行識別,首先需要克服以上的困難绍绘,對字符進行正確分割處理奶镶。這需要一些圖像處理的知識迟赃,圖像的計算其實就是矩陣的計算,有一定數(shù)學(xué)功底的同學(xué)稍微搜索一下原理就能很容易的理解下面將要介紹的內(nèi)容厂镇。

本文所有的實驗圖像和代碼均已提交到github中纤壁,項目所在位置:https://github.com/CarsonCao/knnCodeRecognition

2.1.準備工作

python版本:
python 3.5+
安裝pip3:
sudo apt-get install -y python3-pip
安裝opencv:
pip3 install opencv-python
安裝numpy:
pip3 install numpy
安裝matplotlib:
pip3 install matplotlib
安裝jupyter:
sudo pip3 install jupyter

2.2.圖像形態(tài)學(xué)處理

為了能更清晰的識別字符,我們首先對圖像進行去噪并做二值化處理捺信。


Fig.2 圖像形態(tài)學(xué)處理

通過簡單裁切處理可以去掉一部分圖像開始和結(jié)尾的噪聲(如Fig.2中的圖1)酌媒。

分別用OTSU和高斯自適應(yīng)對圖像進行二值化后,我們發(fā)現(xiàn)二值化之后的圖像有很多麻點(Fig.2中的圖3和圖5)迄靠,這是因為原始圖像中有很多高頻的噪聲馍佑,需要首先去噪處理,我們選用高斯模糊(Fig.2中 圖2)梨水。高斯模糊之后再做二值化拭荤,可以看到圖像中的麻點消失了(Fig.2 中圖4和圖6)。

通過對比Fig.2中圖4和圖6我們發(fā)現(xiàn)疫诽,OTSU的二值化結(jié)果字符有很多細節(jié)丟失了舅世,而高斯自適應(yīng)二值化很好的保留了字符的細節(jié),所以我們選擇高斯自適應(yīng)二值化算法奇徒。

二值化之后雏亚,水平方向橫穿整幅圖像的線條噪聲也保留了下來,所以我們需要把它去除掉摩钙,因為這條線特征比較明顯罢低,大部分是水平的辛慰,且穿過字符的部分比較細探熔,所以我們用[2*1]的算子對二值化之后的圖像做腐蝕運算燕垃,去掉水平噪聲(Fig.2中圖7)裹纳。水平噪聲雖然去掉了岔擂,但是有些字符的橫線也被腐蝕了棚潦,下面再用[2*2]的算子對圖像進行膨脹運算棺棵,得到比較飽滿的字符圖像(Fig.2中圖8)毫别。為啥選擇[2*1]和[2*2]的算子身弊,這些都是通過大量實驗得到的辟汰,有興趣的同學(xué)可以自己試驗一下,沒準能找到去噪更好的算子阱佛。

總結(jié)一下上面說的帖汞,為了得到清晰的去噪的字符二值化圖像,我們先后對圖像進行的操作為:簡單裁切 -> 高斯模糊 -> 高斯自適應(yīng)二值化 -> 垂直方向腐蝕 -> 膨脹 凑术。Fig.2中圖8為二值化最終的結(jié)果翩蘸。

形態(tài)學(xué)處理的代碼不多,直接上代碼供大家參考:
https://github.com/CarsonCao/knnCodeRecognition/blob/master/src/study/03_threshold.ipynb

import cv2      
import numpy as np 
import matplotlib.pyplot as plt  
import os

def imgThreshold(img, num):

    height, width = img.shape

    img_seg = img[8:height,40:width-40]

    ##圖像二值化##
    # Gaussian模糊去噪
    blur = cv2.GaussianBlur(img_seg,(5,5),0)

    # Otsu's threholding without gaussian blur
    ret1,th1 = cv2.threshold(img_seg,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

    # Otsu's threholding with gaussian blur
    ret2,th2 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

    # gaussian adaptiveThreshold without gaussian blur
    th3 = cv2.adaptiveThreshold(img_seg,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
                cv2.THRESH_BINARY_INV,11,3)

    # gaussian adaptiveThreshold with gaussian blur
    th4 = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
                cv2.THRESH_BINARY_INV,11,3)

    # 垂直方向腐蝕
    kernel = np.ones((2,1),np.uint8)
    erosion = cv2.erode(th4,kernel,iterations = 3)
    
   #膨脹
    kernel2 = np.ones((2,2),np.uint8)
    dilation = cv2.dilate(erosion,kernel2,iterations = 1)
    
    # plot images
    #     plt.subplot(5,1,1),plt.imshow(img_seg,'gray')
    #     plt.title(''), plt.xticks([]), plt.yticks([])
    #     plt.show()

    # saving result
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_1_chop.jpg", img_seg, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_2_blur.jpg", blur, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_3_otsu.jpg", th1, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_4_otsu_blur.jpg", th2, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_5_gauss.jpg", th3, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_6_gauss_blur.jpg", th4, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_7_erosion.jpg", erosion, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) 
    cv2.imwrite("../../images/study/threshold/" + str(num) + "_8_dilation.jpg", dilation, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) 


if __name__ == '__main__': 
    for i in range(0,10):
        file =  "../../images/study/input/"+str(i)+".jpg"
        if not os.path.exists(file):
            continue
        img = cv2.imread(file, 0)  #直接讀取成灰度圖片
        imgThreshold(img, i)

2.3.ROI定位

由于字符在圖像中的開始和結(jié)束位置是隨機的麦萤,我們不能通過固定的裁切獲取到ROI(region of interest鹿鳖,感興趣區(qū)域)圖像扁眯。所以需要對ROI進行定位。

現(xiàn)在我們已經(jīng)獲取了去噪之后的二值化圖像翅帜,現(xiàn)在需要精確定位字符所在的位置姻檀。我們采用像素投影的方法實現(xiàn),步驟:
1)統(tǒng)計非0像素點個數(shù)在x軸方向投影涝滴,生成直方圖绣版;
2)利用滑動窗口對直方圖平滑去噪;
3)設(shè)置兩個指針分別從直方圖左和右進行計算歼疮,滿足閾值的位置設(shè)為ROI起始點杂抽;
4)對圖像進行裁切得到ROI圖像

Fig.3 ROI定位

這部分的代碼也比較簡單,有興趣的同學(xué)可以嘗試一下:
https://github.com/CarsonCao/knnCodeRecognition/blob/master/src/study/04_getROI.ipynb

2.4. 字符分割

在進行字符識別之前韩脏,還有一個很重要的工作缩麸,就是要對字符進行分割,另外我們只是對水平方向進行了ROI定位赡矢,垂直方向還需要在字符分割出來之后再針對單個字符進行精確定位杭朱。

Fig.4 字符黏連傾斜

字符有很多黏連和傾斜的情況,所以想要對字符分割不是很容易吹散。我們采用能量累加的方式進行計算弧械。我們可以將二值圖像的每個像素點看做是一個能量點,白色區(qū)域是能量高的區(qū)域空民,黑色為能量低的區(qū)域刃唐,我們需要在兩個字符之間尋找一條能量最小的像素細縫,能夠?qū)蓚€字符分割開來界轩。


Fig.5 能量累加矩陣

如Fig.5 所示画饥,用V(i,j)表示每個像素點的能量,初始化為對應(yīng)二值圖像的像素值耸棒。用E(i,j)表示累加能量荒澡。


當i=1的時候报辱,累加能量E(i,j) = V(i,j),從第二行開始与殃,取前一行3鄰域(左右邊緣取前一行2鄰域)的最小累加能量值與當前坐標能量值相加,作為當前坐標的累加能量值碍现。在計算累加能量矩陣的同時幅疼,還需要記錄矩陣每個坐標累加自前一行的位置,可以單獨用一個矩陣表示昼接,比如可以用-1表示來自左上鄰域爽篷,用0表示正上鄰域,用1表示右上鄰域慢睡。這樣逐工,當計算完累加能量矩陣之后铡溪,能從最后一行任何一個點往上追溯得到一條累加能量的鏈。累加能量矩陣的最后一行的最小值泪喊,肯定是貫穿能量最小的棕硫。

利用上述原理,我們可以實現(xiàn)針對輕度黏連字符或者傾斜字符的分割袒啼。Fig.6和Fig.7對尋找最小能量細縫的過程進行了舉例說明哈扮。

Fig.6 一幅含有5和2數(shù)字的二值圖像

Fig.7 尋找最小累加能量細縫的過程

在實際分割過程中,需要規(guī)定累加能量矩陣的左右邊界蚓再,單個圖像需要進行四次分割滑肉,也就是需要計算四個能量累加矩陣。將ROI圖像平均分成5等分摘仅,取每份的中心線靶庙,兩個中心線作為能量累加矩陣的左右邊界。如Fig.8所示娃属。
Fig.8字符分割

得到分割的字符之后惶洲,有些字符頭部還會有噪聲,需要在垂直方向?qū)Ψ?字符的個數(shù)進行投影膳犹,與水平方向相同的方法恬吕,通過平滑去噪,得到字符的在垂直方向的正確位置须床。然后將字符圖像標準化到32*32大小铐料。
Fig.9字符標準化處理

該部分的代碼:
https://github.com/CarsonCao/knnCodeRecognition/blob/master/src/study/05_segment.ipynb

2.5.字符標注

將分割好的字符進行分類整理,放入以字符命名的對應(yīng)文件夾中豺旬。這樣我們就建好了一個有標簽的樣本庫(也有叫訓(xùn)練庫)钠惩。


Fig.10
Fig.11

2.3.KNN分類

將樣本庫中的圖像向量化,輸入未知圖像族阅,依次做如下操作:
簡單裁切 -> 高斯模糊 -> 高斯自適應(yīng)二值化 -> 垂直方向腐蝕 -> 膨脹 -> ROI定位 -> 能量累加矩陣分割字符 -> 字符標準化 -> 字符向量化 -> 與樣本庫一一比對得到最相近topN的標簽 -> 輸出各個字符的分類結(jié)果

Fig.12圖像向量化

收集了2300個分割之后的字符圖像篓跛,分別按照3比1設(shè)置樣本庫和測試庫,識別成功率在98%以上坦刀。輸入原始圖像進行識別愧沟,成功率在60%~70%之間。因為原始圖像還需要對字符進行去噪分割鲤遥,這部分準確度沒有字符識別高沐寺。
最終的驗證碼識別代碼:
https://github.com/CarsonCao/knnCodeRecognition/tree/master/src/verificationCode_recognition

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盖奈,隨后出現(xiàn)的幾起案子混坞,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件究孕,死亡現(xiàn)場離奇詭異啥酱,居然都是意外死亡,警方通過查閱死者的電腦和手機厨诸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門懈涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泳猬,你說我怎么就攤上這事批钠。” “怎么了得封?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵埋心,是天一觀的道長。 經(jīng)常有香客問我忙上,道長拷呆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任疫粥,我火速辦了婚禮茬斧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梗逮。我一直安慰自己项秉,他們只是感情好,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布慷彤。 她就那樣靜靜地躺著娄蔼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪底哗。 梳的紋絲不亂的頭發(fā)上岁诉,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機與錄音跋选,去河邊找鬼涕癣。 笑死,一個胖子當著我的面吹牛前标,可吹牛的內(nèi)容都是我干的坠韩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼候生,長吁一口氣:“原來是場噩夢啊……” “哼同眯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唯鸭,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硅确,沒想到半個月后目溉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體明肮,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年缭付,在試婚紗的時候發(fā)現(xiàn)自己被綠了柿估。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡陷猫,死狀恐怖秫舌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绣檬,我是刑警寧澤足陨,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站娇未,受9級特大地震影響墨缘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜零抬,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一镊讼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧平夜,春花似錦蝶棋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锰扶,卻和暖如春献酗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坷牛。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工罕偎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人京闰。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓颜及,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蹂楣。 傳聞我的和親對象是個殘疾皇子俏站,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容