Effortlessly Recognize US Driver's Licenses in Android Apps

Xiao Ling - Aug 30 - - Dev Community

According to the American Association of Motor Vehicle Administrators (AAMVA) specification, US driver's licenses store personal information using the PDF417 barcode symbology. The combination of Dynamsoft Barcode Reader and Dynamsoft Code Parser can recognize PDF417 barcodes and extract driver's license data. This article will guide you through integrating driver's license recognition into Android apps using both solutions.

Recognizing Driver's Licenses on Android

Prerequisites

Sample Driver's License Image

Below is a sample image of a driver's license for testing purposes:

driver's license

Reading Driver's License with Dynamsoft Barcode Reader and Dynamsoft Code Parser

In this section, you'll learn how to implement driver's license recognition step by step using the Dynamsoft Barcode Reader and Dynamsoft Code Parser.

Android Project Configuration

  1. In your project's build.gradle file, include the Dynamsoft Maven repository:

    allprojects {
        repositories {
            google()
            mavenCentral()
            maven { url "https://download2.dynamsoft.com/maven/aar" }
        }
    }
    
  2. In the module's build.gradle file, add the following dependencies:

    implementation "com.dynamsoft:dynamsoftbarcodereaderbundle:10.2.1100"
    implementation "com.dynamsoft:dynamsoftcodeparser:2.2.11"
    implementation "com.dynamsoft:dynamsoftcodeparserdedicator:1.2.20"
    

    Explanation

    • The dynamsoftbarcodereaderbundle: Provides camera control and barcode decoding functionalities.
    • The dynamsoftcodeparser and dynamsoftcodeparserdedicator: Used for parsing PDF417 barcodes and extracting driver's license information.

Activating Dynamsoft Barcode Reader

In the MainActivity.java file, initialize the license key as follows:

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()));
                }
            });
        }
        ...
}
Enter fullscreen mode Exit fullscreen mode

