How to Implement an Angular MRZ Recognition Module from Scratch

NPM Package

https://www.npmjs.com/package/ngx-mrz-sdk

Pre-requisites

npm install -g @angular/cli

Steps to Build an Angular MRZ Recognition Module

Scaffold an Angular Library Project

We use Angular CLI to create a new Angular project and add a library project to it:

ng new angular-mrz-scanner
cd angular-mrz-scanner
ng generate library ngx-mrz-sdk
ng build ngx-mrz-sdk --watch
ng serve --ssl
cd projects/ngx-mrz-sdk/src/lib

Install Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer

Add dynamsoft-label-recognizer and dynamsoft-camera-enhancer to peerDependencies in the package.json file:

"peerDependencies": {
"dynamsoft-camera-enhancer": "^3.0.1",
"dynamsoft-label-recognizer": "^2.2.11"
},

Configure License Key and Resource Path

The ngx-mrz-sdk.service.ts file exports a global accessible service, in which we can set the license key of Dynamsoft Label Recognizer and resource paths of Dynamsoft Camera Enhancer and Dynamsoft Label Recognizer.

import { Injectable, Optional } from '@angular/core';
import { LabelRecognizer } from 'dynamsoft-label-recognizer';
import { CameraEnhancer } from 'dynamsoft-camera-enhancer';

export class MrzSdkServiceConfig {
licenseKey = '';
dceResourcePath = '';
dlrResourcePath = '';
}

@Injectable({
providedIn: 'root'
})

export class NgxMrzSdkService {

constructor(@Optional() config?: MrzSdkServiceConfig) {
if (config) {
LabelRecognizer.license = config.licenseKey;
LabelRecognizer.engineResourcePath = config.dlrResourcePath;
CameraEnhancer.engineResourcePath = config.dceResourcePath;
}
}
}

Parse MRZ Data

As MRZ strings are recognized, we need to parse them and extract detailed information. Since Dynamsoft online demo has contained the parsing logic, we don’t need to re-invent the wheel. Create a parser.ts file containing the following code:

export class MrzParser {

static parseTwoLines(line1: string, line2: string): any {
let mrzInfo: any = {};
let type = line1.substring(0, 1);
if (!(/[I|P|V]/.test(type))) return false;
if (type === 'P') {
mrzInfo.type = 'PASSPORT (TD-3)';
}
...
return mrzInfo;
};

static parseThreeLines(line1: string, line2: string, line3: string): any {
let mrzInfo: any = {};
let type = line1.substring(0, 1);
if (!(/[I|P|V]/.test(type))) return false;
mrzInfo.type = 'ID CARD (TD-1)';
...
return mrzInfo;
}

}

Show Results on Overlay

To provider a better user experience, we can show the results on an overlay. Create an overlay.ts file to draw MRZ recognition results:

export class OverlayManager {
overlay: HTMLCanvasElement | undefined;
context: CanvasRenderingContext2D | undefined;

initOverlay(overlay: HTMLCanvasElement): void {
this.overlay = overlay;
this.context = this.overlay.getContext('2d') as CanvasRenderingContext2D;
}

updateOverlay(width: number, height: number): void {
if (this.overlay) {
this.overlay.width = width;
this.overlay.height = height;
this.clearOverlay();
}
}

clearOverlay(): void {
if (this.context && this.overlay) {
this.context.clearRect(0, 0, this.overlay.width, this.overlay.height);
this.context.strokeStyle = '#ff0000';
this.context.lineWidth = 5;
}
}

drawOverlay(points: any, text: any): void {
if (this.context) {
this.context.beginPath();
this.context.moveTo(points[0].x, points[0].y);
this.context.lineTo(points[1].x, points[1].y);
this.context.lineTo(points[2].x, points[2].y);
this.context.lineTo(points[3].x, points[3].y);
this.context.lineTo(points[0].x, points[0].y);
this.context.stroke();

this.context.font = '18px Verdana';
this.context.fillStyle = '#ff0000';
let x = [points[0].x, points[1].x, points[0].x, points[0].x];
let y = [points[0].y, points[1].y, points[0].y, points[0].y];
x.sort(function (a, b) {
return a - b;
});
y.sort(function (a, b) {
return b - a;
});
let left = x[0];
let top = y[0];

this.context.fillText(text, left, top);
}
}
}

Generate MRZ Reader and MRZ Scanner Components

We run the following commands to generate two components: ngx-mrz-reader and ngx-mrz-scanner:

ng generate component ngx-mrz-reader --skip-import
ng generate component ngx-mrz-scanner --skip-import
  • ngx-mrz-reader is used to scan MRZ from image files.
<span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span>
<br />

<input type="file" title="file" id="file" accept="image/*" (change)="onChange($event)" />

<div id="imageview">
<img id="image" alt=""/>
<canvas id="overlay"></canvas>
</div>
  • ngx-mrz-scanner is used to scan MRZ from camera.
<div id="mrz-scanner">
<span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span>
<br />

<div>
<label for="videoSource">Video Source</label>
<select id="videoSource" (change)="openCamera()"></select>
</div>

