Developing a Desktop MRZ Scanner for Passports, IDs, and Visas with Dynamsoft C++ Capture Vision SDK

The Machine Readable Zone (MRZ) is a section on passports, IDs, visas, and other travel documents that encodes key personal information, such as the holder’s name, nationality, document number, date of birth, gender, and document expiration date, in a standardized format. In this article, you’ll learn how to use the Dynamsoft Capture Vision C++ SDK to develop a desktop MRZ scanner that accurately recognizes and extracts this information from various travel documents, providing a step-by-step guide to implementing a robust MRZ recognition solution.

MRZ Scanner Demo Video

Prerequisites

Setting Up a CMake Project for MRZ Recognition

This section will guide you through setting up a CMake project to implement MRZ recognition using the Dynamsoft Capture Vision SDK in C++ on both Windows and Linux. We will cover two examples: the first demonstrates loading images from files, and the second shows capturing and processing images from a camera. Both examples include recognizing MRZ data and extracting relevant information.

To get started, create two source files: main.cpp for loading images from files, and maincv.cpp for capturing images from a camera. Then, create a CMakeLists.txt file in your project directory with the following configuration:

cmake_minimum_required (VERSION 3.8)
project (main)
MESSAGE( STATUS "PROJECT_NAME: " ${PROJECT_NAME} )

option(ENABLE_OPENCV "Build with OpenCV" OFF)
MESSAGE(STATUS "Build with OpenCV: ${ENABLE_OPENCV}")

if (CMAKE_HOST_WIN32)
    set(WINDOWS 1)
elseif(CMAKE_HOST_UNIX)
    set(LINUX 1)
endif()

# Set RPATH
if(CMAKE_HOST_UNIX)
    SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
    SET(CMAKE_INSTALL_RPATH "$ORIGIN")
    SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()

# Add search path for include and lib files
MESSAGE( STATUS "CPU architecture ${CMAKE_SYSTEM_PROCESSOR}" )
if(WINDOWS)
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        link_directories("${PROJECT_SOURCE_DIR}/../sdk/platforms/win/bin/") 
    else()
        link_directories("${PROJECT_SOURCE_DIR}/../sdk/platforms/win/lib/") 
    endif()
elseif(LINUX)
    if (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
        MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/../sdk/platforms/linux/" )
        link_directories("${PROJECT_SOURCE_DIR}/../sdk/platforms/linux/")
    endif()
endif()
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/../sdk/include/")

# Add the executable
if (ENABLE_OPENCV)
    find_package(OpenCV REQUIRED)
    add_executable(${PROJECT_NAME} maincv.cpp)
    if(WINDOWS)
        if(CMAKE_CL_64)
            target_link_libraries (${PROJECT_NAME} "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64" ${OpenCV_LIBS})
        endif()
    else()
        target_link_libraries (${PROJECT_NAME} "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility" pthread ${OpenCV_LIBS})
    endif()
else()
    add_executable(${PROJECT_NAME} main.cpp)
    if(WINDOWS)
        if(CMAKE_CL_64)
            target_link_libraries (${PROJECT_NAME} "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64" )
        endif()
    else()
        target_link_libraries (${PROJECT_NAME} "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility"  pthread)
    endif()
endif()

if(WINDOWS)
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    "${PROJECT_SOURCE_DIR}/../sdk/platforms/win/bin/"      
    $<TARGET_FILE_DIR:main>)
else()
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    "${PROJECT_SOURCE_DIR}/../sdk/platforms/linux/"      
    $<TARGET_FILE_DIR:main>)
endif()

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${PROJECT_SOURCE_DIR}/../sdk/DLR-PresetTemplates.json"
$<TARGET_FILE_DIR:main>/DLR-PresetTemplates.json)

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${PROJECT_SOURCE_DIR}/../sdk/MRZ.json"
$<TARGET_FILE_DIR:main>/MRZ.json)

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${PROJECT_SOURCE_DIR}/../sdk/ConfusableChars.data"
$<TARGET_FILE_DIR:main>/ConfusableChars.data)

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:main>/CharacterModel
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${PROJECT_SOURCE_DIR}/../sdk/CharacterModel"
$<TARGET_FILE_DIR:main>/CharacterModel)


