# 银行支票(从 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 :最小字符高度。此参数是可选的,外汇跟单gendan5.com默认值为 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 获取下一个字符轮廓,计算边界框,初始化 ROI
c = next(charIter)
(cX, cY, cW, cH) = cv2.boundingRect(c)
roi = None
# 检查是否边界框宽度、高度足够大,代表着其是否是数字,还是需要合并 3 个轮廓为 1 个的符号区
if cW >= minW and cH >= minH:
# 提取 ROI
roi = 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)
# 提取 ROI
roi = 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) = loc
cv2.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)