使用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 ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍
Jun 10 Python
python通过wxPython打开一个音频文件并播放的方法
Mar 25 Python
python 3.0 模拟用户登录功能并实现三次错误锁定
Nov 01 Python
PyTorch学习笔记之回归实战
May 28 Python
Python返回数组/List长度的实例
Jun 23 Python
Python实用技巧之利用元组代替字典并为元组元素命名
Jul 11 Python
python的中异常处理机制
Aug 30 Python
Python中最大递归深度值的探讨
Mar 05 Python
python使用配置文件过程详解
Dec 28 Python
Python 实现黑客帝国中的字符雨的示例代码
Feb 20 Python
python能在浏览器能运行吗
Jun 17 Python
Python内存泄漏和内存溢出的解决方案
Sep 26 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
浅析HTTP消息头网页缓存控制以及header常用指令介绍
2013/06/28 PHP
Json_decode 解析json字符串为NULL的解决方法(必看)
2017/02/17 PHP
JQuery中如何传递参数如click(),change()等具体实现
2013/04/28 Javascript
JQuery的自定义事件代码,触发,绑定简单实例
2013/08/01 Javascript
输入自动提示搜索提示功能的使用说明:sugggestion.txt
2013/09/02 Javascript
js实现单行文本向上滚动效果实例代码
2013/11/28 Javascript
JS实现仿中关村论坛评分后弹出提示效果的方法
2015/02/23 Javascript
JavaScript中函数(Function)的apply与call理解
2015/07/08 Javascript
使用jQuery制作基础的Web图片轮播效果
2016/04/22 Javascript
js控制台输出的方法(详解)
2016/11/26 Javascript
最常见和最有用的字符串相关的方法详解
2017/02/06 Javascript
原生js实现放大镜特效
2017/03/08 Javascript
JavaScript简介_动力节点Java学院整理
2017/06/26 Javascript
vue.js路由跳转详解
2017/08/28 Javascript
你点的 ES6一些小技巧,请查收
2018/04/25 Javascript
JS合并两个数组的3种方法详解
2019/10/24 Javascript
jQuery实现可编辑的表格
2019/12/11 jQuery
vue中实现点击按钮滚动到页面对应位置的方法(使用c3平滑属性实现)
2019/12/29 Javascript
vant-ui框架的一个bug(解决切换后onload不触发)
2020/11/11 Javascript
[02:36]DOTA2上海特锦赛 回忆电竞生涯的重要瞬间
2016/03/25 DOTA
Python的Twisted框架上手前所必须了解的异步编程思想
2016/05/25 Python
Python针对给定列表中元素进行翻转操作的方法分析
2018/04/27 Python
Python如何实现转换URL详解
2019/07/02 Python
python3实现斐波那契数列(4种方法)
2019/07/15 Python
python实现桌面气泡提示功能
2019/07/29 Python
Python3.7+tkinter实现查询界面功能
2019/12/24 Python
使用TensorFlow直接获取处理MNIST数据方式
2020/02/10 Python
pytorch数据预处理错误的解决
2020/02/20 Python
PyQt5.6+pycharm配置以及pyinstaller生成exe(小白教程)
2020/06/02 Python
Html5新增标签有哪些
2017/04/13 HTML / CSS
Clos19英国:高档香槟、葡萄酒和烈酒在线购物平台
2020/07/10 全球购物
廉洁家庭事迹材料
2014/05/15 职场文书
高等教育学专业自荐书
2014/06/17 职场文书
初中生毕业评语
2014/12/29 职场文书
交通事故协议书范本
2016/03/19 职场文书
vue ref如何获取子组件属性值
2022/03/31 Vue.js