使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能


Posted in Python onAugust 30, 2021

这篇博客将介绍如何通过OpenCV和Python使用模板匹配执行光学字符识别(OCR)。具体来说,将使用Python+OpenCV实现模板匹配算法,以自动识别卡的类型和以及16位卡号数字。

在比较数字时,模板匹配是一种非常快速的方法。

为此将图像处理管道分为4个步骤:

  1. 通过各种图像处理技术检测信用卡上四组四个数字,包括形态学操作、阈值和轮廓提取。
  2. 从四个分组中提取每个单独的数字,得到16个需要分类的数字。
  3. 将模板匹配应用于每个数字,将其与OCR-A字体进行比较,以获得数字分类。
  4. 检查信用卡号的第一位数字以确定发卡公司。

在对信用卡OCR系统进行评估后,发现如果发卡信用卡公司使用OCR-A字体作为数字,该系统的准确率为100%。 优化可以考虑在野外采集信用卡的真实图像,并训练机器学习模型(通过标准特征提取或训练或卷积神经网络),以进一步提高此系统的准确性。

1. 效果图

首先了解一下卡的组成:

使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能

OCR-A 参考字体识别如下:原始图 VS 灰度图 VS 阈值化图 VS 轮廓每个数字提取图:
灰度图:忽略颜色对轮廓提取的影响
阈值化图:使得轮廓在前景白色,背景黑色便于轮廓提取。
轮廓提取图:提取每个数字ROI并记录,方便后续对比卡片中的区域以识别出对应的数字。

使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能

以下卡号均是演示卡,
正确的识别卡的类型和卡号,效果图1:

使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能

识别过程1——原图 VS 灰度图 VS 白帽图 VS 梯度图如下:
灰度图:忽略色彩影响
白帽图:从较暗的背景中提取较亮的区域
梯度图:计算Schaar梯度图,便于了解图像的色彩分配及提取;

使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能

识别过程2——形态学闭合图 VS 二值化图1 VS 阈值化图2 如下:
形态学闭合图:矩形框形态学闭合操作,以帮助闭合信用卡数字之间的小的缝隙
二值化图:以便于提取
阈值化图:方形框形态学闭合操作,以二次帮助闭合信用卡数字区域之间的缝隙

使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能

识别过程3——轮廓过滤图 VS 提取最终效果图 如下:
轮廓过滤图:根据面积及纵横比,只保留卡片中的卡号区
最终效果图:提取4组4数字每一个组,然后对每一个组中的4个数字进行截取ROI并识别,并与之前存储的数字ROI进行模板匹配,选取匹配值最高的作为最终结果。

使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能

2. 原理

2.1 OCR-A字体

OCR-A字体,是一种专门用于辅助光学字符识别算法的字体。

主要分为:

检测图像中信用卡的位置;本地化信用卡上的四组四位数字;应用OCR识别信用卡上的16位数字;识别信用卡的类型。

Tesseract库在某些情况无法正确识别数字(这可能是因为Tesseract未接受信用卡示例字体培训)。

2.2 检测过程步骤

在字典中存储卡类型映射关系(卡号的第一位数字代表卡类型)。获取参考图像并提取数字。将数字模板存储在字典中。本地化四个信用卡号组,每个组有四位数字(总共16位)。提取要“匹配”的数字。对每个数字执行模板匹配,将每个单独的ROI与每个数字模板0-9进行比较,同时存储每个尝试匹配的分数。查找每个候选数字的最高分数,并构建一个名为“输出”的列表。其中包含信用卡号。将信用卡号和信用卡类型输出到终端,并将输出图像显示到屏幕上。

2.3 优化

使用OpenCV和Python匹配OCR脚本的模板在100%的时间内正确识别了16位数字中的每一位。然而在将OCR图像应用于真实的信用卡图像时,考虑到照明条件、视角和其他一般噪音的变化,可能需要采取更面向机器学习的方法。

3. 源代码

# 信用卡类型及卡号OCR系统
# USAGE
# python ocr_template_match.py --reference images/ocr_a_reference.png --image images/credit_card_05.png

import argparse

import cv2
import imutils
import numpy as np
# 导入必要的包
from imutils import contours

# 构建命令行参数及解析
# --image 必须 要进行OCR的输入图像
# --reference 必须 参考OCR-A图像
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
                help="path to input image")
ap.add_argument("-r", "--reference", required=True,
                help="path to reference OCR-A image")
args = vars(ap.parse_args())

# 定义一个字典(映射信用卡第一位数字和信用卡类型的编号)
FIRST_NUMBER = {
    "3": "American Express",
    "4": "Visa",
    "5": "MasterCard",
    "6": "Discover Card"
}

# 从磁盘加载参考OCR-A图像,转换为灰度图,阈值化图像以显示为白色前景和黑色背景
# 并反转图像
# and invert it, such that the digits appear as *white* on a *black*
ref_origin = cv2.imread(args["reference"])
cv2.imshow("ref_origin", ref_origin)
ref = ref_origin.copy()
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
cv2.imshow("ref_gray", ref)
ref = cv2.threshold(ref, 180, 255, cv2.THRESH_BINARY)[1]
cv2.imshow("ref_threshhold", ref)
cv2.waitKey(0)

