检测护照图像中的机器可读区域

检测护照图像中的机器可读区域)

这篇博客将介绍如何只使用基本的图像处理技术(例如阈值处理,形态运算和轮廓属性)来检测护照图像中的机器可读区域(MRZ Machine-Readable Zones (MRZs))。

1. 效果图

原始图,来自pyimageblog示例图像~,图中个人信息非真实
在这里插入图片描述
灰度图 VS 阈值化图像1 应用矩形内核减少了字母间间距:
在这里插入图片描述

阈值化图像2:应用正方形内核+腐蚀,减少了各行间隙,腐蚀去除了无关的小斑点
在这里插入图片描述
阈值化图像3 去掉了MRZ区域俩边的斑点
在这里插入图片描述结果图及MRZ区域:
在这里插入图片描述

2. 原理

护照或旅行卡中的机读区分为两类:1类和3类。1类机读区分为三行,每行包含30个字符。3类机读区只有两行,但每行包含44个字符。在任何一种情况下,机读区都对给定公民的身份信息进行编码,包括护照的类型,护照ID,签发国家,姓名,国籍,有效期等。

机读区是在图片的顶部还是底部都没有关系。通过应用形态学运算,提取轮廓并计算轮廓属性,能够准确地提取MRZ。

该方法适用于包含三行的Type 1机读区,仅包含两行的Type 3机读区同样适用。

这篇博客将介绍如何仅使用基本图像处理技术来检测护照扫描中的机器可读区域(MRZ),即:

  • Thresholding 阈值化
  • Gradients 梯度
  • Morphological operations (specifically, closings and erosions).形态学操作(特别是闭合、侵蚀)
  • Contour properties轮廓属性

这些操作虽然简单,却能够检测图像中的MRZ区域,而不必依靠更高级的特征提取和机器学习方法(例如Linear SVM + HOG)来进行对象检测。

3. 源码

# 检测护照图像上的 MRZ Machine Readable Zones
# USAGE
# python detect_mrz.py --images images/origin.jpg

# 导入必要的类
import numpy as np
import argparse
import imutils
import cv2

# 构建命令行参数及解析
# --images 护照图像目录
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--images", required=True, help="path to images directory")
args = vars(ap.parse_args())

# 初始化矩形和正方形结构内核
# 稍后将在应用形态学操作时使用它们,特别是关闭操作。
# 暂时,只需注意第一个内核是矩形,其宽度大约比高度大3倍。
# 第二个内核是正方形。这些内核将能够缩小MRZ字符之间的间隙和MRZ行之间的开口。
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (13, 5))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 21))

# 加载图像,缩放高度未600px,转换为灰度图
image = cv2.imread(args['images'])
image = imutils.resize(image, height=600)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("origin", image)
cv2.waitKey(0)

# 使用3*3高斯核平滑图像以减少高频噪声,然后应用blackhat形态运算符可在浅色背景上找到黑暗区域
# blackhat操作用于在浅色背景(即护照本身的背景)下显示深色区域(即MRZ文本)。由于护照文本在浅色背景上始终是黑色的
gray = cv2.GaussianBlur(gray, (3, 3), 0)
blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, rectKernel)

# MRZ检测的下一步是使用Scharr运算符计算blackhat图像的梯度幅度表示,并缩放结果在[0,255]范围内
# 沿着blackhat图像的x轴计算Scharr梯度,揭示出图像的区域不仅在浅色背景下变暗,而且还包含梯度的垂直变化,例如MRZ文本区域。
# 然后使用最小/最大缩放比例拍摄此渐变图像并将其缩放回[0,255]范围
# 计算blackhat图像的梯度可以提高MRZ检测的准确性,应用此操作对于减少误报机读区非常有帮助。没有它可能会意外地将护照上装饰或设计的区域标记为MRZ
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))).astype("uint8")

# 使用矩形核执行关闭操作以关闭字母之间的间距-然后应用Otsu's thresholding阈值方法
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imshow("gray VS thresh1", np.hstack([gray, thresh]))
cv2.imwrite("gray.jpg", gray)
cv2.imwrite("thresh1.jpg", thresh)
cv2.waitKey(0)

# 下一步是缩小实际线条之间的间隙, 以提供一个与机读区相对应的大矩形区域
# 使用正方形内核执行另一个关闭操作以缩小MRZ各个行之间的间隙,从而提供一个对应于MRZ的大区域。
# 然后执行一系列腐蚀,以断开可能在关闭操作时已连接的连接组件,腐蚀也有助于去除与机读区无关的小斑点。
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
thresh = cv2.erode(thresh, None, iterations=4)
cv2.imshow("thresh2", thresh)
cv2.imwrite("thresh2.jpg", thresh)
cv2.waitKey(0)

# 对于某些护照扫描, 在阈值化过程中, 护照的边框可能已附着到MRZ区域。为了解决这个问题,将图片的左右边框的5%设置为零(即黑色)
p = int(image.shape[1] * 0.05)
thresh[:, 0:p] = 0
thresh[:, image.shape[1] - p:] = 0
cv2.imshow("thresh3", thresh)
cv2.imwrite("thresh3.jpg", thresh)
cv2.waitKey(0)

# 最后一步是在阈值图像中找到轮廓,并使用轮廓属性来识别机读区
# 在阈值图像种寻找轮廓,并根据其面积倒序排列
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

# 遍历所有轮廓
for c in cnts:
    # 计算轮廓的边界框, 并使用它来计算两个属性:纵横比和覆盖率。长宽比就是边界框的宽度除以高度。覆盖率是边界框的宽度除以实际图像的宽度。
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)
    crWidth = w / float(gray.shape[1])

    # 检查机读区的正确性,机读区为矩形宽度远大于高度。机读区应至少覆盖输入图像的75%。
    # 检查长宽比率和覆盖度是否在可接受的标准
    if ar > 5 and crWidth > 0.75:
        # 填充边界框,因为应用了腐蚀,现在需要重新生长
        pX = int((x + w) * 0.03)
        pY = int((y + h) * 0.03)
        (x, y) = (x - pX, y - pY)
        (w, h) = (w + (pX * 2), h + (pY * 2))

        # 从图像中提取ROI区域,并绘制边界框在MRZ上
        roi = image[y:y + h, x:x + w].copy()
        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
        break

# 展示输出图像
cv2.imshow("Result", image)
cv2.imshow("ROI", roi)
cv2.waitKey(0)

参考

猜你喜欢

转载自blog.csdn.net/qq_40985985/article/details/113583413