<div id="videoview">
<div class="dce-video-container" id="videoContainer"></div>
<canvas id="overlay"></canvas>
</div>
</div>
@Input() showOverlay: boolean;
@Output() result = new EventEmitter<any>();

constructor() {
this.showOverlay = true;
}
ngOnInit(): void {
this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
(async () => {
LabelRecognizer.onResourcesLoaded = (resourcePath) => {
this.isLoaded = true;
};
this.reader = await LabelRecognizer.createInstance();
await this.reader.updateRuntimeSettingsFromString("MRZ");

})();
}
this.reader.recognize(file).then((results: any) => {
let txts: any = [];
try {
if (results.length > 0) {
for (let result of results) {
for (let line of result.lineResults) {
txts.push(line.text);
if (this.showOverlay) this.overlayManager.drawOverlay(line.location.points, line.text);
}
}

let parsedResults = "";
if (txts.length == 2) {
parsedResults = MrzParser.parseTwoLines(txts[0], txts[1]);
}
else if (txts.length == 3) {
parsedResults = MrzParser.parseThreeLines(txts[0], txts[1], txts[2]);
}
this.result.emit([txts.join('\n'), parsedResults]);
} else {
this.result.emit(txts.join(''));
}
} catch (e) {
alert(e);
}
});
this.cameraEnhancer = await CameraEnhancer.createInstance();
let uiElement = document.getElementById('videoContainer');
if (uiElement) {
await this.cameraEnhancer.setUIElement(uiElement);
}

if (this.showOverlay) {
await this.scanner.setImageSource(this.cameraEnhancer, { resultsHighlightBaseShapes: DrawingItem });
}
else {
await this.scanner.setImageSource(this.cameraEnhancer, {});
}

this.scanner.onImageRead = (results: any) => {
this.overlayManager.clearOverlay();
let txts: any = [];
try {
if (results.length > 0) {
for (let result of results) {
for (let line of result.lineResults) {
txts.push(line.text);
if (this.showOverlay) this.overlayManager.drawOverlay(line.location.points, line.text);
}
}

let parsedResults = "";
if (txts.length == 2) {
parsedResults = MrzParser.parseTwoLines(txts[0], txts[1]);
}
else if (txts.length == 3) {
parsedResults = MrzParser.parseThreeLines(txts[0], txts[1], txts[2]);
}
this.result.emit([txts.join('\n'), parsedResults]);
} else {
this.result.emit(txts.join(''));
}
} catch (e) {
alert(e);
}
};
this.cameraEnhancer.on("played", (playCallBackInfo: any) => {
this.updateResolution();
});
await this.scanner.startScanning(true);

Configure *.module.ts

The ngx-mrz-sdk.module.ts file is used to export the Angular components and initialize the service.

import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { NgxMrzReaderComponent } from './ngx-mrz-reader/ngx-mrz-reader.component';
import { NgxMrzScannerComponent } from './ngx-mrz-scanner/ngx-mrz-scanner.component';
import { MrzSdkServiceConfig } from './ngx-mrz-sdk.service';

@NgModule({
declarations: [
NgxMrzReaderComponent,
NgxMrzScannerComponent
],
imports: [
],
exports: [
NgxMrzReaderComponent,
NgxMrzScannerComponent
]
})
export class NgxMrzSdkModule {
constructor(@Optional() @SkipSelf() parentModule?: NgxMrzSdkModule) {
if (parentModule) {
throw new Error(
'GreetingModule is already loaded. Import it in the AppModule only');
}
}

static forRoot(config: MrzSdkServiceConfig): ModuleWithProviders<NgxMrzSdkModule> {
return {
ngModule: NgxMrzSdkModule,
providers: [
{ provide: MrzSdkServiceConfig, useValue: config }
]
};
}
}

Test the Angular MRZ Recognition Module

  1. Install ngx-mrz-sdk package.
npm install ngx-mrz-sdk
ng generate component mrz-scanner
<div id="mrzResult">

</div>

<ngx-mrz-scanner
(result)="onResultReady($event)"
[showOverlay]="true"
></ngx-mrz-scanner>
import { Component, OnInit } from '@angular/core';
import { NgxMrzSdkService } from 'ngx-mrz-sdk';

@Component({
selector: 'app-mrz-scanner',
templateUrl: './mrz-scanner.component.html',
styleUrls: ['./mrz-scanner.component.css']
})
export class MrzScannerComponent implements OnInit {
mrzResult: string = '';
constructor(private mrzSdkService: NgxMrzSdkService) {
}

ngOnInit(): void {
}

// result = [originalValue, parsedValue]
onResultReady(result: any): void {
this.mrzResult = "";
for (let i in result[1]) {
this.mrzResult += i + ": " + result[1][i] + '\n';
}
// this.mrzResult = result[0];
}
}
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "./node_modules/dynamsoft-label-recognizer/dist",
"output": "assets/dynamsoft-label-recognizer"
},
{
"glob": "**/*",
"input": "./node_modules/dynamsoft-camera-enhancer/dist",
"output": "assets/dynamsoft-camera-enhancer"
}
],
ng serve

Source Code

https://github.com/yushulx/ngx-mrz-sdk

--

--

Manager of Dynamsoft Open Source Projects | Tech Lover

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store