# 寻找OCR-A图像中的轮廓(数字的外轮廓线)
# 并从左到右排序轮廓,初始化一个字典来存储数字ROI
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,
                           cv2.CHAIN_APPROX_SIMPLE)
print('findContours: ', len(refCnts))
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
digits = {}

# 遍历OCR-A轮廓
for (i, c) in enumerate(refCnts):
    # 计算数字的边界框,提取它,缩放到固定的大小
    (x, y, w, h) = cv2.boundingRect(c)
    cv2.rectangle(ref_origin, (x, y), (x + w, y + h), (0, 255, 0), 2)
    roi = ref[y:y + h, x:x + w]
    roi = cv2.resize(roi, (57, 88))

    # 更新数字字典,数字匹配ROI
    digits[i] = roi
cv2.imshow("ref and digits", ref_origin)
cv2.waitKey(0)

# 初始化矩形和方形结构内核
# 在图像上滑动它来进行(卷积)操作,如模糊、锐化、边缘检测或其他图像处理操作。
# 使用矩形函数作为Top-hat形态学运算符,使用方形函数作为闭合运算。
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

# 准备进行OCR的输入图像
# 加载输入图像,保持纵横比缩放图像宽度为300,转换为灰度图
origin = cv2.imread(args["image"])
origin = imutils.resize(origin, width=300)
image = origin.copy()
cv2.imshow("origin", origin)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)

# 执行形态学操作
# 应用tophat(白帽)形态学操作以在暗的背景中提取出亮的区域(信用卡上的数字卡号)
# Top hat操作在深色背景(即信用卡号)下显示浅色区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
cv2.imshow("tophat", tophat)

# 计算Scharr梯度,计算梯度值
# 在白色礼帽上,计算x方向的Scharr梯度,然后缩放到范围[0, 255]
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
# 最小/最大归一化, 由float转换gradX到uint8范围[0-255]
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
cv2.imshow("gradient", gradX)

# 使用矩形框应用闭合操作以帮助闭合信用卡数字之间的小的缝隙
# 应用Otsu's阈值方法二值化图像
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv2.imshow("morphologyEx", gradX)
thresh = cv2.threshold(gradX, 0, 255,
                       cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imshow("thresh1", thresh)

# 在二值化图像上,应用二次闭合操作
# 再一次方形框形态学操作,帮助闭合信用卡数字区域之间的缝隙
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
cv2.imshow("thresh2", thresh)

# 阈值图像中查找轮廓,然后初始化数字位置列表
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
locs = []

# 遍历轮廓
for (i, c) in enumerate(cnts):
    # 计算轮廓的边界框,并计算纵横比
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)

    # 由于信用卡有固定的4组4数字,可以根据纵横比来寻找潜在的轮廓
    if ar > 2.5 and ar < 4.0:
        # 轮廓可以在最小/最大宽度上进一步修剪
        if (w > 40 and w < 55) and (h > 10 and h < 20):
            # 添加数字组轮廓的编辑框轮廓到位置list
            locs.append((x, y, w, h))
            cv2.rectangle(origin, (x, y), (x + w, y + h), (255, 0, 0), -1)

cv2.imshow("contours filter", origin)
# 突出显示信用卡上四组四位数字(总共十六位)。
# 从左到右排序轮廓,并初始化list来存储信用卡数字列表
locs = sorted(locs, key=lambda x: x[0])
output = []

# 遍历四组四位数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
    # 初始化存放每组数字的list
    groupOutput = []

    # 提取每组4位数字的灰度图ROI
    # 应用阈值方法从背景信用卡中分割数字
    group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
    group = cv2.threshold(group, 0, 255,
                          cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

    # 检测组中每个单独数字的轮廓
    # 从左到右排序轮廓
    digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
                                 cv2.CHAIN_APPROX_SIMPLE)
    digitCnts = imutils.grab_contours(digitCnts)
    digitCnts = contours.sort_contours(digitCnts,
                                       method="left-to-right")[0]

    # 遍历数字轮廓
    for c in digitCnts:
        # 计算每个单独数字的边界框
        # 提取数字,缩放以拥有和参考OCR-A字体模板图像相同的大小
        (x, y, w, h) = cv2.boundingRect(c)
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))

        # 初始化模板匹配分数list
        scores = []

        # 遍历参考数字名和数字ROI
        for (digit, digitROI) in digits.items():
            # 应用基于相关性的模板匹配,计算分数,更新分数list
            # apply correlation-based template matching, take the
            # score, and update the scores list
            result = cv2.matchTemplate(roi, digitROI,
                                       cv2.TM_CCOEFF)
            (_, score, _, _) = cv2.minMaxLoc(result)
            scores.append(score)

        # 数字ROI的分类将取 模板匹配分数中分数最大的参考数字
        # the classification for the digit ROI will be the reference
        # digit name with the *largest* template matching score
        groupOutput.append(str(np.argmax(scores)))

    # 围绕每组画一个矩形,并以红色文本标识图像上的信用卡号
    # 绘制每组的数字识别分类结果
    cv2.rectangle(image, (gX - 5, gY - 5),
                  (gX + gW + 5, gY + gH + 5), (0, 0, 255), 2)
    cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
                cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

    # 更新输出数字分组列表
    # Pythonic的方法是使用extend函数,它将iterable对象的每个元素(本例中为列表)追加到列表的末尾
    output.extend(groupOutput)

