使用Python,OpenCV进行银行支票数字和符号的OCR(第一部分)

  • 1. 效果图
  • 2. 原理
    • 2.1 MICR E-13B字体
    • 2.2 从MICR E-13B参考图像中提取数字和符号
  • 3. 源码
    • 3.1 MICR E-13B符号和数字提取
    • 3.2 银行支票数字和符号OCR
  • 参考

上一篇介绍了:使用Pyhton,OpenCV进行卡类型及16位卡号数字的OCR,这一篇将介绍如何使用Python,OpenCV从支票图像中提取OCR银行账户和路由号码(数字和符号)。

OCR银行支票比OCR信用卡更难——这主要是因为银行支票符号由多个部分组成。因此不能假设参考字体图像中的每个轮廓都映射到单个字符。
相反需要额外的逻辑来检查每个轮廓的尺寸,并确定检测到的是数字还是符号。

在找到符号的情况下,需要抓取下两个轮廓来构建边界框(因为银行支票控件字符由三个不同的部分组成)。

1. 效果图

MICR E-13B字符集简单提取过程如下:原始图 VS 灰度图 VS 阈值化图 VS 简单提取效果图

可以看到符号区域虽然每个符号由3个轮廓组成,但也被展示为3个轮廓。下一步高级复杂提取则将符号区域的3个轮廓提取为一个。

MICR E-13B字符集高级复杂提取效果如下:

银行支票提取数字和符号OCR效果图如下:

银行支票提取数字和符号OCR过程图如下:
原始图 VS 灰度图 VS 黑帽图 VS Scharr梯度图 VS
矩形内核形态学闭合图 VS Octus阈值化二值化图 VS 清除边界图

  • 灰度图:忽略色彩的影响
  • 黑帽图:在浅色背景下查找暗区域(明亮的背景下查找暗的区域——帐号部分)
  • Scharr梯度图:了解颜色分布闭合部分缝隙
  • 矩形内核形态学闭合图:闭合数字字符轮廓之间的小的缝隙
  • Octus阈值化二值化图:将图像分为白色前景和黑色背景,便于轮廓提取
  • 清除边界图:清除掉靠近边界的任何像素,很有效
  • 轮廓过滤:根据轮廓的面积和宽高比过滤掉无效的轮廓

2. 原理

2.1 MICR E-13B字体

MICR(Magnetic Ink Character Recognition 磁性墨水字符识别)是一种用于处理文档的金融行业技术。您经常会在对账单和支票的底部发现这种E-13B格式的磁性墨水。

银行支票上的数字是MICR E-13B字体,如下图包括类似font-a字体,0~9的10个数字,及后边的4个符号区,共14个字符。

  • 数字:数字0-9
  • ⑆ :中转银行分行分隔符
  • ⑇ :金额,交易金额分隔符
  • ⑈ :客户帐号分隔符
  • ⑉ :破折号:数字分隔符

2.2 从MICR E-13B参考图像中提取数字和符号

将使用OpenCV轮廓和一点Python迭代器切片“魔法”来实现提取。
数字区相对容易一些,一个数字一个轮廓。然而每个符号包含3个轮廓。

3. 源码

3.1 MICR E-13B符号和数字提取

