信用卡识别

本片文章介绍的是利用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阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容