如何在 Python 中实现机器可读区域 (MRZ) 识别?

发布:2024-10-18 14:32 阅读:44 点赞:0

机器可读区域 (MRZ)是现代护照、签证和身份证采用的一项重要功能。它包含有关证件持有人的基本信息,例如其姓名、性别、国家代码和证件号码。MRZ 识别在边境管制、机场安全和酒店入住过程中起着关键作用。在本教程中,我们将演示如何利用 Dynamsoft Capture Vision SDK在WindowsLinuxmacOS平台上实现 MRZ 识别。本指南将提供分步方法来利用 SDK 的强大功能,使跨平台 MRZ 检测无缝且高效。

macOS 上的 Python 机读区识别演示

先决条件

  • Dynamsoft Capture Vision 试用许可证:获取Dynamsoft Capture Vision SDK 的30 天试用许可证密钥。

  • Python 包:使用以下命令安装所需的 Python 包:

    pip install dynamsoft-capture-vision-bundle opencv-python
    

    这些包裹是做什么用的?

    • dynamsoft-capture-vision-bundle是适用于 Python 的 Dynamsoft Capture Vision SDK。
    • opencv-python捕获相机帧并显示处理后的图像结果。

开始使用 Dynamsoft Python Capture Vision 示例

官方 MRZ 扫描仪示例演示了如何在短时间内使用 Dynamsoft Capture Vision SDK 创建一个简单的基于 Python 的 MRZ 阅读器。

我们来看一下源代码并分析一下其功能:


import sys
from dynamsoft_capture_vision_bundle import *
import os

class MRZResult:
    def __init__(self, item: ParsedResultItem):
        self.doc_type = item.get_code_type()
        self.raw_text=[]
        self.doc_id = None
        self.surname = None
        self.given_name = None
        self.nationality = None
        self.issuer = None
        self.gender = None
        self.date_of_birth = None
        self.date_of_expiry = None
        if self.doc_type == "MRTD_TD3_PASSPORT":
            if item.get_field_value("passportNumber") != None and item.get_field_validation_status("passportNumber") != EnumValidationStatus.VS_FAILED:
                self.doc_id = item.get_field_value("passportNumber")
            elif item.get_field_value("documentNumber") != None and item.get_field_validation_status("documentNumber") != EnumValidationStatus.VS_FAILED:
                self.doc_id = item.get_field_value("documentNumber")

        line = item.get_field_value("line1")
        if line is not None:
            if item.get_field_validation_status("line1") == EnumValidationStatus.VS_FAILED:
                line += ", Validation Failed"
            self.raw_text.append(line)
        line = item.get_field_value("line2")
        if line is not None:
            if item.get_field_validation_status("line2") == EnumValidationStatus.VS_FAILED:
                line += ", Validation Failed"
            self.raw_text.append(line)
        line = item.get_field_value("line3")
        if line is not None:
            if item.get_field_validation_status("line3") == EnumValidationStatus.VS_FAILED:
                line += ", Validation Failed"
            self.raw_text.append(line)

        if item.get_field_value("nationality") != None and item.get_field_validation_status("nationality") != EnumValidationStatus.VS_FAILED:
            self.nationality = item.get_field_value("nationality")
        if item.get_field_value("issuingState") != None and item.get_field_validation_status("issuingState") != EnumValidationStatus.VS_FAILED:
            self.issuer = item.get_field_value("issuingState")
        if item.get_field_value("dateOfBirth") != None and item.get_field_validation_status("dateOfBirth") != EnumValidationStatus.VS_FAILED:
            self.date_of_birth = item.get_field_value("dateOfBirth")
        if item.get_field_value("dateOfExpiry") != None and item.get_field_validation_status("dateOfExpiry") != EnumValidationStatus.VS_FAILED:
            self.date_of_expiry = item.get_field_value("dateOfExpiry")
        if item.get_field_value("sex") != None and item.get_field_validation_status("sex") != EnumValidationStatus.VS_FAILED:
            self.gender = item.get_field_value("sex")
        if item.get_field_value("primaryIdentifier") != None and item.get_field_validation_status("primaryIdentifier") != EnumValidationStatus.VS_FAILED:
            self.surname = item.get_field_value("primaryIdentifier")
        if item.get_field_value("secondaryIdentifier") != None and item.get_field_validation_status("secondaryIdentifier") != EnumValidationStatus.VS_FAILED:
            self.given_name = item.get_field_value("secondaryIdentifier")
    def to_string(self):
        msg = (f"Raw Text:\n")
        for index, line in enumerate(self.raw_text):
            msg += (f"\tLine {index + 1}: {line}\n")
        msg+=(f"Parsed Information:\n"
            f"\tDocumentType: {self.doc_type or ''}\n"
            f"\tDocumentID: {self.doc_id or ''}\n"
            f"\tSurname: {self.surname or ''}\n"
            f"\tGivenName: {self.given_name or ''}\n"
            f"\tNationality: {self.nationality or ''}\n"
            f"\tIssuingCountryorOrganization: {self.issuer or ''}\n"
            f"\tGender: {self.gender or ''}\n"
            f"\tDateofBirth(YYMMDD): {self.date_of_birth or ''}\n"
            f"\tExpirationDate(YYMMDD): {self.date_of_expiry or ''}\n")
        return msg
