使用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 相关文章推荐
linux系统使用python获取内存使用信息脚本分享
Jan 15 Python
Python循环语句之break与continue的用法
Oct 14 Python
python实现定时自动备份文件到其他主机的实例代码
Feb 23 Python
Python之读取TXT文件的方法小结
Apr 27 Python
详解如何将python3.6软件的py文件打包成exe程序
Oct 09 Python
python 实现将txt文件多行合并为一行并将中间的空格去掉方法
Dec 20 Python
Python Django 前后端分离 API的方法
Aug 28 Python
关于numpy数组轴的使用详解
Dec 05 Python
Python实现计算长方形面积(带参数函数demo)
Jan 18 Python
tensorflow:指定gpu 限制使用量百分比,设置最小使用量的实现
Feb 06 Python
Django QuerySet查询集原理及代码实例
Jun 13 Python
Pycharm2020.1安装中文语言插件的详细教程(不需要汉化)
Aug 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日历制作代码分享
2014/01/20 PHP
curl不使用文件存取cookie php使用curl获取cookie示例
2014/01/26 PHP
自己写的php curl库实现整站克隆功能
2015/02/12 PHP
解决PHP 7编译安装错误:cannot stat ‘phar.phar’: No such file or directory
2017/02/25 PHP
PHP结合Vue实现滚动底部加载效果
2017/12/17 PHP
学习javascript,实现插入排序实现代码
2011/07/31 Javascript
THREE.JS入门教程(3)着色器-下
2013/01/24 Javascript
浅谈关于JavaScript的语言特性分析
2013/04/11 Javascript
BootStrap 附加导航组件
2016/07/22 Javascript
AngularJs 动态加载模块和依赖
2016/09/15 Javascript
canvas实现简易的圆环进度条效果
2017/02/28 Javascript
Vue ElementUI之Form表单验证遇到的问题
2017/08/21 Javascript
vue组件与复用详解
2018/04/08 Javascript
JavaScript实现正则去除a标签并保留内容的方法【测试可用】
2018/07/18 Javascript
H5+C3+JS实现五子棋游戏(AI篇)
2020/05/28 Javascript
详解Vue之父子组件传值
2019/04/01 Javascript
Vue使用NProgress的操作过程解析
2019/10/10 Javascript
vue中的过滤器及其时间格式化问题
2020/04/09 Javascript
[06:07]刀塔密之二:攻之吾命受之吾幸
2014/07/03 DOTA
python读取浮点数和读取文本文件示例
2014/05/06 Python
Python机器学习之SVM支持向量机
2017/12/27 Python
Python使用Selenium+BeautifulSoup爬取淘宝搜索页
2018/02/24 Python
python自动截取需要区域,进行图像识别的方法
2018/05/17 Python
在Python中通过getattr获取对象引用的方法
2019/01/21 Python
Python 3.8正式发布,来尝鲜这些新特性吧
2019/10/15 Python
Crocs欧洲官网:Crocs Europe
2020/01/14 全球购物
Nixon手表英国官网:美国尼克松手表品牌
2020/02/10 全球购物
集团公司总经理岗位职责
2013/12/20 职场文书
国贸专业的职业规划书
2014/03/15 职场文书
个人借款担保书
2014/04/02 职场文书
语文课外活动总结
2014/08/27 职场文书
爱护公共设施倡议书
2014/08/29 职场文书
纪念九一八事变演讲稿:牢记历史,捍卫主权
2014/09/14 职场文书
计划生育个人总结
2015/03/02 职场文书
单位工作证明范本
2015/06/15 职场文书
同学会演讲稿
2019/04/02 职场文书