Creating a Fragment for Barcode Scanning

  1. Create a new layout file named fragment_scanner.xml in the layout folder:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 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"
        tools:context=".fragments.ScannerFragment">
    
        <com.dynamsoft.dce.CameraView
            android:id="@+id/camera_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <TextView
            android:id="@+id/tv_parsed"
            android:layout_margin="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            app:layout_constraintBottom_toBottomOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    The layout includes a CameraView for displaying the camera preview and a TextView showing parsed messages.

  2. Place a drivers-license.json template file in the assets folder. This template defines how to decode the PDF417 barcode and extract driver's license information. Below is an example of the template:

    {
        "BarcodeFormatSpecificationOptions" : 
        [
            {
                "Name" : "bfs_pdf_417",
                "BarcodeBytesLengthRangeArray" : 
                [
                    {
                        "MaxValue" : 2147483647,
                        "MinValue" : 0
                    }
                ],
                "BarcodeFormatIds" : 
                [
                    "BF_DEFAULT",
                    "BF_POSTALCODE",
                    "BF_PHARMACODE",
                    "BF_NONSTANDARD_BARCODE",
                    "BF_DOTCODE"
                ],
                "BarcodeHeightRangeArray" : null,
                "BarcodeTextLengthRangeArray" : 
                [
                    {
                        "MaxValue" : 2147483647,
                        "MinValue" : 0
                    }
                ],
                "MinResultConfidence" : 30,
                "MirrorMode" : "MM_NORMAL",
                "PartitionModes" : 
                [
                    "PM_WHOLE_BARCODE",
                    "PM_ALIGNMENT_PARTITION"
                ]
            }
        ],
        "BarcodeReaderTaskSettingOptions" : 
        [
            {
                "Name" : "pdf_417_task",
                "BarcodeColourModes" : 
                [
                    {
                        "LightReflection" : 1,
                        "Mode" : "BICM_DARK_ON_LIGHT"
                    }
                ],
                "BarcodeFormatSpecificationNameArray" : 
                [
                    "bfs_pdf_417"
                ],
                "DeblurModes" : null,
                "LocalizationModes" : 
                [
                    {
                        "Mode" : "LM_CONNECTED_BLOCKS"
                    },
                    {
                        "Mode" : "LM_LINES"
                    },
                    {
                        "Mode" : "LM_STATISTICS"
                    }
                ],
                "MaxThreadsInOneTask" : 1,
                "SectionImageParameterArray" : 
                [
                    {
                        "ContinueWhenPartialResultsGenerated" : 1,
                        "ImageParameterName" : "ip_localize_barcode",
                        "Section" : "ST_REGION_PREDETECTION"
                    },
                    {
                        "ContinueWhenPartialResultsGenerated" : 1,
                        "ImageParameterName" : "ip_localize_barcode",
                        "Section" : "ST_BARCODE_LOCALIZATION"
                    },
                    {
                        "ContinueWhenPartialResultsGenerated" : 1,
                        "ImageParameterName" : "ip_decode_barcode",
                        "Section" : "ST_BARCODE_DECODING"
                    }
                ]
            }
        ],
        "CaptureVisionTemplates" : 
        [
            {
                "Name" : "ReadPDF417",
                "ImageROIProcessingNameArray" : 
                [
                    "roi_pdf_417"
                ],
                "ImageSource" : "",
                "MaxParallelTasks" : 4,
                "MinImageCaptureInterval" : 0,
                "OutputOriginalImage" : 0,
                "SemanticProcessingNameArray": [ "sp_pdf_417" ],
                "Timeout" : 10000
            }
        ],
        "GlobalParameter" : 
        {
            "MaxTotalImageDimension" : 0
        },
        "ImageParameterOptions" : 
        [
            {
                "Name" : "ip_localize_barcode",
                "BinarizationModes" : 
                [
                    {
                        "BinarizationThreshold" : -1,
                        "BlockSizeX" : 71,
                        "BlockSizeY" : 71,
                        "EnableFillBinaryVacancy" : 0,
                        "GrayscaleEnhancementModesIndex" : -1,
                        "Mode" : "BM_LOCAL_BLOCK",
                        "ThresholdCompensation" : 10
                    }
                ],
                "GrayscaleEnhancementModes" : 
                [
                    {
                        "Mode" : "GEM_GENERAL"
                    }
                ]
            },
            {
                "Name" : "ip_decode_barcode",
                "ScaleDownThreshold" : 99999
            },
            {
                "Name": "ip_recognize_text",
                "TextDetectionMode": {
                    "Mode": "TTDM_LINE",
                    "Direction": "HORIZONTAL",
                    "CharHeightRange": [
                        20,
                        1000,
                        1
                    ],
                    "Sensitivity": 7
                }
            }
        ],
        "TargetROIDefOptions" : 
        [
            {
                "Name" : "roi_pdf_417",
                "TaskSettingNameArray" : 
                [
                    "pdf_417_task"
                ]
            }
        ],
        "CharacterModelOptions": [
            {
                "Name" : "NumberLetter"
            }
        ],
        "SemanticProcessingOptions": [
            {
                "Name": "sp_pdf_417",
                "ReferenceObjectFilter": {
                    "ReferenceTargetROIDefNameArray": [
                        "roi_pdf_417"
                    ]
                },
                "TaskSettingNameArray": [
                    "dcp_pdf_417"
                ]
            }
        ],
        "CodeParserTaskSettingOptions": [
            {
                "Name": "dcp_pdf_417",
                "CodeSpecifications": ["AAMVA_DL_ID","AAMVA_DL_ID_WITH_MAG_STRIPE","SOUTH_AFRICA_DL"]
            }
        ]
    }
    
    
  3. Inflate the layout and set up the barcode scanning in the ScannerFragment.java file:

    package com.dynamsoft.dcv.driverslicensescanner.fragments;
    
    import android.app.AlertDialog;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    import androidx.annotation.NonNull;
    import androidx.fragment.app.Fragment;
    import androidx.lifecycle.ViewModelProvider;
    import androidx.navigation.fragment.NavHostFragment;
    
    import com.dynamsoft.core.basic_structures.CompletionListener;
    import com.dynamsoft.cvr.CaptureVisionRouter;
    import com.dynamsoft.cvr.CaptureVisionRouterException;
    import com.dynamsoft.cvr.CapturedResultReceiver;
    import com.dynamsoft.dbr.DecodedBarcodesResult;
    import com.dynamsoft.dce.CameraEnhancer;
    import com.dynamsoft.dce.CameraEnhancerException;
    import com.dynamsoft.dcp.ParsedResult;
    import com.dynamsoft.dcv.driverslicensescanner.FileUtil;
    import com.dynamsoft.dcv.driverslicensescanner.MainViewModel;
    import com.dynamsoft.dcv.driverslicensescanner.ParseUtil;
    import com.dynamsoft.dcv.driverslicensescanner.R;
    import com.dynamsoft.dcv.driverslicensescanner.databinding.FragmentScannerBinding;
    
    import java.util.Locale;
    
    public class ScannerFragment extends Fragment {
        private static final String TEMPLATE_ASSETS_FILE_NAME = "drivers-license.json";
        private static final String TEMPLATE_READ_PDF417 = "ReadPDF417";
        private FragmentScannerBinding binding;
        private CameraEnhancer mCamera;
        private CaptureVisionRouter mRouter;
        private MainViewModel viewModel;
    
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            binding = FragmentScannerBinding.inflate(inflater, container, false);
            viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
            viewModel.reset();
            mCamera = new CameraEnhancer(binding.cameraView, getViewLifecycleOwner());
            if (mRouter == null) {
                initCaptureVisionRouter();
            }
            try {
                mRouter.setInput(mCamera);
            } catch (CaptureVisionRouterException e) {
                e.printStackTrace();
            }
            return binding.getRoot();
        }
    
        @Override
        public void onResume() {
            super.onResume();
            try {
                mCamera.open();
            } catch (CameraEnhancerException e) {
                e.printStackTrace();
            }
            mRouter.startCapturing(TEMPLATE_READ_PDF417, new CompletionListener() {
    
                @Override
                public void onSuccess() {
    
                }
    
                @Override
                public void onFailure(int errorCode, String errorString) {
                    requireActivity().runOnUiThread(() ->
                            showDialog("Error", String.format(Locale.getDefault(), "ErrorCode: %d %nErrorMessage: %s", errorCode, errorString)));
                }
            });
        }
    
        @Override
        public void onPause() {
            super.onPause();
            try {
                mCamera.close();
            } catch (CameraEnhancerException e) {
                e.printStackTrace();
            }
            mRouter.stopCapturing();
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            binding = null;
        }
    
        private void initCaptureVisionRouter() {
            mRouter = new CaptureVisionRouter(requireContext());
            try {
                String template = FileUtil.readAssetFileToString(requireContext(), TEMPLATE_ASSETS_FILE_NAME);
                mRouter.initSettings(template);
            } catch (CaptureVisionRouterException e) {
                e.printStackTrace();
            }
            mRouter.addResultReceiver(new CapturedResultReceiver() {
                @Override
                public void onDecodedBarcodesReceived(DecodedBarcodesResult result) {
                    if (result.getItems().length > 0) {
                        viewModel.parsedText = result.getItems()[0].getText();
                    }
                }
    
                @Override
                public void onParsedResultsReceived(ParsedResult result) {
                    if (result.getItems().length > 0) {
                        String[] displayStrings = ParseUtil.parsedItemToDisplayStrings(result.getItems()[0]);
                        if (displayStrings == null || displayStrings.length <= 1/*Only have Document Type content*/) {
                            showParsedText();
                            return;
                        }
                        viewModel.results = displayStrings;
                        requireActivity().runOnUiThread(() -> NavHostFragment.findNavController(ScannerFragment.this)
                                .navigate(R.id.action_ScannerFragment_to_ResultFragment));
                        mRouter.stopCapturing();
                    } else {
                        showParsedText();
                    }
                }
            });
        }
    
        private void showParsedText() {
            if (viewModel.parsedText != null && !viewModel.parsedText.isEmpty()) {
                requireActivity().runOnUiThread(() -> {
                    if (binding != null) {
                        binding.tvParsed.setText("Failed to parse the result. The drivers' information does not exist in the barcode! ");
                    }
                });
            }
        }
    
        private void showDialog(String title, String message) {
            new AlertDialog.Builder(requireContext())
                    .setCancelable(true)
                    .setPositiveButton("OK", null)
                    .setTitle(title)
                    .setMessage(message)
                    .show();
        }
    }
    

    Explanation

    • CameraEnhancer: Manages the camera preview.
    • CaptureVisionRouter: Handles the capture process, providing callbacks for decoded barcodes and parsed results.
    • initSettings: Loads the template file from the assets.
    • ParsedResult: Stores the parsed result.
    • viewModel: Shares data between fragments.

Creating a Fragment for Displaying Driver's License Information

  1. Create a layout file named fragment_result.xml containing a ListView to display the driver's license information:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 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"
        tools:context=".fragments.ResultFragment">
    
        <ListView
            android:id="@+id/lv_result"
            android:padding="10dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. Inflate the layout and load data from viewModel in the ResultFragment.java file:

    package com.dynamsoft.dcv.driverslicensescanner.fragments;
    
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    
    import androidx.annotation.NonNull;
    import androidx.fragment.app.Fragment;
    import androidx.lifecycle.ViewModelProvider;
    
    import com.dynamsoft.dcv.driverslicensescanner.MainViewModel;
    import com.dynamsoft.dcv.driverslicensescanner.databinding.FragmentResultBinding;
    
    public class ResultFragment extends Fragment {
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FragmentResultBinding binding = FragmentResultBinding.inflate(inflater, container, false);
            MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
            ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, viewModel.results);
            binding.lvResult.setAdapter(adapter);
            return binding.getRoot();
        }
    
    }
    

    Dynamsoft driver's license

Source Code

https://github.com/yushulx/android-camera-barcode-mrz-document-scanner/tree/main/examples/10.x/driver_license

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player