# 银行支票(从MICR字体中提取数字和符号提取)
# MICR(Magnetic Ink Character Recognition 磁性墨水字符识别)是一种用于处理文档的金融行业技术。
# 经常会在对账单和支票的底部发现这种E-13B格式的磁性墨水。# USAGE
# python bank_check_ocr.py --image images/example_check.jpg --reference images/micr_chars.png# 导入必要的包
from skimage.segmentation import clear_border # pip install -U scikit-image
from imutils import contours # pip install --upgrade imutils
import numpy as np # pip install numpy
import argparse
import imutils
import cv2# 从MICR中提取数字和符号
# image: MICR E-13B字体图像(在代码下载中提供)。
# charCnts:包含参考图像中字符轮廓的列表
# minW:表示最小字符宽度的可选参数。这有助于当遇到2或3个小轮廓时,它们一起构成一个MICR字符。默认值为5像素的宽度。
# minH:最小字符高度。此参数是可选的,默认值为15像素。rational的用法与minW相同
def extract_digits_and_symbols(image, charCnts, minW=5, minH=15):# 获取字符轮廓列表的内置Python迭代器# 并分别初始化存放ROI和位置的列表charIter = charCnts.__iter__()rois = []locs = []# Python迭代器没有Java等语言中的“hasNext”方法——相反,当iterable对象中没有更多项时,Python将抛出异常。# 因此在函数中使用try-catch块来解释此异常。# 保持遍历字符轮廓,知道到达list末尾while True:try:# 从list获取下一个字符轮廓,计算边界框,初始化ROIc = next(charIter)(cX, cY, cW, cH) = cv2.boundingRect(c)roi = None# 检查是否边界框宽度、高度足够大,代表着其是否是数字,还是需要合并3个轮廓为1个的符号区if cW >= minW and cH >= minH:# 提取ROIroi = image[cY:cY + cH, cX:cX + cW]rois.append(roi)locs.append((cX, cY, cX + cW, cY + cH))# 反之,认为其是符号区的特殊符号之一(需要3个合并一个的部分)# MICR符号区包含3部分,需要合并else:# 从迭代器提取接下来的俩部分,然后计算边界框parts = [c, next(charIter), next(charIter)](sXA, sYA, sXB, sYB) = (np.inf, np.inf, -np.inf,-np.inf)# 遍历每一部分for p in parts:# 计算每一部分的边界框,并更新纪薄变量(pX, pY, pW, pH) = cv2.boundingRect(p)sXA = min(sXA, pX)sYA = min(sYA, pY)sXB = max(sXB, pX + pW)sYB = max(sYB, pY + pH)# 提取ROIroi = image[sYA:sYB, sXA:sXB]rois.append(roi)locs.append((sXA, sYA, sXB, sYB))# 优雅的退出循环except StopIteration:break# 返回区域rois和位置locs的元祖return (rois, locs)# 构建命令行参数及解析
# --image:输入图像路径
# --reference: MICR E-13B字体参考图像
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 MICR E-13B font")
args = vars(ap.parse_args())# 为每个字符和符号创建参考字符名称list,和它们在图片中出现的顺序一样
# T = Transit 划界银行分行路由中转
# U = On-us 界定客户账号
# A = Amount 界定交易金额
# D = Dash 分隔数字的各个部分,如路由或帐户
# 由于OpenCV不支持unicode绘图字符,需要定义“T”表示传输,“U”表示客户帐号,“A”表示数量,“D”表示破折号。
charNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0","T", "U", "A", "D"]# 从磁盘加载MICR字体图像,并进行预处理(转换灰度图、保持宽高比缩放为宽度400、Octus二值化逆阈值化)以保证该图像以白色前景和黑色背景呈现,便于轮廓提取
ref = cv2.imread(args["reference"])
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = imutils.resize(ref, width=400)
ref = cv2.threshold(ref, 0, 255, cv2.THRESH_BINARY_INV |cv2.THRESH_OTSU)[1]# 从MICR图像查找字符的轮廓线,并从左到右排序轮廓
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# 注意:OpenCV 2.4、3和4返回的轮廓不同,因此下一行代码来兼容不同版本及返回值
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]# 复制一个原始图像
clone = np.dstack([ref.copy()] * 3)# 遍历排序过的轮廓
for c in refCnts:# 计算边界框,并绘制边界框绿色在图像上(x, y, w, h) = cv2.boundingRect(c)cv2.rectangle(clone, (x, y), (x + w, y + h), (0, 255, 0), 2)# 展示应用简单轮廓方法的结果
cv2.imshow("Simple Method", clone)
cv2.waitKey(0)# 使用更高级的方法:从轮廓列表中提取数字和符号,然后初始化一个字典来匹配字符名称和ROI
(refROIs, refLocs) = extract_digits_and_symbols(ref, refCnts,minW=10, minH=20)
chars = {}# 重新初始化克隆图像以便于绘制轮廓
clone = np.dstack([ref.copy()] * 3)# 遍历参考list的名称、区域及位置
for (name, roi, loc) in zip(charNames, refROIs, refLocs):# 在输出图像上绘制字符边界框(xA, yA, xB, yB) = loccv2.rectangle(clone, (xA, yA), (xB, yB), (0, 255, 0), 2)# 缩放ROI区域为固定大小36*36,然后更新匹配字符名称和ROI图像的字典roi = cv2.resize(roi, (36, 36))chars[name] = roi# 展示字符ROI到屏幕cv2.imshow("Char", roi)cv2.waitKey(0)# 展示更高级方法提取的效果图
cv2.imshow("Better Method", clone)
cv2.waitKey(0)

3.2 银行支票数字和符号OCR

