MRZ Recognition on Android Using Dynamsoft MRZ Scanner SDK
Machine Readable Zone (MRZ) recognition is a critical feature in applications that require scanning and processing identity documents, such as passports and visas. On Android devices, implementing accurate MRZ recognition can significantly enhance the efficiency and user experience of such apps. In this article, we will explore how to use the Dynamsoft MRZ Scanner SDK to integrate MRZ recognition into your Android application. The SDK provides powerful OCR capabilities specifically tailored for reading MRZ data, allowing you to capture and decode information quickly and reliably, making your app more robust and user-friendly.
This article is Part 2 in a 4-Part Series.
- Part 1 - How to Create a Flutter Plugin for Passport MRZ Scan on Windows, Linux, Android, iOS, and Web
- Part 2 - MRZ Recognition on Android Using Dynamsoft MRZ Scanner SDK
- Part 3 - Developing a Desktop MRZ Scanner for Passports, IDs, and Visas with Dynamsoft C++ Capture Vision SDK
- Part 4 - How to Build a Web MRZ Scanner and Reader with HTML5 and JavaScript
Demo Video: Android MRZ Scanner
Prerequisites
Sample Passport Image with MRZ
Below is a sample MRZ image for testing:

Implementing MRZ Recognition on Android
Step 1: Configure Your Android Project with Dynamsoft Label Recognizer SDK
-
In your project’s root
build.gradlefile, add the Dynamsoft Maven repository:allprojects { repositories { google() mavenCentral() maven { url "https://download2.dynamsoft.com/maven/aar" } } } -
In the module’s
build.gradlefile, include the necessary dependencies:implementation 'com.dynamsoft:mrzscannerbundle:3.0.0'
Step 2: Activate Dynamsoft MRZ Scanner SDK
In your MainActivity.java file, set up the license key for the SDK as shown below:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
LicenseManager.initLicense("LICENSE-KEY", this, (isSuccessful, error) -> {
if (!isSuccessful) {
error.printStackTrace();
runOnUiThread(() -> ((TextView) findViewById(R.id.tv_license_error)).setText("License initialization failed: "+error.getMessage()));
}
});
}
...
}
This setup initializes the SDK and checks for any licensing errors, ensuring the app is ready for MRZ recognition.
Step 3: Create a Camera Preview for MRZ Recognition
-
Add a new layout file named
activity_scan.xmlin thelayoutfolder:<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.dynamsoft.dce.CameraView android:id="@+id/dce_camera_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:layout_above="@+id/tv_result" android:id="@+id/tv_message" android:layout_marginHorizontal="24dp" android:layout_centerHorizontal="true" android:layout_marginBottom="80dp" android:textColor="@color/red00" android:textSize="16sp" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="70dp" android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>Explanation
CameraView: Displays the camera preview for capturing MRZ data.TextView: Displays messages such as license information and detection errors.
-
Inflate the layout and configure the camera preview in the
MainActivity.javafile:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scan); PermissionUtil.requestCameraPermission(this); LicenseManager.initLicense("LICENSE-KEY", this, (isSuccess, error) -> { if (!isSuccess) { runOnUiThread(() -> { ((TextView)findViewById(R.id.tv_message)) .setText("License initialization failed: "+error.getMessage()); }); error.printStackTrace(); } }); mCameraView = findViewById(R.id.dce_camera_view); mTextResult = findViewById(R.id.tv_result); mCamera = new CameraEnhancer(mCameraView, this); try { mCamera.enableEnhancedFeatures(EnumEnhancerFeatures.EF_FRAME_FILTER); } catch (CameraEnhancerException e) { throw new RuntimeException(e); } mRouter = new CaptureVisionRouter(this); MultiFrameResultCrossFilter filter = new MultiFrameResultCrossFilter(); filter.enableResultCrossVerification(EnumCapturedResultItemType.CRIT_TEXT_LINE, true); mRouter.addResultFilter(filter); try { mRouter.initSettingsFromFile("MRZScanner.json"); mRouter.setInput(mCamera); } catch (CaptureVisionRouterException e) { throw new RuntimeException(e); } } @Override protected void onResume() { super.onResume(); try { mCamera.open(); } catch (CameraEnhancerException e) { e.printStackTrace(); } mRouter.startCapturing("ReadMRZ", new CompletionListener() { @Override public void onSuccess() { } @Override public void onFailure(int errorCode, String errorString) { runOnUiThread(() -> showDialog("Error", String.format(Locale.getDefault(), "ErrorCode: %d %nErrorMessage: %s", errorCode, errorString))); } }); } @Override protected void onPause() { super.onPause(); succeed = false; try { mCamera.close(); } catch (CameraEnhancerException e) { e.printStackTrace(); } mRouter.stopCapturing(); } @Override protected void onStop() { mCameraView.getDrawingLayer(DrawingLayer.DLR_LAYER_ID).clearDrawingItems(); super.onStop(); } -
Register the callbacks for handling recognized MRZ data:
mRouter.addResultReceiver(new CapturedResultReceiver() { @Override // Implement this method to receive RecognizedTextLinesResult. public void onRecognizedTextLinesReceived(@NonNull RecognizedTextLinesResult result) { onLabelTextReceived(result); } @Override public void onParsedResultsReceived(@NonNull ParsedResult result) { if (!succeed) { onParsedResultReceived(result); } } });The
ParsedResultobject contains the MRZ data extracted from the captured image, formatted as key-value pairs.
-
Bind the parsed MRZ data to an
Intentand start a new activity to display the results:private void onParsedResultReceived(ParsedResult result) { if (result.getItems() == null) { return; } if (result.getItems().length == 0) { runOnUiThread(() -> { if (!mText.isEmpty()) { String errorMsg = "error: Failed to parse the content. The MRZ text is " + mText; mTextResult.setText(errorMsg); } }); } else { HashMap<String, String> labelMap = assembleMap(result.getItems()[0]); if (labelMap != null && !labelMap.isEmpty()) { succeed = true; Intent intent = new Intent(this, ResultActivity.class); intent.putExtra("labelMap", labelMap); startActivity(intent); runOnUiThread(() -> { mTextResult.setText(""); }); } else { runOnUiThread(() -> { if (!mText.isEmpty()) { String errorMsg = "error: Failed to parse the content. The MRZ text is " + mText; mTextResult.setText(errorMsg); } }); } } }
Step 4: Display the MRZ Data
-
Create a layout file named
activity_result.xmlin thelayoutfolder:<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/dy_black_3B" tools:context=".ResultActivity"> <RelativeLayout android:id="@+id/rl_title" android:layout_width="match_parent" android:layout_height="48dp" android:background="@color/dy_black_2B"> <ImageView android:id="@+id/iv_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginStart="16dp" android:background="@color/transparent" android:src="@drawable/ic_arrow_left" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toEndOf="@id/iv_back" android:text="Back" android:textColor="@color/white" android:textSize="17sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_gravity="center_horizontal" android:text="Result" android:textColor="@color/white" android:textSize="20sp" android:textStyle="bold" /> </RelativeLayout> <ScrollView android:layout_below="@id/rl_title" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/ll_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="20dp"> </LinearLayout> </ScrollView> </RelativeLayout> -
Inflate the layout and display the MRZ data in the
ResultActivity.javafile:package com.dynamsoft.mrzscanner; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import java.util.HashMap; public class ResultActivity extends AppCompatActivity { private LinearLayout content; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_result); findViewById(R.id.iv_back).setOnClickListener((v) -> { finish(); }); content = findViewById(R.id.ll_content); HashMap<String, String> properties = (HashMap<String, String>) getIntent(). getSerializableExtra("labelMap"); if (properties != null) { fillViews(properties); } } @NonNull private View childView(String label, String labelText) { LinearLayout layout = new LinearLayout(this); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(0, 30, 0, 0); layout.setLayoutParams(params); layout.setOrientation(LinearLayout.VERTICAL); TextView labelView = new TextView(this); labelView.setPadding(0, 30, 0, 0); labelView.setTextColor(ContextCompat.getColor(this, R.color.dy_grey_AA)); labelView.setTextSize(16); labelView.setText(label); TextView textView = new TextView(this); textView.setTextSize(16); textView.setTextColor(Color.WHITE); textView.setText(labelText); layout.addView(labelView); layout.addView(textView); return layout; } private void fillViews(HashMap<String, String> properties) { content.addView(childView("Document Type:", properties.get("Document Type"))); content.addView(childView("Document Number:", properties.get("Document Number"))); content.addView(childView("Name:", properties.get("Name"))); content.addView(childView("Issuing State:", properties.get("Issuing State"))); content.addView(childView("Nationality:", properties.get("Nationality"))); content.addView(childView("Date of Birth(YYYY-MM-DD):", properties.get("Date of Birth(YYYY-MM-DD)"))); content.addView(childView("Sex:", Character.toUpperCase(properties.get("Sex").charAt(0)) + properties.get("Sex").substring(1))); content.addView(childView("Date of Expiry(YYYY-MM-DD):", properties.get("Date of Expiry(YYYY-MM-DD)"))); } }
Source Code
https://github.com/yushulx/android-camera-barcode-mrz-document-scanner/tree/main/examples/10.x/mrz