def print_results(result: ParsedResult) -> None:
    tag = result.get_original_image_tag()
    if isinstance(tag, FileImageTag):
        print("File:", tag.get_file_path())
    if result.get_error_code() != EnumErrorCode.EC_OK:
        print("Error:", result.get_error_string())        
    else:
        items = result.get_items()
        print("Parsed", len(items), "MRZ Zones.")
        for item in items:
            mrz_result = MRZResult(item)
            print(mrz_result.to_string())

if __name__ == '__main__':

    print("**********************************************************")
    print("Welcome to Dynamsoft Capture Vision - MRZ Sample")
    print("**********************************************************")

    error_code, error_message = LicenseManager.init_license("LICENSE-KEY")
    if error_code != EnumErrorCode.EC_OK and error_code != EnumErrorCode.EC_LICENSE_CACHE_USED:
        print("License initialization failed: ErrorCode:", error_code, ", ErrorString:", error_message)
    else:
        cvr_instance = CaptureVisionRouter()
        while (True):
            image_path = input(
                ">> Input your image full path:\n"
                ">> 'Enter' for sample image or 'Q'/'q' to quit\n"
            ).strip('\'"')

            if image_path.lower() == "q":
                sys.exit(0)

            if image_path == "":
                image_path = "../Images/passport-sample.jpg"

            if not os.path.exists(image_path):
                print("The image path does not exist.")
                continue
            result = cvr_instance.capture(image_path, "ReadPassportAndId")
            if result.get_error_code() != EnumErrorCode.EC_OK:
                print("Error:", result.get_error_code(), result.get_error_string())
            else:
                parsed_result = result.get_parsed_result()
                if parsed_result is None or len(parsed_result.get_items()) == 0:
                    print("No parsed results.")
                else:
                    print_results(parsed_result)
    input("Press Enter to quit...")
 

解释

  • LicenseManager.init_license方法使用有效的许可证密钥初始化 Dynamsoft Capture Vision SDK。
  • 该类CaptureVisionRouter管理图像处理任务并协调各个图像处理模块。其capture方法处理输入图像并返回结果。
  • ReadPassportAndId指定处理模式的内置模板。SDK 支持各种处理模式,例如MRZ 识别文档边缘检测条形码检测
  • get_parsed_result方法以字典形式检索 MRZ 识别结果。该类MRZResult提取并包装相关的 MRZ 信息。由于此类可以在不同的应用程序中重复使用,因此建议将其移动到utils.py文件中。

护照图像中机器可读区域位置的可视化

在上面的代码中,是CapturedResultresult类的一个实例。调用其方法将检索TextLineResultItem对象的列表。每个对象都包含检测到的文本行的坐标。使用以下代码片段提取坐标并在护照图像上绘制轮廓:get_recognized_text_lines_result()TextLineResultItem

cv_image = cv2.imread(image_path)
line_result = result.get_recognized_text_lines_result()

items = line_result.get_items()
for item in items:
    location = item.get_location()
    x1 = location.points[0].x
    y1 = location.points[0].y
    x2 = location.points[1].x
    y2 = location.points[1].y
    x3 = location.points[2].x
    y3 = location.points[2].y
    x4 = location.points[3].x
    y4 = location.points[3].y
    del location

    cv2.drawContours(
        cv_image, [np.intp([(x1, y1), (x2, y2), (x3, y3), (x4, y4)])], 0, (0, 255, 0), 2)

