In the digital era, extracting information from documents and vehicle IDs has become increasingly important for web-based applications. Machine Readable Zones (MRZ) and Vehicle Identification Numbers (VIN) can now be scanned directly in the browser using modern web technologies. In this tutorial, you'll learn how to build a web-based MRZ and VIN scanner using JavaScript, HTML5 and Dynamsoft Capture Vision SDK.

Web MRZ/VIN Scanner Demo Video

Prerequisites

Online Demo

Try it here

Installation

The dynamsoft-capture-vision-bundle is the JavaScript version of Dynamsoft Capture Vision, available via npm or CDN. To use it, include the library in your index.html:

<span class="na">src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dcv.bundle.min.js">

Setting Up the HTML Structure

The target HTML layout for the MRZ/VIN scanner consists of three main sections:

  • A div element for license key setup, input source selection (File or Camera), and scanning mode toggle (MRZ or VIN).

    class="container">
    class="row">
    Get a License key from href="https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform" target="_blank">here type="text" id="license_key" value="LICENSE-KEY" placeholder="LICENSE-KEY">
    class="row">
    id="modeSelector"> type="radio" name="scanMode" value="mrz" checked> MRZ type="radio" name="scanMode" value="vin"> VIN
  • A div for displaying the uploaded image and its scanning result.

    class="container" id="file_container">
    type="file" id="pick_file" accept="image/*" />
    class="row">
    class="imageview"> id="image_file" src="default.png" /> id="overlay_canvas" class="overlay">
    class="row">
  • A dev for showing the live camera stream along with real-time scanning results.

    class="container" id="camera_container">
    id="videoview">
    id="camera_view">
    class="row">

Initializing MRZ and VIN Recognition Engines

The recognition engines for MRZ and VIN are initialized in the activate() function, which is triggered when the user clicks the Activate SDK button. This function sets up the license key, loads the required models and code parsers, and registers result receivers for both MRZ and VIN.

async function activate() {
    toggleLoading(true);
    let divElement = document.getElementById("license_key");
    let licenseKey = divElement.value == "" ? divElement.placeholder : divElement.value;

    try {
        await Dynamsoft.License.LicenseManager.initLicense(
            licenseKey,
            true
        );

        Dynamsoft.Core.CoreModule.loadWasm(["DLR"]);

        parser = await Dynamsoft.DCP.CodeParser.createInstance();

        // Load VIN and MRZ models
        await Dynamsoft.DCP.CodeParserModule.loadSpec("VIN");
        await Dynamsoft.DLR.LabelRecognizerModule.loadRecognitionData("VIN");

        await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD1_ID");
        await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID");
        await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_ID");
        await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_VISA");
        await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_PASSPORT");
        await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_VISA");
        await Dynamsoft.DLR.LabelRecognizerModule.loadRecognitionData("MRZ");

        mrzRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
        await mrzRouter.initSettings("./mrz.json");
        mrzRouter.addResultReceiver({
            onCapturedResultReceived: (result) => {
                // TODO: Handle MRZ result
            },
        });
        vinRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
        await vinRouter.initSettings("./vin.json");
        vinRouter.addResultReceiver({
            onCapturedResultReceived: (result) => {
                // TODO: Handle MRZ result
            },
        });

        isSDKReady = true;
    }
    catch (ex) {
        console.error(ex);
    }

    toggleLoading(false);
}

Accessing the Camera

Dynamsoft Capture Vision SDK provides the CameraEnhancer and CameraView classes for managing camera access and display. CameraEnhancer wraps the getUserMedia() method, while CameraView class adds a live video view to the DOM.

async function openCamera(cameraEnhancer, cameraInfo) {
    if (!Dynamsoft) return;

    try {
        await cameraEnhancer.selectCamera(cameraInfo);
        cameraEnhancer.on("played", function () {
            resolution = cameraEnhancer.getResolution();
        });
        await cameraEnhancer.open();
    }
    catch (ex) {
        console.error(ex);
    }
}

async function closeCamera(cameraEnhancer) {
    if (!Dynamsoft) return;

    try {
        await cameraEnhancer.close();
    }
    catch (ex) {
        console.error(ex);
    }
}

async function setResolution(cameraEnhancer, width, height) {
    if (!Dynamsoft) return;

    try {
        await cameraEnhancer.setResolution(width, height);
    }
    catch (ex) {
        console.error(ex);
    }
}

async function initCamera() {
    if (!Dynamsoft) return;

    try {
        cameraView = await Dynamsoft.DCE.CameraView.createInstance();
        cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView);
        let scanRegion = {
            x: 10,
            y: 30,
            width: 80,
            height: 40,
            isMeasuredInPercentage: true
        };
        cameraEnhancer.setScanRegion(scanRegion);

        cameras = await cameraEnhancer.getAllCameras();
        if (cameras != null && cameras.length > 0) {
            for (let i = 0; i < cameras.length; i++) {
                let option = document.createElement("option");
                option.text = cameras[i].label;
                cameraSource.add(option);
            }

            try {
                let uiElement = document.getElementById("camera_view");
                uiElement.append(cameraView.getUIElement());

                cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-camera')?.setAttribute('style', 'display: none');
                cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-resolution')?.setAttribute('style', 'display: none');
            }
            catch (ex) {
                console.error(ex);
            }

        }

        else {
            alert("No camera found.");
        }
    }
    catch (ex) {
        console.error(ex);
    }
}

