本文主要介绍通过OpenCV- python实现简单的银行卡卡号识别的思路和具体实现过程。

目录

知识准备

项目概述

实现过程

代码讲解

1.自定义函数

2.模版读入与预处理

3.银行卡读入与形态学操作

4. 卡号筛选与ROI切割

5.模版匹配,得出结果

结语


知识准备

该过程需要用到以下知识:

        1.OpenCV图像基础操作,如读取,灰度转换,改变大小等

        2.阈值操作,如二值化

        3.轮廓检测以及boundingBox构建

        4.卷积核构建

        5.Sobel算子求梯度,梯度融合

        6.形态学操作,如tophat,close操作

        7.machTemplate模版匹配

        8.用pyplot查看图片,便于debug

项目概述

        本项目旨在综合运用OpenCV的各种方法实现对银行卡卡号的自动识别。

 主要原理基于模版匹配,故需要提供与银行卡数字字体相同的数字模版作为参照。

opencv数量识别 opencv模式识别_人工智能

实现过程

        1.读入模版图片,识别边框,取出各个数字作为参考项。

        2.读入银行卡图片,进行形态学操作,识别边框,筛选数字区域。

        3.将银行卡的卡号数字割出来,与模版比较,选出相似度最高的答案。

代码讲解

1.自定义函数

import cv2
import matplotlib.pyplot as plt


def read(img, thresh=127, inv=False):
    origin = cv2.imread(img)
    gray = cv2.imread(img, 0)
    binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY_INV if inv else cv2.THRESH_BINARY)[1]
    return origin, gray, binary


def unify_size(img_list):
    outputlist = []
    xsize, ysize = img_list[0].shape
    for img in img_list:
        outputlist.append(cv2.resize(img, (ysize, xsize)))
    return outputlist

        在本项目中,笔者自定义了两个函数。read()实现了传入一个图片文件名,返回其原图,灰度图以及二值化处理后的结果。unify_size()实现了传入一个由图片构成的列表,对列表中所有元素按照列表第一个图片的大小进行大小统一。

2.模版读入与预处理

template_bgr, template_gray, template_bin = read('template.png', inv=True)
contours, hier = cv2.findContours(template_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
rectangles = []
for cnt in contours:
    x, y, w, h = cv2.boundingRect(cnt)
    rectangles.append([x, y, w, h])
rectangles.sort(key=lambda rect: rect[0])
num_template = []
for [x, y, w, h] in rectangles:
    num_template.append(template_bin[y:y + h, x:x + w])
num_template = unify_size(num_template)

        在本段代码中,首先调用自定义的read()方法返回了模版的bgr格式,灰度和二值化(反向)后的结果。然后调用findContours方法识别了模版图片中的所有外边界,储存在contours列表中。在循环中遍历所有外边框,将每个边框的x,y,w,h存储在rectangles列表中,并对其按照从左到右排序,使得下标和数字一一对应。最后截取每个ROI,储存在num_template列表中,并进行大小的统一。

3.银行卡读入与形态学操作

# 读入
idcard_bgr, idcard_gray, idcard_bin = read('1.png', 127)
# 构建三个将来会用到的卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5))
rectKernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (18, 7))
basicKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# 改变大小
idcard_bgr, idcard_gray, idcard_bin = cv2.resize(idcard_bgr, (583, 368)), cv2.resize(idcard_gray,
                                                                                     (583, 368)), cv2.resize(idcard_bin,
                                                                                                             (583, 368))