cv2.imshow(
    "Original Image with Detected MRZ Zone", cv_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
 

从静态图像进行 Python mrz 识别

通过网络摄像头实时扫描和识别 MRZ

通过网络摄像头实时扫描和识别 MRZ 需要捕获连续的图像流。我们可以使用 OpenCV 库从网络摄像头捕获帧并使用 Dynamsoft Capture Vision SDK 对其进行处理。以下代码片段演示了如何使用网络摄像头实现实时 MRZ 识别:

from dynamsoft_capture_vision_bundle import *
import cv2
import numpy as np
import queue
from utils import *


class FrameFetcher(ImageSourceAdapter):
    def has_next_image_to_fetch(self) -> bool:
        return True

    def add_frame(self, imageData):
        self.add_image_to_buffer(imageData)


class MyCapturedResultReceiver(CapturedResultReceiver):
    def __init__(self, result_queue):
        super().__init__()
        self.result_queue = result_queue

    def on_captured_result_received(self, captured_result):
        self.result_queue.put(captured_result)


if __name__ == '__main__':
    errorCode, errorMsg = LicenseManager.init_license(
        "LICENSE-KEY")
    if errorCode != EnumErrorCode.EC_OK and errorCode != EnumErrorCode.EC_LICENSE_CACHE_USED:
        print("License initialization failed: ErrorCode:",
              errorCode, ", ErrorString:", errorMsg)
    else:
        vc = cv2.VideoCapture(0)
        if not vc.isOpened():
            print("Error: Camera is not opened!")
            exit(1)

        cvr = CaptureVisionRouter()
        fetcher = FrameFetcher()
        cvr.set_input(fetcher)

        # Create a thread-safe queue to store captured items
        result_queue = queue.Queue()

        receiver = MyCapturedResultReceiver(result_queue)
        cvr.add_result_receiver(receiver)

        errorCode, errorMsg = cvr.start_capturing("ReadPassportAndId")

        if errorCode != EnumErrorCode.EC_OK:
            print("error:", errorMsg)

        while True:
            ret, frame = vc.read()
            if not ret:
                print("Error: Cannot read frame!")
                break

            fetcher.add_frame(convertMat2ImageData(frame))

            if not result_queue.empty():
                captured_result = result_queue.get_nowait()

                items = captured_result.get_items()
                for item in items:

                    if item.get_type() == EnumCapturedResultItemType.CRIT_TEXT_LINE:
                        text = item.get_text()
                        line_results = text.split('\n')
                        location = item.get_location()
                        x1 = location.points[0].x
                        y1 = location.points[0].y
                        x2 = location.points[1].x
                        y2 = location.points[1].y
                        x3 = location.points[2].x
                        y3 = location.points[2].y
                        x4 = location.points[3].x
                        y4 = location.points[3].y
                        cv2.drawContours(
                            frame, [np.intp([(x1, y1), (x2, y2), (x3, y3), (x4, y4)])], 0, (0, 255, 0), 2)

                        delta = y3 - y1
                        for line_result in line_results:
                            cv2.putText(
                                frame, line_result, (x1, y1), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
                            y1 += delta

                        del location

                    elif item.get_type() == EnumCapturedResultItemType.CRIT_PARSED_RESULT:
                        mrz_result = MRZResult(item)
                        print(mrz_result.to_string())

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

            cv2.imshow('frame', frame)

        cvr.stop_capturing()
        vc.release()
        cv2.destroyAllWindows()

 

解释

  • 该类FrameFetcher实现ImageSourceAdapter将帧数据输入到内置缓冲区的接口。
  • 该类MyCapturedResultReceiver实现CapturedResultReceiver接口。该on_captured_result_received方法在本机 C++ 工作线程上运行,将CapturedResult对象发送到主线程,并将它们存储在线程安全队列中以供进一步使用。
  • ACapturedResult包含多个CapturedResultItem对象。CRIT_TEXT_LINE类型表示已识别的文本行,类型CRIT_PARSED_RESULT表示已解析的 MRZ 数据。

在 Windows 上运行实时 MRZ 识别演示

Python实时mrz识别