# 银行支票(从MICR字体中提取数字和符号提取)
# MICR(Magnetic Ink Character Recognition 磁性墨水字符识别)是一种用于处理文档的金融行业技术。
# 经常会在对账单和支票的底部发现这种E-13B格式的磁性墨水。# USAGE
# python bank_check_ocr2.py --image images/example_check.jpg --reference images/micr_chars.pngimport argparseimport cv2
import imutils
import numpy as np  # pip install numpy
# 导入必要的包
from imutils import contours  # pip install --upgrade imutils
from skimage.segmentation import clear_border  # pip install -U scikit-image# 从MICR中提取数字和符号(根据轮廓查找和定位数字和符号)
# image: MICR E-13B字体图像(在代码下载中提供)。
# charCnts:包含参考图像中字符轮廓的列表
# minW:表示最小字符宽度的可选参数。这有助于当遇到2或3个小轮廓时,它们一起构成一个MICR字符。默认值为5像素的宽度。
# minH:最小字符高度。此参数是可选的,默认值为15像素。rational的用法与minW相同
def extract_digits_and_symbols(image, charCnts, minW=5, minH=15):# 获取字符轮廓列表的内置Python迭代器# 并分别初始化存放ROI和位置的列表charIter = charCnts.__iter__()rois = []locs = []# Python迭代器没有Java等语言中的“hasNext”方法——相反,当iterable对象中没有更多项时,Python将抛出异常。# 因此在函数中使用try-catch块来解释此异常。# 保持遍历字符轮廓,知道到达list末尾while True:try:# 从list获取下一个字符轮廓,计算边界框,初始化ROIc = next(charIter)(cX, cY, cW, cH) = cv2.boundingRect(c)roi = None# 检查是否边界框宽度、高度足够大,代表着其是否是数字,还是需要合并3个轮廓为1个的符号区if cW >= minW and cH >= minH:# 提取ROIroi = image[cY:cY + cH, cX:cX + cW]rois.append(roi)locs.append((cX, cY, cX + cW, cY + cH))# 反之,认为其是符号区的特殊符号之一(需要3个合并一个的部分)# MICR符号区包含3部分,需要合并else:# 从迭代器提取接下来的俩部分,然后计算边界框parts = [c, next(charIter), next(charIter)](sXA, sYA, sXB, sYB) = (np.inf, np.inf, -np.inf,-np.inf)# 遍历每一部分for p in parts:# 计算每一部分的边界框,并更新纪薄变量(pX, pY, pW, pH) = cv2.boundingRect(p)sXA = min(sXA, pX)sYA = min(sYA, pY)sXB = max(sXB, pX + pW)sYB = max(sYB, pY + pH)# 提取ROIroi = image[sYA:sYB, sXA:sXB]rois.append(roi)locs.append((sXA, sYA, sXB, sYB))# 捕获停止迭代异常,以优雅的退出循环except StopIteration:break# 返回区域rois和位置locs的元祖return (rois, locs)# 构建命令行参数及解析
# --image:输入图像路径
# --reference: MICR E-13B字体参考图像
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 MICR E-13B font")
args = vars(ap.parse_args())# 为每个字符和符号创建参考字符名称list,和它们在图片中出现的顺序一样
# T = Transit 划界银行分行路由中转
# U = On-us 界定客户账号
# A = Amount 界定交易金额
# D = Dash 分隔数字的各个部分,如路由或帐户
# 由于OpenCV不支持unicode绘图字符,需要定义“T”表示传输,“U”表示客户帐号,“A”表示数量,“D”表示破折号。
charNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0","T", "U", "A", "D"]# 从磁盘加载MICR字体图像,并进行预处理(转换灰度图、保持宽高比缩放为宽度400、Octus二值化逆阈值化)以保证该图像以白色前景和黑色背景呈现,便于轮廓提取
ref = cv2.imread(args["reference"])
ref = imutils.resize(ref, width=400)
cv2.imshow("ref_origin", ref)
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
cv2.imshow("ref_gray", ref)
ref = cv2.threshold(ref, 0, 255, cv2.THRESH_BINARY_INV |cv2.THRESH_OTSU)[1]
cv2.imshow("ref_thresh", ref)# 从MICR图像查找字符的轮廓线,并从左到右排序轮廓
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# 注意:OpenCV 2.4、3和4返回的轮廓不同,因此下一行代码来兼容不同版本及返回值
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]# 使用更高级的方法:从轮廓列表中提取数字和符号,然后初始化一个字典来匹配字符名称和ROI
refROIs = extract_digits_and_symbols(ref, refCnts,minW=10, minH=20)[0]
chars = {}# 遍历参考list的名称、区域及位置
for (name, roi) in zip(charNames, refROIs):# 缩放ROI区域为固定大小36*36,然后更新匹配字符名称和ROI图像的字典roi = cv2.resize(roi, (36, 36))chars[name] = roi# 将实例化一个矩形内核,加载并提取包含帐号的check映像的底部20%:
# 初始化一个list来存储检查的OCR
# an empty list to store the output of the check OCR
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 7))
output = []# 加载输入图像,获取其维度,应用数组切片获取图像底部的20%(包含帐号信息的部分)
# 注意:如果支票被旋转过,看起来是颠倒的或垂直的,那么需要先添加逻辑来旋转它,在支票上应用自上而下的透视变换
image = cv2.imread(args["image"])
origin = image.copy()
# image = imutils.resize(image, width=700)
cv2.imshow("origin", image)
(h, w,) = image.shape[:2]
delta = int(h - (h * 0.2))
bottom = image[delta:h, 0:w]# 转换底部图像为灰度图像,并应用黑帽形态学操作以在浅色背景下查找暗区域(明亮的背景下查找暗的区域——帐号部分)
gray = cv2.cvtColor(bottom, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, rectKernel)
cv2.imshow("blackhat", blackhat)# 计算黑帽图像在x方向上的Scharr梯度,缩放范围为[0,255]
gradX = cv2.Sobel(blackhat, ddepth=cv2.CV_32F, dx=1, dy=0,ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
cv2.imshow("gradX", gradX)# 进一步缩小字符之间的间距并对图像进行二值化:
# 应用矩形内核形态学闭合操作以帮助闭合字符之间的缝隙,然后应用Octus阈值化方法二值化图像
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("thresh", thresh)# 在预处理检查图像时,形态学+阈值操作无疑会留下“假阳性”检测区域————可以应用一些额外的处理来帮助删除这些操作:
# 移除图像边界像素来简单地清除边界;结果很微妙,但非常有用:
thresh = clear_border(thresh)
cv2.imshow("clear_border", thresh)# 在支票上清楚地找到了三组数字。但是如何实际提取每个单独的组?
# 在阈值化图像上寻找轮廓,并初始化一个list来存储每个组的位置
groupCnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
groupCnts = imutils.grab_contours(groupCnts)
groupLocs = []# 遍历每组的轮廓
for (i, c) in enumerate(groupCnts):# 计算轮廓的边界框(x, y, w, h) = cv2.boundingRect(c)# 仅当ROI轮廓足够大的时候,认为其是合法的组轮廓if w > 50 and h > 15:groupLocs.append((x, y, w, h))cv2.rectangle(thresh, (x, y), (x + w, y + h), (255, 255, 0), -1)cv2.imshow("contours", thresh)
# 从左到右排序数字帐号的组轮廓
groupLocs = sorted(groupLocs, key=lambda x: x[0])# 遍历每组轮廓
for (gX, gY, gW, gH) in groupLocs:# 初始化grouplist来存储输出的字符groupOutput = []# 从灰度图像提取字符组的ROI,人后应用阈值化从卡背景中分割数字group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]group = cv2.threshold(group, 0, 255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]cv2.imshow("Group", group)cv2.waitKey(0)# 寻找组中的轮廓,并对它们进行从左到右排序charCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)charCnts = imutils.grab_contours(charCnts)charCnts = contours.sort_contours(charCnts,method="left-to-right")[0]# 提取组中的数字及符号(rois, locs) = extract_digits_and_symbols(group, charCnts)# 遍历组中的ROI区域for roi in rois:# 初始化一个分数list来存储模板匹配的得分# 缩放ROI区域到固定的大小,36*36scores = []roi = cv2.resize(roi, (36, 36))# 遍历参考数字列表和其ROIfor charName in charNames:# 应用基于关系的模板匹配,并获取分数,更新分数listresult = cv2.matchTemplate(roi, chars[charName],cv2.TM_CCOEFF)(_, score, _, _) = cv2.minMaxLoc(result)scores.append(score)# 获取字符ROI中匹配得到最高分数的字符作为分类的结果groupOutput.append(charNames[np.argmax(scores)])# 在每组绘制矩形框,并在其上方输出OCR识别的结果(绘制组输出字符(路由、检查和检查编号)cv2.rectangle(image, (gX - 10, gY + delta - 10),(gX + gW + 10, gY + gY + delta), (0, 0, 255), 2)cv2.putText(image, "".join(groupOutput),(gX - 10, gY + delta - 25), cv2.FONT_HERSHEY_SIMPLEX,0.95, (0, 0, 255), 3)# 把组内的识别结果加入到总的OCR输出listoutput.append("".join(groupOutput))# 展示OCR识别的信息到屏幕
print("Check OCR: {}".format(" ".join(output)))
cv2.imshow("Check OCR", image)
cv2.waitKey(0)

参考

  • https://www.pyimagesearch.com/2017/07/24/bank-check-ocr-with-opencv-and-python-part-i/
  • https://www.pyimagesearch.com/2017/07/31/bank-check-ocr-opencv-python-part-ii/

使用Python,OpenCV进行银行支票数字和符号的OCR相关推荐

  1. 使用Python,OpenCV进行银行支票数字和符号的OCR!

    这一篇将介绍如何使用Python,OpenCV从支票图像中提取OCR银行账户和路由号码(数字和符号). OCR银行支票比OCR信用卡更难--这主要是因为银行支票符号由多个部分组成.因此不能假设参考字体 ...

  2. Python+OpenCV 识别银行卡卡号

    Python+OpenCV 识别银行卡卡号 今天尝试一下用python+OpenCV,使用模板匹配的方式做个简单地识别银行卡卡号(大部分参考网上的,自己改了一部分,代码写的有点不太好,但是思路很清晰, ...

  3. Python实现用户名字母大小写数字特殊符号

    Python 正则表达式: 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配. 字符集: 实例 描述 ^ 匹配字符串的开头 $ 匹配字符串的末尾 [Pp]ython 匹 ...

  4. RPA教学:Python“奇怪的““特殊的“数字字符串处理

    了解RPA: www.i-search.com.cn 学习RPA https://support.i-search.com.cn/ 下载RPA: https://www.i-search.com.cn ...

  5. Python,OpenCV使用KNN来构建手写数字及字母识别OCR

    Python,OpenCV使用KNN来构建手写数字及字母识别OCR 1. 原理 1.1 手写数字识别 1.2 字母识别 2. 源码 2.1 手写数字OCR 2.2 字母OCR 参考 这篇博客将介绍如何 ...

  6. python信用卡识别_python opencv实现信用卡的数字识别

    本项目利用python以及opencv实现信用卡的数字识别 前期准备 导入工具包 定义功能函数 模板图像处理 读取模板图像 cv2.imread(img) 灰度化处理 cv2.cvtColor(img ...

  7. 【实战】python以及opencv实现信用卡的数字识别

    本项目利用python以及opencv实现信用卡的数字识别 前期准备 导入工具包 定义功能函数 模板图像处理 读取模板图像 cv2.imread(img) 灰度化处理 cv2.cvtColor(img ...

  8. 数字图像处理二维码识别 python+opencv实现二维码实时识别

    数字图像处理二维码识别 python+opencv实现二维码实时识别 特点: (1)可以实现普通二维码,条形码: (2)解决了opencv输出中文乱码的问题 (3)增加网页自动跳转功能 (4)实现二维 ...

  9. 【数字图像处理3】(下) 使用Python+OpenCV+EDSR模型,实现超分辨率重建(SR)

    超分辨率重建(SR) Python+OpenCV+EDSR模型 文章目录 超分辨率重建(SR) Python+OpenCV+EDSR模型 注意 1.安装OpenCV contrib模块 2.contr ...

最新文章

  1. 超实用的 Mybatis 3.5 新特性
  2. C/C++调试:gdbserver的简单使用
  3. php抽象类继承抽象类,PHP面向对象程序设计高级特性详解(接口,继承,抽象类,析构,克隆等)...
  4. 九度OJ1111题-单词替换
  5. Python语法之com[1][:-7]
  6. 《统计会犯错——如何避免数据分析中的统计陷阱》—第1章构建置信区间
  7. JDK源码如何启动编译
  8. 永恒之蓝漏洞紧急应对方案
  9. Android7.0 MTK 需求文档(一)
  10. Verilog状态机详述
  11. 交换机console是干什么的
  12. apicloud studio 怎么开启自动wifi同步?
  13. 初级,中级,高级程序员需要具备的能力
  14. JS中NaN定义与用法
  15. ibm服务器日志文件提取,IBM服务器日志搜集方法
  16. 【Unity】 节奏类游戏的表盘卡点功能
  17. MySQL将字段数据自增自减
  18. Assembly ADODB注册
  19. [转载]C#中IndexOf的使用
  20. 【云游戏】云游戏学习与实践(一)——云游戏概述

热门文章

  1. HLS流媒体点播系统
  2. 用友APaaS市场第一 全球领先
  3. 将echarts地图的经纬度坐标转换成页面定位坐标
  4. HFSS学习笔记—4.边界条件
  5. SOCKS5代理的四大应用场景
  6. C#三十五 三层架构企业应用
  7. MailStore邮件归档
  8. codevs 2618 核电站问题 题解报告
  9. 移动网游SDK接入渠道收集
  10. 企业微信加密消息体_加解密方案说明