async function cameraChanged() {
    if (cameras != null && cameras.length > 0) {
        let index = cameraSource.selectedIndex;
        await openCamera(cameraEnhancer, cameras[index]);
    }
}

Implementing the MRZ/VIN Scanner Logic

To recognize MRZ and VIN from images or camera streams, use the capture() and startCapturing() methods respectively. The capture() method returns the recognition results directly, while the startCapturing() method starts a continuous capturing process and returns the results through the onCapturedResultReceived callback.

  • Recognizing MRZ/VIN from Image Files

    function loadImage2Canvas(base64Image) {
        imageFile.src = base64Image;
        img.src = base64Image;
        img.onload = function () {
            let width = img.width;
            let height = img.height;
    
            overlayCanvas.width = width;
            overlayCanvas.height = height;
    
            if (!isSDKReady) {
                alert("Please activate the SDK first.");
                return;
            }
            toggleLoading(true);
    
            let selectedMode = document.querySelector('input[name="scanMode"]:checked').value;
    
            let context = overlayCanvas.getContext('2d');
            context.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
            try {
                if (selectedMode == "mrz") {
                    mrzRouter.capture(img.src, "ReadMRZ").then((result) => {
                        showFileResult(selectedMode, context, result);
                    });
                }
                else if (selectedMode == "vin") {
    
                    vinRouter.capture(img.src, "ReadVINText").then((result) => {
                        showFileResult(selectedMode, context, result);
                    });
                }
            }
            catch (ex) {
                console.error(ex);
            }
    
            toggleLoading(false);
        };
    
    }
    
  • Recognizing MRZ/VIN from Camera Stream

    async function scan() {
        if (!isSDKReady) {
            alert("Please activate the SDK first.");
            return;
        }
    
        let selectedMode = document.querySelector('input[name="scanMode"]:checked').value;
    
        if (!isDetecting) {
            scanButton.innerHTML = "Stop";
            isDetecting = true;
    
            if (selectedMode == "mrz") {
                mrzRouter.setInput(cameraEnhancer);
                mrzRouter.startCapturing("ReadMRZ");
            }
            else if (selectedMode == "vin") {
                vinRouter.setInput(cameraEnhancer);
                vinRouter.startCapturing("ReadVINText");
            }
        }
        else {
            scanButton.innerHTML = "Scan";
            isDetecting = false;
    
            if (selectedMode == "mrz") {
                mrzRouter.stopCapturing();
            }
            else if (selectedMode == "vin") {
                vinRouter.stopCapturing();
            }
    
            cameraView.clearAllInnerDrawingItems();
        }
    }
    

Drawing Overlays and Results

You can draw overlays on the image or video stream to highlight recognized areas and display the parsed results in a text area.

  • On Image Files

    async function showFileResult(selectedMode, context, result) {
        let parseResults = '';
        let detection_result = document.getElementById('detection_result');
        detection_result.innerHTML = "";
        let txts = [];
        let items = result.items;
        if (items.length > 0) {
            for (var i = 0; i < items.length; ++i) {
    
                if (items[i].type !== Dynamsoft.Core.EnumCapturedResultItemType.CRIT_TEXT_LINE) {
                    continue;
                }
    
                let item = items[i];
                parseResults = await parser.parse(item.text);
    
                txts.push(item.text);
                localization = item.location;
    
                context.strokeStyle = '#ff0000';
                context.lineWidth = 2;
    
                let points = localization.points;
    
                context.beginPath();
                context.moveTo(points[0].x, points[0].y);
                context.lineTo(points[1].x, points[1].y);
                context.lineTo(points[2].x, points[2].y);
                context.lineTo(points[3].x, points[3].y);
                context.closePath();
                context.stroke();
            }
        }
    
        if (txts.length > 0) {
            detection_result.innerHTML += txts.join('\n') + '\n\n';
            if (selectedMode == "mrz") {
                detection_result.innerHTML += JSON.stringify(extractMrzInfo(parseResults));
            }
            else if (selectedMode == "vin") {
    
                detection_result.innerHTML += JSON.stringify(extractVinInfo(parseResults));
            }
    
        }
        else {
            detection_result.innerHTML += "Recognition Failed\n";
        }
    }
    
  • On Camera Stream

    async function showCameraResult(result) {
        let selectedMode = document.querySelector('input[name="scanMode"]:checked').value;
        let items = result.items;
        let scan_result = document.getElementById('scan_result');
    
        if (items != null && items.length > 0) {
            let item = items[0];
            let parseResults = await parser.parse(item.text);
    
            if (selectedMode == "mrz") {
                scan_result.innerHTML = JSON.stringify(extractMrzInfo(parseResults));
            }
            else if (selectedMode == "vin") {
    
                scan_result.innerHTML = JSON.stringify(extractVinInfo(parseResults));
            }
        }
    }
    

Running the Web MRZ/VIN Scanner

  1. Open your terminal and navigate to the project directory.
  2. Start a local server using Python:

    python -m http.server 8000
    
  3. Open your web browser and navigate to http://localhost:8000.

    Web MRZ VIN scanner

Source Code

https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/mrz-vin-scanner