摘要:信用卡識別的案例用到了圖像處理的一些基本操做,對剛上手CV的人來講仍是比較友好的。
本文分享自華爲雲社區《Python openCV案例:信用卡數字識別》,原文做者:深藍的迴音 。git
實踐是檢驗真理的惟一標準。segmentfault
由於以爲一板一眼地學習OpenCV太過枯燥,因而在網上找了一個以項目爲導向的教程學習。話很少說,動手作起來。app
提供信用卡上的數字模板:函數
要求:識別出信用卡上的數字,並將其直接打印在原圖片上。雖然看起來很蠢,但既然能夠將數字打印在圖片上,說明已經成功識別數字,所以也能夠將其轉換爲數字文本保存。車牌號識別等項目的思路與此案例相似。學習
原圖字體
處理後的圖ui
大體分爲以下幾個步驟:
1.模板讀入
2.模板預處理,將模板數字分開,並排序
3.輸入圖像預處理,將圖像中的數字部分提取出來
4.將數字與模板數字進行匹配,匹配率最高的即爲對應數字。spa
import cv2 as cv import numpy as np import myutils def cv_show(name, img): # 自定義的展現函數 cv.imshow(name, img) cv.waitKey(0) # 讀入模板圖 n = 'text' img = cv.imread("images/ocr_a_reference.png") # cv_show(n, template) # 自定義的展現函數,方便顯示圖片
模板的預處理順序:灰度圖,二值化,再進行輪廓檢測。須要注意的是openCV檢測輪廓時是檢測白色邊框,所以要將模板圖的數字二值化變爲白色。code
# 模板轉換爲灰度圖 ref = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # cv_show(n, ref) # 轉換爲二值圖,把數字部分變爲白色 ref = cv.threshold(ref, 10, 255, cv.THRESH_BINARY_INV)[1] # 騷寫法,函數多個返回值爲元組,這裏取第二個返回值 cv_show(n, ref) # 對模板進行輪廓檢測,獲得輪廓信息 refCnts, hierarchy = cv.findContours(ref.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE) cv.drawContours(img, refCnts, -1, (0, 0, 255), 2) # 第一個參數爲目標圖像 # cv_show(n, img)
紅色部分即爲檢測出的輪廓。blog
接下來進行輪廓排序,由於檢測出的輪廓是無序的,所以要按照輪廓的左上角點的x座標來排序。輪廓排序後按順序放入字典,則字典中的鍵值對是正確匹配的,如‘0’對應輪廓0 ,‘1’對應輪廓1。
# 輪廓排序 refCnts = myutils.sort_contours(refCnts)[0] digits = {} # 單個輪廓提取到字典中 for (i, c) in enumerate(refCnts): (x, y, w, h) = cv.boundingRect(c) roi = ref[y:y + h, x:x + w] # 在模板中複製出輪廓 roi = cv.resize(roi, (57, 88)) # 改爲相同大小的輪廓 digits[i] = roi # 此時字典鍵對應的輪廓即爲對應數字。如鍵‘1’對應輪廓‘1’
至此,模板圖處理完畢。
在此步驟中須要將信用卡上的每一個數字提取出來,並與上一步獲得的模板一一匹配。首先初始化卷積核,方便以後tophat操做以及閉運算操做使用。
# 初始化卷積核 rectKernel = cv.getStructuringElement(cv.MORPH_RECT, (9, 3)) sqKernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
接下來讀入圖片,調整圖片大小,轉換爲灰度圖。
# 待分析圖片讀入,預處理 card_image = cv.imread("images/credit_card_01.png") # cv_show('a', card_image) card_image = myutils.resize(card_image, width=300) # 更改圖片大小 gray = cv.cvtColor(card_image, cv.COLOR_BGR2GRAY) # cv_show('gray', gray)
而後進行tophat操做,tophat能夠突出圖片中明亮的區域,過濾掉較暗的部分:
tophat = cv.morphologyEx(gray, cv.MORPH_TOPHAT, rectKernel) # cv_show('tophat', tophat)
再經過sobel算子檢測邊緣,進行一次閉操做,二值化,再進行一次閉操做,填補空洞。
# x方向的Sobel算子 gradX = cv.Sobel(tophat, cv.CV_32F, 1, 0, ksize=3) gradX = np.absolute(gradX) # absolute: 計算絕對值 min_Val, max_val = np.min(gradX), np.max(gradX) gradX = (255 * (gradX - min_Val) / (max_val - min_Val)) gradX = gradX.astype("uint8") # 經過閉操做(先膨脹,再腐蝕)將數字連在一塊兒. 將本是4個數字的4個框膨脹成1個框,就腐蝕不掉了 gradX = cv.morphologyEx(gradX, cv.MORPH_CLOSE, rectKernel) # cv_show('close1', gradX) # 二值化 thresh = cv.threshold(gradX, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)[1] # 閉操做,填補空洞 thresh = cv.morphologyEx(thresh, cv.MORPH_CLOSE, sqKernel) # cv_show('close2', thresh)
以後就能夠查找輪廓了。
threshCnts = cv.findContours(thresh.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)[0] card_copy = card_image.copy() cv.drawContours(card_copy, threshCnts, -1, (0, 0, 255), 2) cv_show('Input_Contours', card_copy)
將模板數字和待識別的圖片都處理好後,就能夠進行匹配了。
locs = [] # 存符合條件的輪廓 for i, c in enumerate(threshCnts): # 計算矩形 x, y, w, h = cv.boundingRect(c) ar = w / float(h) # 選擇合適的區域,根據實際任務來,這裏的基本都是四個數字一組 if 2.5 < ar < 4.0: if (40 < w < 55) and (10 < h < 20): # 符合的留下來 locs.append((x, y, w, h)) # 將符合的輪廓從左到右排序 locs = sorted(locs, key=lambda x: x[0])
接下來,遍歷每個大輪廓,每一個大輪廓中有四個數字,對應四個小輪廓。將小輪廓與模板匹配。
output = [] # 存正確的數字 for (i, (gx, gy, gw, gh)) in enumerate(locs): # 遍歷每一組大輪廓(包含4個數字) groupOutput = [] # 根據座標提取每個組(4個值) group = gray[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5] # 往外擴一點 # cv_show('group_' + str(i), group) # 預處理 group = cv.threshold(group, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)[1] # 二值化的group # cv_show('group_'+str(i),group) # 計算每一組的輪廓 這樣就分紅4個小輪廓了 digitCnts = cv.findContours(group.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)[0] # 排序 digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0] # 計算並匹配每一組中的每個數值 for c in digitCnts: # c表示每一個小輪廓的終點座標 z = 0 # 找到當前數值的輪廓,resize成合適的的大小 (x, y, w, h) = cv.boundingRect(c) # 外接矩形 roi = group[y:y + h, x:x + w] # 在原圖中取出小輪廓覆蓋區域,即數字 roi = cv.resize(roi, (57, 88)) # cv_show("roi_"+str(z),roi) # 計算匹配得分: 0得分多少,1得分多少... scores = [] # 單次循環中,scores存的是一個數值 匹配 10個模板數值的最大得分 # 在模板中計算每個得分 # digits的digit正好是數值0,1,...,9;digitROI是每一個數值的特徵表示 for (digit, digitROI) in digits.items(): # 進行模板匹配, res是結果矩陣 res = cv.matchTemplate(roi, digitROI, cv.TM_CCOEFF) # 此時roi是X digitROI是0 依次是1,2.. 匹配10次,看模板最高得分多少 Max_score = cv.minMaxLoc(res)[1] # 返回4個,取第二個最大值Maxscore scores.append(Max_score) # 10個最大值 # print("scores:",scores) # 獲得最合適的數字 groupOutput.append(str(np.argmax(scores))) # 返回的是輸入列表中最大值的位置 z = z + 1 # 畫出來 cv.rectangle(card_image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1) # 左上角,右下角 # putText參數:圖片,添加的文字,左上角座標,字體,字體大小,顏色,字體粗細 cv.putText(card_image, "".join(groupOutput), (gx, gy - 15), cv.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
最後將其打印出來,任務就完成了。
cv.imshow("Output_image_"+str(i), card_image) cv.waitKey(0)
信用卡識別的案例用到了圖像處理的一些基本操做,對剛上手CV的人來講仍是比較友好的。