實戰1-銀行卡號識別
項目來源:opencv入門
項目目的:識別傳入的銀行卡照片中的卡號
難點:銀行卡上會有一些干擾項,如何排除這些干擾項,并且打印正確的號碼是一個問題
最終效果如上圖
實現這樣的功能需要以下幾個步驟:
- 首先必須有與銀行卡中卡號數字基本一樣的數字模板,將模板中的數字提取出來并存儲起來(0-9)
- 將需要檢測的銀行卡圖片中的數字提取出來
- 將銀行卡的數字與模板數字一一對比,最終找到一個匹配度最高的數字,并把數字標注上
整個思路很簡單,但是難點就在于如何將圖片處理得更加容易讓計算機識別數字,所以整個項目要圍繞著圖片得的處理來做
第一步-提取數字模板
這是事先準備好的數字模板
接下來要將圖片中的數字都找到,也就是找到各個數字在整個圖片上的像素點坐標(輪廓)
首先得到圖片的灰度圖,再進行二值化處理(這一切都是為了讓圖片中的數字更易于識別)
# 灰度圖
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值圖像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]#超過閾值部分取maxval ( 最大值 ),否則取0
然后會得到這樣的圖像
好了,現在圖片已經很清晰了,不需要再進行其它的處理了,直接將其提取
那怎么提取呢?
可以通過cv2.findContours()找到數字的輪廓
函數 cv2.?ndContours() 有三個參數,第一個是輸入圖像,第二個是 輪廓檢索模式,第三個是輪廓近似方法。返回值有三個,第一個是圖像,第二個 是輪廓,第三個是(輪廓的)層析結構。輪廓(第二個返回值)是一個 Python 列表,其中存儲這圖像中的所有輪廓。每一個輪廓都是一個 Numpy 數組,包 含對象邊界點(x,y)的坐標。
注意新版本中這個api的返回值有變化
返回兩個參數contours和 hierarchy,contours就是每個數字的輪廓數組,包含邊界點的坐標
其中cv2.RETR_EXTERNAL是獲取外輪廓
contours, hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
接下來可以將輪廓畫出來看看
會用到cv2.drawContours()函數
函數 cv2.drawContours() 可以被用來繪制輪廓。它可以根據你提供 的邊界點繪制任何形狀。它的第一個參數是原始圖像,第二個參數是輪廓,一 個 Python 列表。第三個參數是輪廓的索引(在繪制獨立輪廓是很有用,當設置 -1時繪制所有輪廓)。接下來的參數是輪廓的顏色和厚度等。
cv2.drawContours(img,contours,-1, (0, 0, 255), 3)
看下效果
好,現在輪廓都找到了,并且我們也有了輪廓的坐標,這個時候我們應該將每個數字的像素點位置都存起來(并不是將圖片分割!,整個圖片仍然沒有任何變化)
好,現在有一個要注意的點,那就是我們在上面得到的contours數組并不是按圖片中各個數字從左到右排列的,也就是說數組中第一個坐標可能是圖片中8的坐標,那這個時候我們就必須對數組進行排序,排序順序就是從左到右存
那排序怎么實現呢,其實就是根據x坐標從小到大排序就行了
排完序之后,contours中0下標存的就是數字0的模板,這里很好的利用了數組下標的優點
好的,排序完之后,我們就可以來存這個數字的模板了
思路是遍歷contours數組,得到每個模板的坐標以及寬高,利用x+w就能得到圖片的x軸范圍,y+h就能得到y軸的范圍,把他們存起來就得到一個數字的模板了
digits = {}
#遍歷每一個輪廓
for (i,c) in enumerate(contours):#計算外接矩形并resize合適的大小(x, y, w, h) = cv2.boundingRect(c)# cv2.rectangle(img,(x,y),(x + w, y + h),(0, 0, 255), 2)roi = ref[y:y + h, x:x + w]# 第二個參數是輸出圖像的寬高roi = cv2.resize(roi, (57, 88))# 每一個數字對應每一個模板digits[i] = roi
至此,我們項目的第一步就完成了
接下來就是將要檢測的圖像中的數字提取出來,其實整個提取思路都是一樣的,但是銀行卡的圖像比我們的模板往往更加復雜,所以我們要對圖片增加一些處理的步驟
跟著上面的來說,我們對復雜圖片的處理需要引入卷積核,這里我們定義兩個卷積核
# 初始化卷積核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
一個是9x3的矩陣,一個是5x5
下面對圖像進行處理,老規矩,取灰度圖
然后進行禮帽處理,目的是為了突出更明亮的區域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
接下來再用 Sobel核子對圖片進行卷積,目的的為了得到圖像梯度,也就是邊緣檢測
我們現在要做的是把可能為數字的區域都找出來
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
上圖看上去更加模糊了,但是數字和非數字區域的明亮度變了
好的,接下來可以通過閉操作(先膨脹,再腐蝕),將數字連起來(是為了最后找到數字區域,因為卡號是4個數字連在一起的,我們把4個數字的區域找出來)
變成這樣
再來一次閾值操作
thresh = cv2.threshold(gradX, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
變成這樣
矩形區域好像白色沒有填滿,再來一次閉操作
ok了,現在疑似數字的區域都很明顯了吧,那下一步就是將這個區域進行排除,找到真正為銀行卡號的區域,其他的區域就不要了
那怎么做呢?我們先把他們的輪廓都找出來,然后判斷這些輪廓的寬度,符合銀行卡號區域寬的的留下,不符合的去掉就可以了
# 計算輪廓
contours_, hierarchy_ = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = contours_
cur_img = image.copy()
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)
locs = []
# 遍歷輪廓
for (i, c) in enumerate(cnts):# 計算矩形(x, y, w, h) = cv2.boundingRect(c)ar = w / float(h)# 選擇合適的區域,根據實際任務來,這里的基本都是四個數字一組if ar > 2.5 and ar < 4.0:if (w > 40 and w < 55) and (h > 10 and h < 20):# 符合的留下來locs.append((x, y, w, h))
得到卡號輪廓后,同樣對其從左至右排序
好了,那接下來干嘛呢,我們剛剛得到的是四個數字組成的區域的輪廓,這個時候我們應該遍歷這些區域,得到里面的四個數字的輪廓
同樣也是個遍歷操作
for (i, (gX, gY, gW, gH)) in enumerate(locs):groupOutput = []# 根據坐標提取每一個組group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]cv_show('group', group)
會得到四個這樣的組
然后就獲取這個組的輪廓,就像第一步驟一樣,將數字提取出來就可以了
#計算每一組的輪廓digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 從左到右排序digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]
好的,接下來就是最重要的第三部操作了,將模板與上面得到的數字匹配,找到匹配度最高的那個模板數字就是我們要找的數字了
#計算每一組的每一個數值for c in digitCnts:# 找到當前數值的輪廓,resize成合適的的大小(x, y, w, h) = cv2.boundingRect(c)roi = group[y:y + h, x:x + w]roi = cv2.resize(roi, (57, 88))# cv_show('roi', roi)# 計算匹配得分scores = []for (digit, digitROI) in digits.items():# 模板匹配result = cv2.matchTemplate(roi, digitROI,cv2.TM_CCOEFF)# print('result',result)# 獲取匹配度最高的數值(_, score, _, _) = cv2.minMaxLoc(result)scores.append(score)print("scores",scores)# 得到最合適的數字groupOutput.append(str(np.argmax(scores)))print('groupOutput',groupOutput)
完成上述步驟之后,我們的groupOutput就存放了我們識別出來的銀行卡號了,我們只需要在圖片上將卡號繪制出來就可以了
# 畫出來cv2.rectangle(image, (gX - 5, gY - 5),(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)cv2.putText(image, "".join(groupOutput), (gX, gY - 15),cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)# 得到結果output.extend(groupOutput)
最終效果如下
好了,以上就是此小項目的實現過程
總結:這是我學cv的第一個小實戰項目,確實感覺蠻有意思的,學之前覺得這個東西很神奇,學習之后會發現其實一切都是按照邏輯一步步來的,沒有那么"高大上",繼續努力吧