使用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 14 Python
Python中字符串的处理技巧分享
Sep 17 Python
python中学习K-Means和图片压缩
Nov 20 Python
使用Python通过win32 COM打开Excel并添加Sheet的方法
May 02 Python
Python基于Opencv来快速实现人脸识别过程详解(完整版)
Jul 11 Python
python将字典列表导出为Excel文件的方法
Sep 02 Python
pymysql模块的使用(增删改查)详解
Sep 09 Python
python 实现生成均匀分布的点
Dec 05 Python
Python实现线性判别分析(LDA)的MATLAB方式
Dec 09 Python
Python如何基于smtplib发不同格式的邮件
Dec 30 Python
Python 面向对象部分知识点小结
Mar 09 Python
python实战之一步一步教你绘制小猪佩奇
Apr 22 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创建动态图像
2006/10/09 PHP
一个程序下载的管理程序(三)
2006/10/09 PHP
global.php
2006/12/09 PHP
php strnatcmp()函数的用法总结
2013/11/27 PHP
thinkphp配置连接数据库技巧
2014/12/02 PHP
php+xml编程之SimpleXML的应用实例
2015/01/24 PHP
PHP判断用户是否已经登录(跳转到不同页面或者执行不同动作)
2016/09/22 PHP
PHP实现数组的笛卡尔积运算示例
2017/12/15 PHP
php用户名的密码加密更安全的方法
2019/06/21 PHP
在html页面中包含共享页面的方法
2008/10/24 Javascript
js 鼠标拖动对象 可让任何div实现拖动效果
2009/11/09 Javascript
js escape,unescape解决中文乱码问题的方法
2010/05/26 Javascript
jQuery提交表单ajax查询实例代码
2012/10/07 Javascript
jquery退出each循环的写法
2014/02/26 Javascript
JavaScript中的boolean布尔值使用学习及相关技巧讲解
2016/05/26 Javascript
javascript回到顶部特效
2016/07/30 Javascript
帝国cms首页列表页实现点赞功能
2017/10/30 Javascript
JavaScript实现QQ列表展开收缩扩展功能
2017/10/30 Javascript
vue2.0基于vue-cli+element-ui制作树形treeTable
2019/04/30 Javascript
JS中的一些常用的函数式编程术语
2019/06/15 Javascript
[01:24]2014DOTA2 TI第二日 YYF表示这届谁赢都有可能
2014/07/11 DOTA
Python警察与小偷的实现之一客户端与服务端通信实例
2014/10/09 Python
详解django实现自定义manage命令的扩展
2019/08/13 Python
python3中使用__slots__限定实例属性操作分析
2020/02/14 Python
计算Python Numpy向量之间的欧氏距离实例
2020/05/22 Python
python求numpy中array按列非零元素的平均值案例
2020/06/08 Python
哪种Python框架适合你?简单介绍几种主流Python框架
2020/08/04 Python
定义css设备类型-Media Queries图表简介及使用方法
2013/01/21 HTML / CSS
美国网上鞋城:Shoeline.com
2016/11/17 全球购物
英国女鞋购物网站:Moda in Pelle
2019/02/18 全球购物
酒店采购员岗位职责
2014/03/14 职场文书
室内设计专业毕业生求职信
2014/05/02 职场文书
学校节水倡议书
2015/04/29 职场文书
诚信考试主题班会
2015/08/17 职场文书
家长必看:义务教育,不得以面试 评测等名义选拔学生
2019/07/09 职场文书
Vue过滤器(filter)实现及应用场景详解
2021/06/15 Vue.js