这一篇将介绍如何使用Python,OpenCV从支票图像中提取OCR银行账户和路由号码(数字和符号)。

OCR银行支票比OCR信用卡更难——这主要是因为银行支票符号由多个部分组成。因此不能假设参考字体图像中的每个轮廓都映射到单个字符。

相反需要额外的逻辑来检查每个轮廓的尺寸,并确定检测到的是数字还是符号。

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

. 效果图

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)w

未完待续,你学会了吗!

需要完整代码,教程的

①兼职交流,行业咨询、大佬在线专业解答

②Python开发环境安装教程

③Python400集自学视频

④软件开发常用词汇

⑤Python学习路线图

⑥3000多本Python电子书

如果你用得到的话可以直接拿走,点击领取。不方便点链接的加群:948351247

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

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

    使用Python,OpenCV进行银行支票数字和符号的OCR(第一部分) 1. 效果图 2. 原理 2.1 MICR E-13B字体 2.2 从MICR E-13B参考图像中提取数字和符号 3. 源码 ...

  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. matlab 降低维度,求助。。。matlab索引超出维度要怎么修改。。。谢谢
  2. 分拣外观残缺的机器人_一款分拣搬运机器人的设计
  3. 特斯拉标准续航版Model Y为什么下架?马斯克这么回答
  4. jQuery操作radio、checkbox、select 集合
  5. 操作系统—死锁的避免
  6. C++基础:指针,函数指针
  7. hdu和poj的基础dp30道
  8. JVM monitoring
  9. 北大17秋c语言作业,17春北大 03081002-计算机编程语言 作业答案
  10. vant部署_Vue 3.x配置Vuex使用Vant TabBar及部署
  11. 【2020年高被引学者】 韩家炜 伊利诺伊大学香槟分校
  12. 新一代报表工具FastReport VCL 6.9发布!
  13. 【python】urlencode、quote、unquote
  14. 【DL-安装遇错】解决出现 pip‘s dependency resolver does not currently take into account all the packages that..
  15. 启中教育:淘宝最有效的投诉电话号码?
  16. 阿里云上克隆代码的两种方法
  17. hash函数原理实现
  18. 绕过AMSI详细指南:如何利用DLL hijack轻松绕过AMSI
  19. 基于C++的云安全主动防御系统客户端服务端设计
  20. (二)通过pygame让游戏人物 动起来

热门文章

  1. 十二月英语总结--充满热情
  2. 一体式风速风向传感器
  3. 顶尖条码标签秤android开发,顶尖电子秤使用教程之条码标操作方法
  4. Linux最小化挂载(mount命令)光盘详解
  5. B站又备战虚拟主播了
  6. 计算机应用基础教学实训计划,计算机应用基础学习计划.docx
  7. DNSPOD动态域名解析
  8. shopNC二次开发必备知识
  9. 地表最强之Android开机Logo动态替换(附100余款车商原厂高清logo)
  10. Adobe illustrator使用教程