本片文章介紹的是利用OpenCV對信用卡的卡號進行一個簡單的識別
核心用的是match的方法
先把圖片讀進來,然后轉換為灰度圖后做二值化處理
img = cv2.imread('/Users/qiaoye/Desktop/信用卡識別/images/ocr_a_reference.png')
#cv_show('moban',img)
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('gray',ref)
ref = cv2.threshold(ref,127,255,cv2.THRESH_BINARY_INV)[1]
cv_show('2',ref)
cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]])
img - 目標圖像
contours - 所有輸入的輪廓
contourIdx - 指定輪廓列表的索引 ID(將被繪制),若為負數,則所有的輪廓將會被繪制。
color - 繪制輪廓的顏色。
thickness - 繪制輪廓線條的寬度,若為負值或CV.FILLED則將填充輪廓內部區域
lineType - Line connectivity,(有的翻譯線型,有的翻譯線的連通性)
hierarchy - 層次結構信息,與函數findcontours()的hierarchy有關
maxLevel - 繪制輪廓的最高級別。若為0,則繪制指定輪廓;若為1,則繪制該輪廓和所有嵌套輪廓(nested contours);若為2,則繪制該輪廓、嵌套輪廓(nested contours)/子輪廓和嵌套-嵌套輪廓(all the nested-to-nested contours)/孫輪廓,等等。該參數只有在層級結構時才用到。
offset - 按照偏移量移動所有的輪廓(點坐標)。
#計算輪廓,cv2.findContours 函數接受的參數為二值圖,即黑白,不是黑就是白
cnts,hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, cnts, -1, (0, 0, 255), 3)
cv_show('img', img)
cv2.boundingRect(c)
(1) 第一個參數,InputArray array,一般為findContours函數查找的輪廓,包含輪廓的點集或者Mat;
(2) 返回值,Rect,返回值為最小外接矩形的Rect,即左上點與矩形的寬度和高度;
OpenCV提供了一個函數getStructuringElement,可以獲取常用的結構元素的形狀:矩形(包括線形)、橢圓(包括圓形)及十字形。
矩形:MORPH_RECT;
交叉形:MORPH_CORSS;
橢圓形:MORPH_ELLIPSE;
boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一個最小的矩形,把找到的形狀包起來x,y,h,w
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][0], reverse=False))
digits = {}
for (i,c) in enumerate(cnts):
(x,y,h,w) = cv2.boundingRect(c)
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))
對每個矩形框,找到他最小的外接矩形框 boundingBoxes
然后對這些矩形框進行排序,讓他們分別對應自己的位置,也就是0對應0的框
隨意要對他們進行排序,按照橫坐標升序的方式進行排序
sorted(zip(cnts,boundingBoxes),key = lambda b:b[1][0],reverse=False )
先把 cnts
和 boundingBoxes
綁定為一個元素分別存入一個列表中,然后對兩個分別進行升序排列
zip(*)
分別對數組進行解包,并返回cnts和boundingBoxes
對排序過的進行遍歷
對每個包圍,找到最小的框,并得到該坐標,適當調整后,存入roi中
第二步是設置卷積核大小和形狀
image = cv2.imread('/Users/qiaoye/Desktop/信用卡識別/images/credit_card_01.png')
#cv_show('image',image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('image',gray)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)# 禮帽操作 突出明亮的區域
cv_show('tophat', tophat)
禮帽操作:原圖-開操作 得到的是噪聲圖像
可以返現禮帽操作后,背景板上很多的噪聲都剔除掉了
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize = -1)
#ksize=-1相當于用3*3的
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
sobel 算子
Sobel算子依然是一種過濾器,只是其是帶有方向的。在OpenCV-Python中,使用Sobel的算子的函數原型如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
前四個是必須的參數:
第一個是需要處理的圖像
第二個參數是圖像的深度,-1表示采用的是與原圖像相同的深度。目標圖像的深度必須大于等于原圖像的深度;
dx和dy表示的是求導的階數,0表示這個方向上沒有求導,一般為0、1、2。
dx = 1 ,dy=0 表示對x方向進行求導
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize = 3) #核函數為3*3 現在算出的水平的情況
cv2.imshow(sobelx)
x方向是右邊減去左邊,會存在剪出的結果為負數的情況,為負數的時候圖像進行顯示的時候默認為0 所以在顯示的時候只能顯示出一邊的情況
0,1 在y方向是同理
gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) # 對他做歸一化操作
# 歸一化后要把數據改成uint8的形式
gradX = gradX.astype("uint8")
print(np.array(gradX).shape) #打印出gradx的shape
cv_show('gradx',gradX)
對比發現經過歸一化后的操作,效果比較明顯
對現在的圖像進行閉操作處理,也就是先進行膨脹再進行腐蝕操作,目的是把相近的數字連在一起
# 通過閉操作(先膨脹,再腐蝕)將數字連在一起
gradx = cv2.morphologyEx(gradX,cv2.MORPH_CLOSE,sqKernel)
cv_show('gradx',gradx)
thres = cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] #這個是希望電腦自己去找到自適應的閾值,0并不大是閾值
cv_show('thres',thres)
通過閉操作后的閾值操作,找出合適的閾值,并對圖像進行處理
thres = cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
THRESH_BINARY | THRESH_OTSH
是進行自適應的閾值操作
通過對比發現,進行過閾值操作后的圖像,效果較為明顯
#再來一個閉操作
thres = cv2.morphologyEx(thres,cv2.MORPH_CLOSE,sqKernel)
cv_show('thres2',thres)
接著閉操作再來一次,為了讓相近的數字盡可能的靠在一起
從圖中發現我們要找的區域基本上連在了一起
接下來我們開始計算輪廓
threshCnts, hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
得到后在原圖中進行展示
cnts = threscnts
cur_img = image
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
cv_show('img', cur_img)
可以發現吧原圖中的字符基本都標注出來了,接下來我們要識別哪些是需要的,哪些是不需要的
locs = []
# 遍歷輪廓
for (i,c) in enumerate(cnts):
x,y,h,w = 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))
locs = sorted(locs,key=lambda x:x[0], reverse = False)
output = []
通過比例選擇出合適的留下來
現在已經選出合適的輪廓了。需要把各個輪廓提出來,最后進行分別的識別操作
通過遍歷輪廓中的每一個數字,尋找合適的參數
#遍歷輪廓中的每個數字
for(i,(gx,gy,gw,gh)) in enumerate(locs):
groupOut = []
# 根據坐標提取每一個組
group = gray[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5]
cv_show('group',group)
通過遍歷一遍發現選出的輪廓不符合要求,隨機對對參數進行調整
threscnts,hierarchy = cv2.findContours(thres.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = threscnts
cur_img = image
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
#cv_show('img', cur_img)
locs = []
# 遍歷輪廓
for (i, c) in enumerate(cnts):
#for (i, c) in enumerate(cnts):
(x,y,w,h) = cv2.boundingRect(c)
#(x,y,h,w) = cv2.boundingRect(c)
#計算這個框出來的矩形的比例,通過比例進行選擇
ar = w / float(h)
# 選擇合適的區域,根據實際任務來,這里的基本都是四個數字一組
if ar > 2.5 and ar < 3.5:
if (w > 50 and w < 60) and (h > 10 and h < 30):
# 符合的留下來
locs.append((x, y, w, h))
locs = sorted(locs,key=lambda x:x[0], reverse = False)
output = []
調整過程中發現在閉操作后 輪廓識別不是很好,自己又加上了一個膨脹操作,得到了較好的效果
又對參數進行調整得到較好效果 能比較好的識別
對截取的圖像做閾值處理
group = cv2.threshold(group,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
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:
(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)
現在需要對每個輪廓中識別出每個數字,首先先框出每個輪廓,然后對框出來的輪廓按從左到右進行排序
然后對排序出來的x,y坐標進行處理,得出roi區域
cv2.matchTemplate(img1,img2,方法) 模板匹配
平方差匹配CV_TM_SQDIFF:用兩者的平方差來匹配,最好的匹配值為0
歸一化平方差匹配CV_TM_SQDIFF_NORMED
相關匹配CV_TM_CCORR:用兩者的乘積匹配,數值越大表明匹配程度越好
歸一化相關匹配CV_TM_CCORR_NORMED
相關系數匹配CV_TM_CCOEFF:用兩者的相關系數匹配,1表示完美的匹配,-1表示最差的匹配
歸一化相關系數匹配CV_TM_CCOEFF_NORMED
item()
Python 字典(Dictionary) items() 函數以列表返回可遍歷的(鍵, 值) 元組數組。
dict.items()
匹配函數返回的是一副灰度圖,最白的地方表示最大的匹配。使用cv2.minMaxLoc()
函數可以得到最大匹配值的坐標,以這個點為左上角角點,模板的寬和高畫矩形就是匹配的位置了:
# 相關系數匹配方法:cv2.TM_CCOEFF
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
left_top = max_loc # 左上角
right_bottom = (left_top[0] + w, left_top[1] + h) # 右下角
cv2.rectangle(img, left_top, right_bottom, 255, 2) # 畫出矩形位置
所以為了找到最大的點位
#計算匹配的得分
scores = []
for (digits,digitROI) in digits.items():
result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
score 為最大得分
# 得到最合適的數字
groupOutput.append(str(np.argmax(scores)))
返回得分最大值的下標,就是最佳匹配數字
找到最佳數字后要畫出來
# 畫出來
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)
cv2.putText(img, str(i), (123,456)), font, 2, (0,255,0), 3)
各參數依次是:
圖片,
添加的文字,
左上角坐標,
字體,
字體大小,
顏色,
字體粗細