# 顶帽操作
idcard_gray_tophat = cv2.morphologyEx(idcard_gray, cv2.MORPH_TOPHAT, rectKernel)
plt.imshow(idcard_gray_tophat)
plt.show()
# 梯度计算
gradX = cv2.convertScaleAbs(cv2.Sobel(idcard_gray_tophat, cv2.CV_64F, 1, 0, ksize=3))
gradY = cv2.convertScaleAbs(cv2.Sobel(idcard_gray_tophat, cv2.CV_64F, 0, 1, ksize=3))
grad = cv2.addWeighted(gradX, 0.5, gradY, 0.5, 0)
plt.imshow(grad)
plt.show()
# 闭操作
grad_close = cv2.morphologyEx(grad, cv2.MORPH_CLOSE, rectKernel)
plt.imshow(grad_close)
plt.show()
# 自适应阈值二值化
close_autobin = cv2.threshold(grad_close, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
plt.imshow(close_autobin)
plt.show()
# 闭操作
close2 = cv2.morphologyEx(close_autobin, cv2.MORPH_CLOSE, rectKernel2)
plt.imshow(close2)
plt.show()

        这段代码对读入的银行卡图片进行了多次形态学操作,其输出如下:

 

 tophat

        tophat操作将图片中的反差较大的信息提取了出来。

 

grad 

        梯度操作在tophat的基础上描出了梯度变化较大的部分。

 

 第一次闭操作

        闭操作将梯度轮廓进行了粘连。

 

 二值化

 

第二次闭操作 

        进一步粘连二值化后的结果,使四个号码成块出现。

        需要注意的是,实现上述效果的具体途径不是唯一的,各种操作综合运用是关键所在。在上面给出的例子中,经过反复调整,笔者发现,卷积核大小的设置是至关重要的,卷积核过小的话,进行闭操作无法将数字连在一起,分离的数字不利于下一步的筛选;而卷积核设置过大的话,数字可能会和其他色块粘连在一起,无法通过下一步的像素宽高比和色块大小进行筛选出来,导致特征丢失。

4. 卡号筛选与ROI切割

# 找边界
contours, hier = cv2.findContours(close2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
boundingBoxes = []
# 按照宽高比和像素范围筛选数字组
for cnt in contours:
    x, y, w, h = cv2.boundingRect(cnt)
    ar = w / float(h)
    if 2.6 < ar < 3.8 and 70 < w < 115 and 15 < h < 45:
        boundingBoxes.append([x, y, w, h])
boundingBoxes.sort(key=lambda box: box[0])
group = []
# ROI
for [x, y, w, h] in boundingBoxes:
    group.append(idcard_gray[y - 5:y + h + 5, x - 5:x + w + 5])
    plt.imshow(idcard_gray[y - 5:y + h + 5, x - 5:x + w + 5])
    plt.show()
group_binary = []
# 对数字组进行形态学操作
for nominee in group:
    nominee_bin = cv2.threshold(nominee, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    nominee_close = cv2.morphologyEx(nominee_bin, cv2.MORPH_CLOSE, basicKernel)
    group_binary.append(nominee_close)

    plt.imshow(cv2.threshold(nominee, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1])
    plt.show()
num_nominee = []
# 对数字组找边界
for nominee in group_binary:
    contours, hier = cv2.findContours(nominee, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    rectangles = []
    for cnt in contours:
        x, y, w, h = cv2.boundingRect(cnt)
        rectangles.append([x, y, w, h])
    rectangles.sort(key=lambda rect: rect[0])
    # ROI切割出单个数字
    for [x, y, w, h] in rectangles:
        if w > 8 and h > 20:
            num_nominee.append(nominee[y:y + h, x:x + w])
            plt.imshow(nominee[y:y + h, x:x + w])
            plt.show()

输出如下:

opencv数量识别 opencv模式识别_人工智能_02

 数字组ROI

opencv数量识别 opencv模式识别_opencv_03

 形态学操作后

opencv数量识别 opencv模式识别_计算机视觉_04

 单个数字ROI

        本节代码中要注意几点:

        1.按照二次闭操作后的结果找到原图的边界后,需要对边界进行筛选,这里按照边界外接矩形的宽高比和像素大小进行筛选,这里的范围自行确定,只要避免错选和漏选。

        2.第一次ROI切割要保留几个像素的切割余量。

        3.对数字组ROI二值化后又进行闭操作是为了防止单个数字的笔画没有连起来。

        4.对单个数字的筛选同1。

        5.要注意对轮廓外矩形排序后提取ROI。

5.模版匹配,得出结果

num_nominee = unify_size(num_nominee)
num_standard = []
(x, y) = num_nominee[0].shape
for i in range(len(num_template)):
    num_standard.append(cv2.resize(num_template[i], (y, x)))
ans = []
for nominee in num_nominee:
    score = []
    for standard in num_standard:
        res = cv2.matchTemplate(nominee, standard, cv2.TM_SQDIFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        score.append(min_val)
    ans.append(score.index(min(score)))
print(ans)

        首先对模版数字和待匹配数字大小统一化。

        外循环遍历待匹配数字,内循环将其与10个模版匹配并打分,这里笔者采用了TM_SQDIFF_NORMED模式,分数越低,相似度越高。然后获取最低分对应的数组下标作为结果输出。

结语

        实现这样一个OCR的效果并不难,主要是对OpenCV基础函数的综合运用。本文提到的方法还有大量的改进空间,还存在很大的局限性。