# 显示检测到的信用卡类型和卡号到屏幕上
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)

参考 https://www.pyimagesearch.com/2017/07/17/credit-card-ocr-with-opencv-and-python/

到此这篇关于使用Pyhton+OpenCV进行卡类型及16位卡号数字的OCR功能的文章就介绍到这了,更多相关Pyhton+OpenCV卡号数字识别内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python多线程扫描端口示例
Jan 16 Python
python解决方案:WindowsError: [Error 2]
Aug 28 Python
Python中import机制详解
Nov 14 Python
Python加载带有注释的Json文件实例
May 23 Python
python调用OpenCV实现人脸识别功能
May 25 Python
Python异常处理操作实例详解
Aug 28 Python
python的debug实用工具 pdb详解
Jul 12 Python
解决pycharm下pyuic工具使用的问题
Apr 08 Python
基于Python爬取51cto博客页面信息过程解析
Aug 25 Python
Scrapy爬虫文件批量运行的实现
Sep 30 Python
Pytorch distributed 多卡并行载入模型操作
Jun 05 Python
Python OpenCV形态学运算示例详解
Apr 07 Python
OpenCV绘制圆端矩形的示例代码
Aug 30 #Python
python中super()函数的理解与基本使用
python自动化操作之动态验证码、滑动验证码的降噪和识别
Aug 30 #Python
Python图片验证码降噪和8邻域降噪
Aug 30 #Python
Python音乐爬虫完美绕过反爬
Aug 30 #Python
详解解Django 多对多表关系的三种创建方式
Aug 23 #Python
一些让Python代码简洁的实用技巧总结
Aug 23 #Python
You might like
php实现文本数据导入SQL SERVER
2015/05/17 PHP
PHP获得数组交集与差集的方法
2015/06/10 PHP
php使用curl打开https网站的方法
2015/06/17 PHP
PHP高效获取远程图片尺寸和大小的实现方法
2017/10/20 PHP
ThinkPHP实现的rsa非对称加密类示例
2018/05/29 PHP
PHP与Perl之间知识点区别整理
2019/03/19 PHP
Exjs 入门篇
2010/04/07 Javascript
javascript 事件处理、鼠标拖动效果实现方法详解
2012/05/11 Javascript
javascript动画浅析
2012/08/30 Javascript
js/jQuery简单实现选项卡功能
2014/01/02 Javascript
iframe如何动态创建及释放其所占内存
2014/09/03 Javascript
js对字符的验证方法汇总
2015/02/04 Javascript
javascript实现十秒钟后注册按钮可点击的方法
2015/05/13 Javascript
laypage分页控件使用实例详解
2016/05/19 Javascript
jQuery插件编写步骤详解
2016/06/03 Javascript
AngularJS过滤器filter用法实例分析
2016/11/04 Javascript
Node.js连接postgreSQL并进行数据操作
2016/12/18 Javascript
Vue.js 2.0窥探之Virtual DOM到底是什么?
2017/02/10 Javascript
JavaScript反弹动画效果的实现代码
2017/07/13 Javascript
浅谈Node.js ORM框架Sequlize之表间关系
2017/07/24 Javascript
vue+springmvc导出excel数据的实现代码
2018/06/27 Javascript
详解Python中映射类型(字典)操作符的概念和使用
2015/08/19 Python
python:socket传输大文件示例
2017/01/18 Python
python Matplotlib画图之调整字体大小的示例
2017/11/20 Python
Python多叉树的构造及取出节点数据(treelib)的方法
2019/08/09 Python
python实现图像拼接功能
2020/03/23 Python
解决django xadmin主题不显示和只显示bootstrap2的问题
2020/03/30 Python
pandas分批读取大数据集教程
2020/06/06 Python
如何一键升级Python所有包
2020/11/05 Python
浅谈基于HTML5的在线视频播放方案
2016/02/18 HTML / CSS
《盘古开天地》教学反思
2014/02/28 职场文书
爱我中华教学反思
2014/04/28 职场文书
殡葬服务心得体会
2014/09/11 职场文书
学校学期工作总结
2015/08/13 职场文书
JAVA SpringMVC实现自定义拦截器
2022/03/16 Python
PostgreSQL聚合函数介绍以及分组和排序
2022/04/12 PostgreSQL