信用卡識別

本片文章介紹的是利用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 )

先把 cntsboundingBoxes綁定為一個元素分別存入一個列表中,然后對兩個分別進行升序排列

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
 gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))  # 對他做歸一化操作
    # 歸一化后要把數據改成uint8的形式
    gradX = gradX.astype("uint8")
    print(np.array(gradX).shape) #打印出gradx的shape
    cv_show('gradx',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 = []

調整過程中發現在閉操作后 輪廓識別不是很好,自己又加上了一個膨脹操作,得到了較好的效果
又對參數進行調整得到較好效果 能比較好的識別

4000
1234
5678
9010

對截取的圖像做閾值處理

group = cv2.threshold(group,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('group',group)
4000
1234
5678
9010
 # 計算每一組的輪廓
        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區域

裁剪出來的4
0
1
9

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)

各參數依次是:

圖片,
添加的文字,
左上角坐標,
字體,
字體大小,
顏色,
字體粗細

最后的效果圖
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容