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:
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
-
In your project's
build.gradle
file, include the Dynamsoft Maven repository:
allprojects { repositories { google() mavenCentral() maven { url "https://download2.dynamsoft.com/maven/aar" } } }
-
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
anddynamsoftcodeparserdedicator
: Used for parsing PDF417 barcodes and extracting driver's license information.
- The
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()));
}
});
}
...
}
Creating a Fragment for Barcode Scanning
-
Create a new layout file named
fragment_scanner.xml
in thelayout
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 aTextView
showing parsed messages. -
Place a
drivers-license.json
template file in theassets
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"] } ] }
-
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
-
Create a layout file named
fragment_result.xml
containing aListView
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>
-
Inflate the layout and load data from
viewModel
in theResultFragment.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(); } }