add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:main>/ParserResources
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${PROJECT_SOURCE_DIR}/../sdk/ParserResources"
$<TARGET_FILE_DIR:main>/ParserResources)

Explanation

  • CMake Configuration: The CMakeLists.txt file configures the build environment for both Windows and Linux, detecting the operating system and setting paths for libraries and includes accordingly.
  • Enabling OpenCV: The ENABLE_OPENCV option toggles the inclusion of OpenCV support. When enabled, the project compiles maincv.cpp for camera capture. Otherwise, it compiles main.cpp for loading images from files.

  • Library Linking: The project links with necessary Dynamsoft libraries (DynamsoftCore, DynamsoftLicense, DynamsoftCaptureVisionRouter, and DynamsoftUtility).

  • Resource Copying: After building, required DLLs and resource files (e.g., templates, models) are copied to the output directory using add_custom_command to ensure the application has all necessary assets.

Implementing MRZ Recognition with Dynamsoft Capture Vision SDK

Setting Up MRZ Recognition Templates

To start implementing MRZ recognition, you’ll first need to prepare a template file named MRZ.json. This template defines the settings for MRZ recognition, including parameters such as text length ranges, regular expression patterns, concatenation characters, and other configurations that guide the recognition process. Below is a sample MRZ.json file:

{
    "CaptureVisionTemplates": [
        {
            "Name": "default",
            "OutputOriginalImage": 0,
            "ImageROIProcessingNameArray": [
                "roi-mrz"
            ],
            "SemanticProcessingNameArray": [
                "sp-mrz"
            ],
            "Timeout": 1000000
        }
    ],
    "TargetROIDefOptions": [
        {
            "Name": "roi-mrz",
            "TaskSettingNameArray": [
                "task-mrz"
            ]
        }
    ],
    "TextLineSpecificationOptions": [
        {
            "Name": "tls-mrz-passport",
            "BaseTextLineSpecificationName": "tls-base",
            "StringLengthRange": [
                44,
                44
            ],
            "OutputResults": 1,
            "ExpectedGroupsCount": 1,
            "ConcatResults": 1,
            "ConcatSeparator": "",
            "SubGroups": [
                {
                    "StringRegExPattern": "(P[A-Z<][A-Z<]{3}[A-Z<]{39}){(44)}",
                    "StringLengthRange": [
                        44,
                        44
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                },
                {
                    "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}",
                    "StringLengthRange": [
                        44,
                        44
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                }
            ]
        },
        {
            "Name": "tls-mrz-visa-td3",
            "BaseTextLineSpecificationName": "tls-base",
            "StringLengthRange": [
                44,
                44
            ],
            "OutputResults": 1,
            "ExpectedGroupsCount": 1,
            "ConcatResults": 1,
            "ConcatSeparator": "",
            "SubGroups": [
                {
                    "StringRegExPattern": "(V[A-Z<][A-Z<]{3}[A-Z<]{39}){(44)}",
                    "StringLengthRange": [
                        44,
                        44
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                },
                {
                    "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[A-Z0-9<]{2}){(44)}",
                    "StringLengthRange": [
                        44,
                        44
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                }
            ]
        },
        {
            "Name": "tls-mrz-visa-td2",
            "BaseTextLineSpecificationName": "tls-base",
            "StringLengthRange": [
                36,
                36
            ],
            "OutputResults": 1,
            "ExpectedGroupsCount": 1,
            "ConcatResults": 1,
            "ConcatSeparator": "",
            "SubGroups": [
                {
                    "StringRegExPattern": "(V[A-Z<][A-Z<]{3}[A-Z<]{31}){(36)}",
                    "StringLengthRange": [
                        36,
                        36
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                },
                {
                    "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{8}){(36)}",
                    "StringLengthRange": [
                        36,
                        36
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                }
            ]
        },
        {
            "Name": "tls-mrz-id-td2",
            "BaseTextLineSpecificationName": "tls-base",
            "StringLengthRange": [
                36,
                36
            ],
            "OutputResults": 1,
            "ExpectedGroupsCount": 1,
            "ConcatResults": 1,
            "ConcatSeparator": "",
            "SubGroups": [
                {
                    "StringRegExPattern": "([ACI][A-Z<][A-Z<]{3}[A-Z<]{31}){(36)}",
                    "StringLengthRange": [
                        36,
                        36
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                },
                {
                    "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{8}){(36)}",
                    "StringLengthRange": [
                        36,
                        36
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                }
            ]
        },
        {
            "Name": "tls-mrz-id-td1",
            "BaseTextLineSpecificationName": "tls-base",
            "StringLengthRange": [
                30,
                30
            ],
            "OutputResults": 1,
            "ExpectedGroupsCount": 1,
            "ConcatResults": 1,
            "ConcatSeparator": "",
            "SubGroups": [
                {
                    "StringRegExPattern": "([ACI][A-Z<][A-Z<]{3}[A-Z0-9<]{9}[0-9][A-Z0-9<]{15}){(30)}",
                    "StringLengthRange": [
                        30,
                        30
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                },
                {
                    "StringRegExPattern": "([0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z<]{3}[A-Z0-9<]{11}[0-9]){(30)}",
                    "StringLengthRange": [
                        30,
                        30
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                },
                {
                    "StringRegExPattern": "([A-Z<]{30}){(30)}",
                    "StringLengthRange": [
                        30,
                        30
                    ],
                    "BaseTextLineSpecificationName": "tls-base"
                }
            ]
        },
        {
            "Name": "tls-base",
            "CharacterModelName": "MRZ",
            "CharHeightRange": [
                5,
                1000,
                1
            ],
            "BinarizationModes": [
                {
                    "BlockSizeX": 30,
                    "BlockSizeY": 30,
                    "Mode": "BM_LOCAL_BLOCK",
                    "EnableFillBinaryVacancy": 0,
                    "ThresholdCompensation": 15
                }
            ],
            "ConfusableCharactersCorrection": {
                "ConfusableCharacters": [
                    [
                        "0",
                        "O"
                    ],
                    [
                        "1",
                        "I"
                    ],
                    [
                        "5",
                        "S"
                    ]
                ],
                "FontNameArray": [
                    "OCR_B"
                ]
            }
        }
    ],
    "LabelRecognizerTaskSettingOptions": [
        {
            "Name": "task-mrz",
            "ConfusableCharactersPath": "ConfusableChars.data",
            "TextLineSpecificationNameArray": [
                "tls-mrz-passport",
                "tls-mrz-visa-td3",
                "tls-mrz-id-td1",
                "tls-mrz-id-td2",
                "tls-mrz-visa-td2"
            ],
            "SectionImageParameterArray": [
                {
                    "Section": "ST_REGION_PREDETECTION",
                    "ImageParameterName": "ip-mrz"
                },
                {
                    "Section": "ST_TEXT_LINE_LOCALIZATION",
                    "ImageParameterName": "ip-mrz"
                },
                {
                    "Section": "ST_TEXT_LINE_RECOGNITION",
                    "ImageParameterName": "ip-mrz"
                }
            ]
        }
    ],
    "CharacterModelOptions": [
        {
            "DirectoryPath": "",
            "Name": "MRZ"
        }
    ],
    "ImageParameterOptions": [
        {
            "Name": "ip-mrz",
            "TextureDetectionModes": [
                {
                    "Mode": "TDM_GENERAL_WIDTH_CONCENTRATION",
                    "Sensitivity": 8
                }
            ],
            "BinarizationModes": [
                {
                    "EnableFillBinaryVacancy": 0,
                    "ThresholdCompensation": 21,
                    "Mode": "BM_LOCAL_BLOCK"
                }
            ],
            "TextDetectionMode": {
                "Mode": "TTDM_LINE",
                "CharHeightRange": [
                    5,
                    1000,
                    1
                ],
                "Direction": "HORIZONTAL",
                "Sensitivity": 7
            }
        }
    ],
    "SemanticProcessingOptions": [
        {
            "Name": "sp-mrz",
            "ReferenceObjectFilter": {
                "ReferenceTargetROIDefNameArray": [
                    "roi-mrz"
                ]
            },
            "TaskSettingNameArray": [
                "dcp-mrz"
            ]
        }
    ],
    "CodeParserTaskSettingOptions": [
        {
            "Name": "dcp-mrz",
            "CodeSpecifications": [
                "MRTD_TD3_PASSPORT",
                "MRTD_TD2_VISA",
                "MRTD_TD3_VISA",
                "MRTD_TD1_ID",
                "MRTD_TD2_ID"
            ]
        }
    ]
}

The above template supports multiple MRZ document types, including MRTD_TD3_PASSPORT, MRTD_TD2_VISA, MRTD_TD3_VISA, MRTD_TD1_ID, and MRTD_TD2_ID.

Implementing MRZ Data Parsing

Define an MRZResult class to store and parse the MRZ data from recognized results:

class MRZResult
{
public:
  string docId;
  string docType;
  string nationality;
  string issuer;
  string dateOfBirth;
  string dateOfExpiry;
  string gender;
  string surname;
  string givenname;

  vector<string> rawText;

  MRZResult FromParsedResultItem(const CParsedResultItem *item)
  {
    docType = item->GetCodeType();

    if (docType == "MRTD_TD3_PASSPORT")
    {
      if (item->GetFieldValidationStatus("passportNumber") != VS_FAILED && item->GetFieldValue("passportNumber") != NULL)
      {
        docId = item->GetFieldValue("passportNumber");
      }
    }
    else if (item->GetFieldValidationStatus("documentNumber") != VS_FAILED && item->GetFieldValue("documentNumber") != NULL)
    {
      docId = item->GetFieldValue("documentNumber");
    }

    string line;
    if (docType == "MRTD_TD1_ID")
    {
      if (item->GetFieldValue("line1") != NULL)
      {
        line = item->GetFieldValue("line1");
        if (item->GetFieldValidationStatus("line1") == VS_FAILED)
        {
          line += ", Validation Failed";
        }
        rawText.push_back(line);
      }

      if (item->GetFieldValue("line2") != NULL)
      {
        line = item->GetFieldValue("line2");
        if (item->GetFieldValidationStatus("line2") == VS_FAILED)
        {
          line += ", Validation Failed";
        }
        rawText.push_back(line);
      }

      if (item->GetFieldValue("line3") != NULL)
      {
        line = item->GetFieldValue("line3");
        if (item->GetFieldValidationStatus("line3") == VS_FAILED)
        {
          line += ", Validation Failed";
        }
        rawText.push_back(line);
      }
    }
    else
    {
      if (item->GetFieldValue("line1") != NULL)
      {
        line = item->GetFieldValue("line1");
        if (item->GetFieldValidationStatus("line1") == VS_FAILED)
        {
          line += ", Validation Failed";
        }
        rawText.push_back(line);
      }

      if (item->GetFieldValue("line2") != NULL)
      {
        line = item->GetFieldValue("line2");
        if (item->GetFieldValidationStatus("line2") == VS_FAILED)
        {
          line += ", Validation Failed";
        }
        rawText.push_back(line);
      }
    }

    if (item->GetFieldValidationStatus("nationality") != VS_FAILED && item->GetFieldValue("nationality") != NULL)
    {
      nationality = item->GetFieldValue("nationality");
    }
    if (item->GetFieldValidationStatus("issuingState") != VS_FAILED && item->GetFieldValue("issuingState") != NULL)
    {
      issuer = item->GetFieldValue("issuingState");
    }
    if (item->GetFieldValidationStatus("dateOfBirth") != VS_FAILED && item->GetFieldValue("dateOfBirth") != NULL)
    {
      dateOfBirth = item->GetFieldValue("dateOfBirth");
    }
    if (item->GetFieldValidationStatus("dateOfExpiry") != VS_FAILED && item->GetFieldValue("dateOfExpiry") != NULL)
    {
      dateOfExpiry = item->GetFieldValue("dateOfExpiry");
    }
    if (item->GetFieldValidationStatus("sex") != VS_FAILED && item->GetFieldValue("sex") != NULL)
    {
      gender = item->GetFieldValue("sex");
    }
    if (item->GetFieldValidationStatus("primaryIdentifier") != VS_FAILED && item->GetFieldValue("primaryIdentifier") != NULL)
    {
      surname = item->GetFieldValue("primaryIdentifier");
    }
    if (item->GetFieldValidationStatus("secondaryIdentifier") != VS_FAILED && item->GetFieldValue("secondaryIdentifier") != NULL)
    {
      givenname = item->GetFieldValue("secondaryIdentifier");
    }

    return *this;
  }

  string ToString()
  {
    string msg = "Raw Text:\n";
    for (size_t idx = 0; idx < rawText.size(); ++idx)
    {
      msg += "\tLine " + to_string(idx + 1) + ": " + rawText[idx] + "\n";
    }
    msg += "Parsed Information:\n";
    msg += "\tDocument Type: " + docType + "\n";
    msg += "\tDocument ID: " + docId + "\n";
    msg += "\tSurname: " + surname + "\n";
    msg += "\tGiven Name: " + givenname + "\n";
    msg += "\tNationality: " + nationality + "\n";
    msg += "\tIssuing Country or Organization: " + issuer + "\n";
    msg += "\tGender: " + gender + "\n";
    msg += "\tDate of Birth(YYMMDD): " + dateOfBirth + "\n";
    msg += "\tExpiration Date(YYMMDD): " + dateOfExpiry + "\n";

    return msg;
  }
};

Example 1: Recognize MRZ from Files

  1. Initialize the SDK with a valid license key.

     #include <stdio.h>
     #include <string>
     #include <vector>
     #if defined(_WIN32) || defined(_WIN64)
     #include <windows.h>
     #include <conio.h>
     #include <io.h>
     #else
     #include <cstring>
     #include <dirent.h>
     #include <sys/time.h>
     #endif
        
     #include <fstream>
     #include <streambuf>
     #include <iostream>
     #include <sstream>
        
     #include "DynamsoftCaptureVisionRouter.h"
     #include "DynamsoftUtility.h"
        
     using namespace std;
        
     using namespace dynamsoft::cvr;
     using namespace dynamsoft::dlr;
     using namespace dynamsoft::dcp;
     using namespace dynamsoft::license;
     using namespace dynamsoft::basic_structures;
     using namespace dynamsoft::utility;
    
     int main(int argc, char *argv[])
     {
     	printf("*************************************************\r\n");
     	printf("Welcome to Dynamsoft MRZ Demo\r\n");
     	printf("*************************************************\r\n");
     	printf("Hints: Please input 'Q' or 'q' to quit the application.\r\n");
        
     	int iRet = -1;
     	char szErrorMsg[256];
     	// Initialize license.
     	// Request a trial from https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
     	iRet = CLicenseManager::InitLicense("LICENSE-KEY", szErrorMsg, 256);
     	if (iRet != EC_OK)
     	{
     		cout << szErrorMsg << endl;
     	}
     }
    
  2. Load the MRZ recognition template file.

     int errorCode = 1;
     	char errorMsg[512] = {0};
        
     	CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
     	errorCode = cvr->InitSettingsFromFile("MRZ.json", errorMsg, 512);
     	if (errorCode != EC_OK)
     	{
     		cout << "error:" << errorMsg << endl;
     		return -1;
     	}
    
  3. Load images from files and recognize MRZ data in an infinite loop. Press Q or q to quit the application.

     bool GetImagePath(char *pImagePath)
     {
     	std::string input;
     	while (true)
     	{
     		std::cout << "\n>> Step 1: Input your image file's full path:\n";
     		std::getline(std::cin, input);
    
     		input.erase(0, input.find_first_not_of(" \t\n\r\"\'")); 
     		input.erase(input.find_last_not_of(" \t\n\r\"\'") + 1); 
        
     		if (input == "q" || input == "Q")
     		{
     			return true; 
     		}
        
     		std::strncpy(pImagePath, input.c_str(), 511);
     		pImagePath[511] = '\0'; 
        
     		std::ifstream file(pImagePath);
     		if (file.good())
     		{
     			file.close();
     			return false; 
     		}
        
     		std::cout << "Please input a valid path.\n";
     	}
     }
    
    
     char pszImageFile[512] = {0};
     bool bExit = false;
     while (1)
     {
       bExit = GetImagePath(pszImageFile);
       if (bExit)
         break;
       float costTime = 0.0;
       int errorCode = 0;
      
       CCapturedResult *captureResult = cvr->Capture(pszImageFile);
       if (captureResult)
       {
         CParsedResult *parsedResult = captureResult->GetParsedResult();
         if (parsedResult)
         {
           for (int i = 0; i < parsedResult->GetItemsCount(); i++)
           {
             const CParsedResultItem *item = parsedResult->GetItem(i);
             MRZResult result;
             result.FromParsedResultItem(item);
             cout << result.ToString() << endl;
           }
           parsedResult->Release();
         }
      
         captureResult->Release();
       }
     }
      
     delete cvr, cvr = NULL;
     return 0;
    

Example 2: Recognize MRZ from Camera Stream

  1. Initialize the Capture Vision SDK and open the camera with OpenCV.

     #include "opencv2/core.hpp"
     #include "opencv2/imgproc.hpp"
     #include "opencv2/highgui.hpp"
     #include "opencv2/videoio.hpp"
     #include "opencv2/core/utility.hpp"
     #include "opencv2/imgcodecs.hpp"
     #include <iostream>
     #include <vector>
     #include <chrono>
     #include <iostream>
     #include <string>
    
     #include "DynamsoftCaptureVisionRouter.h"
     #include "DynamsoftUtility.h"
    
     using namespace std;
     using namespace cv;
    
     using namespace dynamsoft::cvr;
     using namespace dynamsoft::dlr;
     using namespace dynamsoft::dcp;
     using namespace dynamsoft::license;
     using namespace dynamsoft::basic_structures;
     using namespace dynamsoft::utility;
    
     int main(int argc, char *argv[])
     {
       bool captured = false;
       cout << "Opening camera..." << endl;
       VideoCapture capture(0); // open the first camera
       if (!capture.isOpened())
       {
         cerr << "ERROR: Can't initialize camera capture" << endl;
         cout << "Press any key to quit..." << endl;
         cin.ignore();
         return 1;
       }
       int iRet = -1;
       char szErrorMsg[256];
       // Initialize license.
       // Request a trial from https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
       iRet = CLicenseManager::InitLicense("LICENSE-KEY", szErrorMsg, 256);
       if (iRet != EC_OK)
       {
         cout << szErrorMsg << endl;
       }
     }
    
  2. Register callback functions for appending camera frames and receiving MRZ recognition results.

     class MyCapturedResultReceiver : public CCapturedResultReceiver
     {
     	virtual void OnRecognizedTextLinesReceived(CRecognizedTextLinesResult *pResult) override
     	{
     		std::lock_guard<std::mutex> lock(textResultsMutex);
     		textResults.clear();
        
     		const CImageTag *tag = pResult->GetOriginalImageTag();
        
     		if (pResult->GetErrorCode() != EC_OK)
     		{
     			cout << "Error: " << pResult->GetErrorString() << endl;
     		}
     		else
     		{
     			int lCount = pResult->GetItemsCount();
     			for (int li = 0; li < lCount; ++li)
     			{
     				TextResult result;
        
     				const CTextLineResultItem *textLine = pResult->GetItem(li);
     				CPoint *points = textLine->GetLocation().points;
     				result.textLinePoints.push_back(cv::Point(points[0][0], points[0][1]));
     				result.textLinePoints.push_back(cv::Point(points[1][0], points[1][1]));
     				result.textLinePoints.push_back(cv::Point(points[2][0], points[2][1]));
     				result.textLinePoints.push_back(cv::Point(points[3][0], points[3][1]));
        
     				result.id = tag->GetImageId();
     				textResults.push_back(result);
     			}
     		}
     	}
        
     	virtual void OnParsedResultsReceived(CParsedResult *pResult)
     	{
     		if (pResult == nullptr)
     		{
     			return;
     		}
        
     		const CImageTag *tag = pResult->GetOriginalImageTag();
        
     		if (pResult->GetErrorCode() != EC_OK)
     		{
     			cout << "Error: " << pResult->GetErrorString() << endl;
     		}
     		else
     		{
     			int lCount = pResult->GetItemsCount();
     			for (int i = 0; i < lCount; i++)
     			{
     				const CParsedResultItem *item = pResult->GetItem(i);
        
     				MRZResult result;
     				result.FromParsedResultItem(item);
     				cout << result.ToString() << endl;
        
     				if (textResults[0].id == tag->GetImageId())
     				{
     					std::lock_guard<std::mutex> lock(textResultsMutex);
     					textResults[0].info = result;
     				}
     			}
     		}
        
     		pResult->Release();
     	}
     };
        
     class MyVideoFetcher : public CImageSourceAdapter
     {
     public:
     	MyVideoFetcher() {};
     	~MyVideoFetcher() {};
     	bool HasNextImageToFetch() const override
     	{
     		return true;
     	}
     	void MyAddImageToBuffer(const CImageData *img, bool bClone = true)
     	{
     		AddImageToBuffer(img, bClone);
     	}
     };
    
     int errorCode = 1;
     char errorMsg[512] = {0};
      
     CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
      
     MyVideoFetcher *fetcher = new MyVideoFetcher();
     fetcher->SetMaxImageCount(4);
     fetcher->SetBufferOverflowProtectionMode(BOPM_UPDATE);
     fetcher->SetColourChannelUsageType(CCUT_AUTO);
     cvr->SetInput(fetcher);
      
     CCapturedResultReceiver *capturedReceiver = new MyCapturedResultReceiver;
     cvr->AddResultReceiver(capturedReceiver);
    
  3. Set the MRZ template and start the MRZ recognition process. Press the C key to capture the recognized frame and display the MRZ data.

     errorCode = cvr->InitSettingsFromFile("MRZ.json", errorMsg, 512);
     if (errorCode != EC_OK)
     {
       cout << "error:" << errorMsg << endl;
     }
    
     errorCode = cvr->StartCapturing("", false, errorMsg, 512);
    
     if (errorCode != EC_OK)
     {
       cout << "error:" << errorMsg << endl;
     }
     else
     {
       int width = (int)capture.get(CAP_PROP_FRAME_WIDTH);
       int height = (int)capture.get(CAP_PROP_FRAME_HEIGHT);
    
       for (int i = 1;; ++i)
       {
         Mat frame;
         capture.read(frame);
         if (frame.empty())
         {
           cerr << "ERROR: Can't grab camera frame." << endl;
           break;
         }
         CFileImageTag tag(nullptr, 0, 0);
         tag.SetImageId(i);
         CImageData data(frame.rows * frame.step.p[0],
                 frame.data,
                 width,
                 height,
                 frame.step.p[0],
                 IPF_RGB_888,
                 0,
                 &tag);
         fetcher->MyAddImageToBuffer(&data);
    
         {
           std::lock_guard<std::mutex> lock(textResultsMutex);
           for (const auto &result : textResults)
           {
             if (!result.textLinePoints.empty())
             {
               for (size_t i = 0; i < result.textLinePoints.size(); ++i)
               {
                 cv::line(frame, result.textLinePoints[i],
                     result.textLinePoints[(i + 1) % result.textLinePoints.size()],
                     cv::Scalar(0, 0, 255), 2);
               }
    
               int x = 20;
               int y = 40;
    
               MRZResult mrzResult = result.info;
               string msg = "Document Type: " + mrzResult.docType;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Document ID: " + mrzResult.docId;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Surname: " + mrzResult.surname;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Given Name: " + mrzResult.givenname;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Nationality: " + mrzResult.nationality;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Issuing Country or Organization: " + mrzResult.issuer;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Gender: " + mrzResult.gender;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Date of Birth(YYMMDD): " + mrzResult.dateOfBirth;
               drawText(frame, msg.c_str(), x, y);
               y += 20;
               msg = "Expiration Date(YYMMDD): " + mrzResult.dateOfExpiry;
    
               if (captured)
               {
                 captured = false;
                 imshow("Captured Frame", frame);
               }
             }
           }
         }
    
         cv::putText(frame, "Press 'ESC' to quit. Press 'C' to capture.",
               cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX,
               0.5, cv::Scalar(0, 255, 0), 2);
    
         imshow("MRZ Scanner", frame);
         int key = waitKey(1);
         if (key == 27 /*ESC*/)
           break;
         else if (key == char('c'))
         {
           captured = true;
         }
       }
       cvr->StopCapturing(false, true);
     }
    
     delete cvr, cvr = NULL;
     delete fetcher, fetcher = NULL;
     delete capturedReceiver, capturedReceiver = NULL;
    
     return 0;
    

Building and Running the MRZ Scanner on Windows and Linux

mkdir build
cd build
cmake ..
cmake --build .

desktop mrz recognition in C++

Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/examples/10.x/mrz