更多深度文章,请关注:https://yq.aliyun.com/cloud
今天我们来介绍一下如何使用(opencv/python)来实现 OCR 处理银行票据。文末有代码和相关文档下载!
在第一部分中,我们将讨论两个主题:
1. 首先,我们将了解 MICR E-13B 字体,美国,英国,加拿大等国家用于支票上都是使用的这种字体。
2. 其次,我们将讨论如何从 MICR E-13B 参考图像中提取数字和符号。这将使我们能够提取每个字符的 ROI,然后将其用于 OCR 银行支票。
MICR E-13B 字体:
MICR(磁墨字符识别)是处理文件的金融工业技术。
MICR 的 E-13B 变体包含 14 个字符:
· 数字:数字 0-9。
· ⑆过境:银行分行分隔。
· ⑇金额:交易金额分隔符。
· ⑈在我们:客户帐号分隔符。
· ⑉dash:数字分隔符(例如路由和帐号之间)。
银行支票字符识别看起来更难:
在银行支票上使用的 MICR E-13B 字体中,数字有一个轮廓。但是,控制符号对于每个角色具有三个轮廓,使任务稍微更具挑战性。我们不能使用简单的轮廓和边框方法。相反,我们需要设计自己的方法来可靠地提取数字和符号。
创建一个新的文件,命名为 bank_check_ocr.py,并插入以下代码:
- # import the necessary packages
- from skimage.segmentation import clear_border
- from imutils import contours
- import numpy as np
- import argparse
- import imutils
- import cv2
首先我们先导入各种我们需要的包,以确保程序的正确运行
· OpenCV:从此页面选择适合您系统的安装版本。
· scikit-image :这是通过 pip 安装的, pip install -U scikit-image
· numpy :通过 pip install numpy。
· imutils :这是可以通过 pip 安装: pip install --upgrade imutils。
接着我们来构建一个从 MICR 字体中提取字符的函数:
- def extract_digits_and_symbols(image, charCnts, minW=5, minH=15):
- charIter = charCnts.__iter__()
- rois = []
- locs = []
对于初学者,我们的功能需要 4 个参数:
· image:MICR E-13B 字体图像(代码下载中提供)。
· charCnts:包含参考图像中的字符轮廓的列表。
· minW :表示最小字符宽度的可选参数。默认值为 5 像素宽度。
· minH :最小字符高度。默认值为 15 像素。
接着我们初始化我们的 charCnts 列表的迭代器。列表对象本质上是 "可迭代的",意味着__iter__方法是由生成器完成的。
最后初始化空列表以保存我们的 rois(感兴趣的区域)和 loc(ROI 位置)。我们将在函数结尾的一个元组中返回这些列表。
我们开始循环,看一下迭代器的工作原理:
- while True:
- try:
- c = next(charIter)
- (cX, cY, cW, cH) = cv2.boundingRect(c)
- roi = None
在我们的函数中,我们开始一个无限循环,我们的退出条件是当我们捕获 StopIterator 异常时。为了捕获这个异常,我们需要设置 try-catch 块。对于循环的每次迭代,我们通过调用 next(charIter)来获取下一个字符轮廓。从这个函数调用,我们可以提取矩形的(x,y)坐标和宽度 / 高度。接着我们初始化一个 roi,我们将在短时间内存储字符图像。
接下来,我们将检查我们的边框宽度和高度的大小,并采取相应的措施:
- if cW >= minW and cH >= minH:
- roi = image[cY:cY + cH, cX:cX + cW]
- rois.append(roi)
- locs.append((cX, cY, cX + cW, cY + cH))
如果字符计数器的尺寸分别大于或等于最小宽度和高度,我们采取以下措施:
1. 使用我们的边界矩形调用的坐标和 width / height 从图像中提取 roi。将 roi 添加到 rois 。
3. 向 locs 附加一个元组。该元组由矩形的两个角的(x,y)坐标组成。稍后我们将返回这个位置列表。
否则,我们假设我们正在使用 MICRE-13B 字符符号,需要应用更高级的一组处理操作:
- 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 = image[sYA:sYB, sXA:sXB]
- rois.append(roi)
- locs.append((sXA, sYA, sXB, sYB))
if-else 的 else 块具有分析包含在 MICR E-13B 字体中的多个轮廓的特殊符号的逻辑。我们做的第一件事就是建立符号的部分。正如我们需要知道一个具有一个轮廓角色的边框,我们需要知道包含三个轮廓的角色的边框。为了实现这一点,通过 sYB 初始化四个边界框参数 sXA。
现在我们将循环遍历表示一个字符 / 符号的部分列表。使用边界矩形参数,我们比较和计算与先前值相关的最小值和最大值。这是我们首先通过 sYB 将 sXA 初始化为正 / 负无限值的原因。
现在我们已经找到了围绕符号的框的坐标,我们从图像中提取出 roi,将 roi 附加到 rois,并将框坐标元组附加到 locs
我们函数的剩余代码块处理我们的 while 循环退出条件和 return 语句。
- Except StopIteration:
- break
- return (rois, locs)
如果在 charIter(我们的 iterator 对象)上调用 next 来抛出一个 StopIteration 异常,那么说明我们已经到达了最后一个轮廓。
现在我们已经准备好解析命令行参数并继续执行脚本:
- 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())
在上面的代码中,我们建立了两个必需的命令行参数:
· - image:我们的查询图像。
· - reference :我们的参考 MICR E-13B 字体图像。
接下来,我们将为每个符号 / 字符创建 "名称",并将其存储在列表中。
- charNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "T", "U", "A", "D"]
上面的代码很简单,我们只是在参考图像中从左到右建立了我们遇到的符号的名称。
注意:由于 OpenCV 不支持 unicode 中的绘图字符,因此需要定义 "T" 表示 transit,"U" 表示 "on-us","A" 表示 amount,"D" 表示 dash。
接下来,我们将参考图像加载到内存中,并执行一些预处理:
- 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]
在上面的代码中,我们完成了四个任务:
1. 加载 图像到存储器中作为 参考。
2. 图片转换为灰度级。
3. 调整 width=400 。
4. 使用 Otsu 方法的二进制逆阈值。
这些简单操作的结果可以在下图中看到:
其余的代码遍历分为两部分。首先,我将向您展示一个逻辑和简单的轮廓方法以及生成的图像。
然后,我们将继续使用更高级的方法,利用我们在脚本顶部写的函数 extract_digits_and_symbols 。
对于这两个部分,我们将使用一些常见的数据,包括 ref(参考图像,我们刚刚预处理的)和 refCnt(参考轮廓,我们即将提取)。
- refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,
- cv2.CHAIN_APPROX_SIMPLE)
- refCnts = refCnts[0] if imutils.is_cv2() else refCnts[1]
- refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
- clone = np.dstack([ref.copy()] * 3)
- # loop over the (sorted) contours
- for c in refCnts:
- (x, y, w, h) = cv2.boundingRect(c)
- cv2.rectangle(clone, (x, y), (x + w, y + h), (0, 255, 0), 2)
- # show the output of applying the simple contour method
- cv2.imshow("Simple Method", clone)
- cv2.waitKey(0)
要从参考图像中提取轮廓,我们利用 OpenCV 的 cv2 .findContours 函数
接下来,我们要绘制图像,所以我们将所有频道复制到上称为克隆的图像。在显示结果之前,简单轮廓方法的最后一步是循环遍历排序轮廓。在这个循环中,我们计算每个轮廓的边界框,然后在其周围绘制一个矩形。通过显示图像显示结果并在此处暂停直到按下一个键参见图 4
你看到这种方法的问题吗?问题是我们有 22 个边框,而不是所需的 14 个边界轮廓(每个角色一个)。显然,这个问题可以通过更先进的方法来解决。
更先进的方法如下所示和描述
- (refROIs, refLocs) = extract_digits_and_symbols(ref, refCnts,
- minW=10, minH=20)
- chars = {}
- clone = np.dstack([ref.copy()] * 3)
- for (name, roi, loc) in zip(charNames, refROIs, refLocs):
- # draw a bounding box surrounding the character on the output
- # image
- (xA, yA, xB, yB) = loc
- cv2.rectangle(clone, (xA, yA), (xB, yB), (0, 255, 0), 2)
- # resize the ROI to a fixed size, then update the characters
- # dictionary, mapping the character name to the ROI
- roi = cv2.resize(roi, (36, 36))
- chars[name] = roi
- # display the character ROI to our screen
- cv2.imshow("Char", roi)
- cv2.waitKey(0)
- # show the output of our better method
- cv2.imshow("Better Method", clone)
- cv2.waitKey(0)
我们初始化一个空字典,chars,它将保存每个符号的 name 和 roi。我们通过用新的 ref 副本覆盖 clone 图像来执行此操作(以摆脱刚刚绘制的矩形)。
最后在 for 循环的主体中,首先我们为 clone 图像中的每个角色绘制一个矩形 。
其次,我们调整 roi 到 36 像素和从词典的 roi 和 name 的键值对,更新我们的 chars。最后一步(主要用于调试 / 开发目的)是在屏幕上显示每个 roi,直到按下一个键。所产生的 "更好的方法" 图像显示在屏幕,直到按下一个键,并且结束了我们的脚本。
现在我们已经编码了我们的 MICR E-13B 数字和符号提取器,让我们试一试。
从那里执行以下脚本:
- $ python bank_check_ocr.py --image example_check.png \
- --reference micr_e13b_reference.png
我们可以看到 OCR 的银行支票比 OCR 的信用卡更难识别,主要是由于银行支票符号由多个部分组成。我们不能假设我们的引用字体图像中的每个轮廓映射到一个单独的角色。
相反,我们需要插入额外的逻辑来检查每个轮廓的尺寸,并确定我们正在检查数字或符号。在我们找到一个符号的情况下,我们需要抓住下两个轮廓来构建我们的边界框(因为银行检查控制字符由三个不同的部分组成)。
希望上述的介绍能够帮助到你!
本文由北邮 @爱可可 - 爱生活老师推荐,@阿里云云栖社区组织翻译。
文章原标题《Bank check OCR with OpenCV and Python | PyImageSearch》
作者:Adrian Rosebrock 译者:袁虎 审阅:
文章为简译,更为详细的内容,请查看原文
来源: https://yq.aliyun.com/articles/155215