Click here to Skip to main content
15,886,137 members
Articles / Web Development / HTML5
Article

Mobile Barcode SDK for Recognizing US Driver’s License

6 Jul 2020CPOL4 min read 7.4K   3  
In this article, you will see how efficient to build mobile native and HTML5 apps to read driver’s license information with Dynamsoft barcode SDK version 7.4.
Here we look at downloading and installing Dynamsoft's mobile barcode SDK and decoding PDF417 and parsing driver’s license information.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Covid-19 is impacting the United States. The pandemic is speeding up the demand for mobile technologies. Dynamsoft mobile barcode SDK can power developers to build enterprise-class contactless solutions, such as using an existing virus tracing system to recognize people who have tested positive by scanning the PDF417 barcode on driver’s license. In this article, you will see how efficient to build mobile native and HTML5 apps to read driver’s license information with Dynamsoft barcode SDK version 7.4.

AAMVA DL/ID Card Design Standard

According to the card design standard of the American Association of Motor Vehicle Administrators (AAMVA), a valid driver’s license must include a PDF417 bar code symbology which is used for encoding driver’s license information.

Image 1

Both OCR (Optical Character Recognition) and barcode recognition technologies are capable of reading driver’s license information. By comparison, barcode recognition technology is much more accurate and efficient than OCR.

Mobile Barcode SDK Download and Installation

When speaking of mobile development, developers often hesitate to make the decision whether they should use a native SDK or an HTML5 SDK. Dynamsoft provides both SDKs for building mobile barcode apps.

Mobile Barcode SDK for Android

Add the URL http://download2.dynamsoft.com/maven/dbr/aar to <Your-Project>/build.gradle file:

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "http://download2.dynamsoft.com/maven/dbr/aar"
        }
    }
}

Import the Android barcode SDK to your project in <Your-Project>/app/build.gradle file:

dependencies {
    implementation 'com.dynamsoft:dynamsoftbarcodereader:7.4.0@aar'
}

Mobile Barcode SDK for iOS

Add the barcode SDK to your Podfile:

target 'DynamsoftBarcodeReaderDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

   #pod 'DynamsoftBarcodeReader'

end

Run the command ‘pod install’ to download the framework.

Add the following lines to the ‘Pods/Target Support Files/Pods-testOc/Pods-xxx.debug.xcconfig’ file:

FRAMEWORK_SEARCH_PATHS = "${SRCROOT}/Pods/DynamsoftBarcodeReader"
HEADER_SEARCH_PATHS = "${SRCROOT}/Pods/DynamsoftBarcodeReader/DynamsoftBarcodeReader.framework/Headers"

JavaScript Barcode SDK for Web

You can either download the JavaScript barcode SDK via:

npm install dynamsoft-javascript-barcode --save

or include the online JS file in your HTML file:

<script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@7.4.0-v1/dist/dbr.js"
data-productKeys="PRODUCT-KEYS"></script>

Decoding PDF417 and Parsing Driver’s License Information

Before getting started with the sample code, you need to get a free trial license from the Dynamsoft online portal.

Android Barcode Reader

Android camera programming is complicated. To save time, you can use a third-party library fotoapparat:

implementation 'io.fotoapparat.fotoapparat:library:2.3.1'

After that, you can get camera frames in the callback function process(Frame frame):

class CodeFrameProcesser implements FrameProcessor {
   @Override
   public void process(@NonNull Frame frame) {
      //isDetected = false;
      if (fotPreviewSize == null) {
         handler.sendEmptyMessage(0);
      }
      if (!detectStart && isCameraOpen) {
         detectStart = true;
         wid = frame.getSize().width;
         hgt = frame.getSize().height;
         Message message = decodeHandler.obtainMessage();
         message.obj = frame;
         decodeHandler.sendMessage(message);
      }
   }
}

The next step is to pass the frame data (NV21 format) to the decodeBuffer() method:

Frame frame = (Frame) msg.obj;
PointResult pointResult = new PointResult();
pointResult.textResults = reader.decodeBuffer(frame.getImage(), frame.getSize().width, frame.getSize().height, frame.getSize().width, EnumImagePixelFormat.IPF_NV21, "");

Although Dynamsoft Barcode Reader does not have a built-in driver’s license class yet, we can create a custom class by referencing AAMVA standard.

Create a new class named DriverLicense:

public class DriverLicense implements Parcelable {
    public String documentType;
    public String firstName;
    public String middleName;
    public String lastName;
    public String gender;
    public String addressStreet;
    public String addressCity;
    public String addressState;
    public String addressZip;
    public String licenseNumber;
    public String issueDate;
    public String expiryDate;
    public String birthDate;
    public String issuingCountry;
 
    public DriverLicense() {
 
    }
    protected DriverLicense(Parcel in) {
       documentType = in.readString();
       firstName = in.readString();
       middleName = in.readString();
       lastName = in.readString();
       gender = in.readString();
       addressStreet = in.readString();
       addressCity = in.readString();
       addressState = in.readString();
       addressZip = in.readString();
       licenseNumber = in.readString();
       issueDate = in.readString();
       expiryDate = in.readString();
       birthDate = in.readString();
       issuingCountry = in.readString();
    }
 
    public static final Creator<DriverLicense> CREATOR = new Creator<DriverLicense>() {
        @Override
        public DriverLicense createFromParcel(Parcel in) {
            return new DriverLicense(in);
        }
 
        @Override
        public DriverLicense[] newArray(int size) {
            return new DriverLicense[size];
        }
    };
 
    @Override
    public int describeContents() {
        return 0;
    }
 
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(documentType);
        parcel.writeString(firstName);
        parcel.writeString(middleName);
        parcel.writeString(lastName);
        parcel.writeString(gender);
        parcel.writeString(addressStreet);
        parcel.writeString(addressCity);
        parcel.writeString(addressState);
        parcel.writeString(addressZip);
        parcel.writeString(licenseNumber);
        parcel.writeString(issueDate);
        parcel.writeString(expiryDate);
        parcel.writeString(birthDate);
        parcel.writeString(issuingCountry);
    }
}

Define some common element IDs in DBRDriverLicenseUtil class:

public static final String CITY = "DAI";
public static final String STATE = "DAJ";
public static final String STREET = "DAG";
public static final String ZIP = "DAK";
public static final String BIRTH_DATE = "DBB";
public static final String EXPIRY_DATE = "DBA";
public static final String FIRST_NAME = "DAC";
public static final String GENDER = "DBC";
public static final String ISSUE_DATE = "DBD";
public static final String ISSUING_COUNTRY = "DCG";
public static final String LAST_NAME = "DCS";
public static final String LICENSE_NUMBER = "DAQ";
public static final String MIDDLE_NAME = "DAD";

To cover all mandatory data elements, you can refer to the chapter D.12.5.1 Minimum mandatory data elements in the 2016 Card Design Standard.

Once you get the string result, first you need to check whether it is a valid driver’s license:

    if (barcodeText == null || barcodeText.length() < 21) {
        return false;
    }
    String str = barcodeText.trim().replace("\r", "\n");
    String[] strArray = str.split("\n");
    ArrayList<String> strList = new ArrayList<>();
    for (int i = 0; i < strArray.length; i++) {
        if (strArray[i].length() != 0) {
            strList.add(strArray[i]);
        }
    }
    if (strList.get(0).equals("@")) {
        byte[] data = strList.get(2).getBytes();
        if (((data[0] == 'A' && data[1] == 'N' && data[2] == 'S' && data[3] == 'I' && data[4] == ' ') || (data[0] == 'A' && data[1] == 'A' && data[2] == 'M' && data[3] == 'V' && data[4] == 'A'))
                && (data[5] >= '0' && data[5] <= '9') && (data[6] >= '0' && data[6] <= '9') && (data[7] >= '0' && data[7] <= '9')
                && (data[8] >= '0' && data[8] <= '9') && (data[9] >= '0' && data[9] <= '9') && (data[10] >= '0' && data[10] <= '9')
                && (data[11] >= '0' && data[11] <= '9') && (data[12] >= '0' && data[12] <= '9')
                && (data[13] >= '0' && data[13] <= '9') && (data[14] >= '0' && data[14] <= '9')
        ) {
            return true;
        }
    }
    return false;
}

If it is a driver’s license, then you can use the following function to parse and extract driver’s license information from PDF417:

public static HashMap<String, String> readUSDriverLicense(String resultText) {
    HashMap<String, String> resultMap = new HashMap<String, String>();
    resultText = resultText.substring(resultText.indexOf("\n") + 1);
    int end = resultText.indexOf("\n");
    String firstLine = resultText.substring(0, end + 1);
    boolean findFirstLine = false;
    for (Map.Entry<String, String> entry : DRIVER_LICENSE_INFO.entrySet()) {
        try {
            int startIndex = resultText.indexOf("\n" + entry.getKey());
            if (startIndex != -1) {
                int endIndex = resultText.indexOf("\n", startIndex + entry.getKey().length() + 1);
                String value = resultText.substring(startIndex + entry.getKey().length() + 1, endIndex);
                resultMap.put(entry.getKey(), value);
            } else if (!findFirstLine) {
                int index = firstLine.indexOf(entry.getKey());
                if (index != -1) {
                    int endIndex = firstLine.indexOf("\n", entry.getKey().length() + 1);
                    String value = firstLine.substring(index + entry.getKey().length(), endIndex);
                    resultMap.put(entry.getKey(), value);
                    findFirstLine = true;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    return resultMap;
}

Here is the final look of our Android app for reading US driver’s licenses.

Image 2

iOS Barcode Reader

While coding for iOS, we can use AVFoundation to create a video capture session:

func setVideoSession()
    {
        do
        {
            inputDevice = self.getAvailableCamera()
            let tInputDevice = inputDevice!
            let captureInput = try? AVCaptureDeviceInput(device: tInputDevice)
            let captureOutput = AVCaptureVideoDataOutput.init()
            captureOutput.alwaysDiscardsLateVideoFrames = true
            var queue:DispatchQueue
            queue = DispatchQueue(label: "dbrCameraQueue")
            captureOutput.setSampleBufferDelegate(self as AVCaptureVideoDataOutputSampleBufferDelegate, queue: queue)
            
            // Enable continuous autofocus
            if(tInputDevice.isFocusModeSupported(AVCaptureDevice.FocusMode.continuousAutoFocus))
            {
                try tInputDevice.lockForConfiguration()
                tInputDevice.focusMode = AVCaptureDevice.FocusMode.continuousAutoFocus
                tInputDevice.unlockForConfiguration()
            }
            
            // Enable AutoFocusRangeRestriction
            if(tInputDevice.isAutoFocusRangeRestrictionSupported)
            {
                try tInputDevice.lockForConfiguration()
                tInputDevice.autoFocusRangeRestriction = AVCaptureDevice.AutoFocusRangeRestriction.near
                tInputDevice.unlockForConfiguration()
            }
            captureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey : kCVPixelFormatType_32BGRA] as [String : Any]
            
            if(captureInput == nil)
            {
                return
            }
            self.m_videoCaptureSession = AVCaptureSession.init()
            self.m_videoCaptureSession.addInput(captureInput!)
            self.m_videoCaptureSession.addOutput(captureOutput)
            
            if(self.m_videoCaptureSession.canSetSessionPreset(AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1920x1080"))){
                self.m_videoCaptureSession.sessionPreset = AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1920x1080")
            }
            else if(self.m_videoCaptureSession.canSetSessionPreset(AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1280x720"))){
                self.m_videoCaptureSession.sessionPreset = AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1280x720")
            }
            else if(self.m_videoCaptureSession.canSetSessionPreset(AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset640x480"))){
                self.m_videoCaptureSession.sessionPreset = AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset640x480")
            }
        }catch{
            print(error)
        }
    }

Instantiate a barcode reader object with a valid license key:

init(license:String)
    {
        super.init()
        barcodeReader = DynamsoftBarcodeReader(license: license)
        barcodeReader.initRuntimeSettings(with: "{\"ImageParameter\":{\"Name\":\"Balance\",\"DeblurLevel\":5,\"ExpectedBarcodesCount\":512,\"LocalizationModes\":[{\"Mode\":\"LM_CONNECTED_BLOCKS\"},{\"Mode\":\"LM_SCAN_DIRECTLY\"}]}}", conflictMode: EnumConflictMode.overwrite, error:nil)
        settings = try! barcodeReader.getRuntimeSettings()
        settings!.barcodeFormatIds = Int(EnumBarcodeFormat.ONED.rawValue) | Int(EnumBarcodeFormat.PDF417.rawValue) | Int(EnumBarcodeFormat.QRCODE.rawValue) | Int(EnumBarcodeFormat.DATAMATRIX.rawValue)
        settings!.barcodeFormatIds_2 = 0 //EnumBarcodeFormat2NULL
        barcodeReader.update(settings!, error: nil)
        self.parametersInit()
    }

To decode barcodes from camera real-time frames, you can invoke decodeBuffer() method in the captureOutput() callback function:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)
    {
        let imageBuffer:CVImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
        let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
        let bufferSize = CVPixelBufferGetDataSize(imageBuffer)
        let width = CVPixelBufferGetWidth(imageBuffer)
        let height = CVPixelBufferGetHeight(imageBuffer)
        let bpr = CVPixelBufferGetBytesPerRow(imageBuffer)
        CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
        startRecognitionDate = NSDate()
        let buffer = Data(bytes: baseAddress!, count: bufferSize)
        guard let results = try? barcodeReader.decodeBuffer(buffer, withWidth: width, height: height, stride: bpr, format: .ARGB_8888, templateName: "") else { return }
        DispatchQueue.main.async{
            self.m_recognitionReceiver?.perform(self.m_recognitionCallback!, with: results as NSArray)
        }
    }

Load driver’s license relevant element IDs from the driverLicenseFields.plist file:

public static func FindDLAbbreviation(text:String,withLineFeed:Bool,rltArray:inout [ScenarioDisplayInf])
    {
        let dicArray = NSArray(contentsOfFile: Bundle.main.path(forResource: "driverLicenseFields", ofType: "plist")!)!
        var rltArrayAll = [DBRDriverLicenseInfo]()
        for dict in dicArray
        {
            let setttingInf = DBRDriverLicenseInfo(dict: dict as! NSDictionary)
            rltArrayAll.append(setttingInf)
        }
        for item in rltArrayAll {
            let fieldValue = GetDriverLicenseField(brcdText:text, keyWord: item.abbreviation, withLineFeed: withLineFeed)
            if(fieldValue != nil)
            {
                rltArray.append(ScenarioDisplayInf(title: item.desc, value: fieldValue!))
            }
        }
    }

Extract driver’s license information as follows:

public static func ExtractDriverLicenseInf(brcdText:String) ->[ScenarioDisplayInf]
    {
        var rltArray = [ScenarioDisplayInf]()
        let subStrArr = SplitDrivingLicenseText(brcdText:brcdText)
        if(subStrArr.count > 2)
        {
            FindDLAbbreviation(text: String(subStrArr[2]) + "\n", withLineFeed: false,rltArray:&rltArray)
        }
        FindDLAbbreviation(text: brcdText, withLineFeed: true,rltArray:&rltArray)
        return rltArray
    }
    
    static func SplitDrivingLicenseText(brcdText:String)-> [Substring]
    {
        var tText = brcdText.trimmingCharacters(in: .whitespaces)
        tText = tText.replacingOccurrences(of: "\r", with: "\n")
        var subStrArr = tText.split(separator: "\n")
        for i in  (0...(subStrArr.count - 1)).reversed()
        {
            if(subStrArr[i] == "")
            {
                subStrArr.remove(at: i)
            }
        }
        return subStrArr
    }

Image 3

Web Barcode Reader

The most advantage of web technology is cross-platform. So if you build a web app for recognizing driver’s licenses, you can run it on PC and mobile web browsers.

To bring the high performance of barcode recognition to web developers, the Dynamsoft web barcode SDK is built using JavaScript and WebAssembly. Besides, it provides built-in camera module APIs which are not available for the native barcode SDK.

Therefore, you can quickly implement a web barcode reader app with a few lines of HTML5 code:

HTML
<!DOCTYPE html>
<html>
<body>
    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@7.4.0-v1/dist/dbr.js" data-productKeys="PRODUCT-KEYS"></script> 
    <script>
        let scanner = null;
        (async()=>{
            scanner = await Dynamsoft.BarcodeScanner.createInstance();
            scanner.onFrameRead = results => {console.log(results);};
            scanner.onUnduplicatedRead = (txt, result) => {alert(txt);};
            await scanner.show();
        })();
    </script> 
</body>
</html>

Note: you have to replace PRODUCT-KEYS with yours.

As long as you are using the latest Chrome, Firefox or Microsoft Edge, you can simply run the HTML file by double-clicking.

Before creating the JavaScript parser for extracting driver’s license information, you can optimize some parameters for decoding PDF417 barcode symbology:

let runtimeSettings = await scanner.getRuntimeSettings();
runtimeSettings.barcodeFormatIds = Dynamsoft.EnumBarcodeFormat.BF_PDF417;
runtimeSettings.LocalizationModes = [2,8,0,0,0,0,0,0];
runtimeSettings.deblurLevel = 2;
await scanner.updateRuntimeSettings(runtimeSettings);

The above configuration will bring the best decoding performance according to our experience. You can also try out different parameters by reading the online documentation.

Now, it is time to create the parser for driver’s license:

const DLAbbrDesMap = {
    'DCA': 'Jurisdiction-specific vehicle class',
    'DBA': 'Expiry Date',
    'DCS': 'Last Name',
    'DAC': 'First Name',
    'DBD': 'Issue Date',
    'DBB': 'Birth Date',
    'DBC': 'Gender',
    'DAY': 'Eye Color',
    'DAU': 'Height',
    'DAG': 'Street',
    'DAI': 'City',
    'DAJ': 'State',
    'DAK': 'Zip',
    'DAQ': 'License Number',
    'DCF': 'Document Discriminator',
    'DCG': 'Issue Country',
    'DAH': 'Street 2',
    'DAZ': 'Hair Color',
    'DCI': 'Place of birth',
    'DCJ': 'Audit information',
    'DCK': 'Inventory Control Number',
    'DBN': 'Alias / AKA Family Name',
    'DBG': 'Alias / AKA Given Name',
    'DBS': 'Alias / AKA Suffix Name',
    'DCU': 'Name Suffix',
    'DCE': 'Physical Description Weight Range',
    'DCL': 'Race / Ethnicity',
    'DCM': 'Standard vehicle classification',
    'DCN': 'Standard endorsement code',
    'DCO': 'Standard restriction code',
    'DCP': 'Jurisdiction-specific vehicle classification description',
    'DCQ': 'Jurisdiction-specific endorsement code description',
    'DCR': 'Jurisdiction-specific restriction code description',
    'DDA': 'Compliance Type',
    'DDB': 'Card Revision Date',
    'DDC': 'HazMat Endorsement Expiration Date',
    'DDD': 'Limited Duration Document Indicator',
    'DAW': 'Weight(pounds)',
    'DAX': 'Weight(kilograms)',
    'DDH': 'Under 18 Until',
    'DDI': 'Under 19 Until',
    'DDJ': 'Under 21 Until',
    'DDK': 'Organ Donor Indicator',
    'DDL': 'Veteran Indicator',
    // old standard
    'DAA': 'Customer Full Name',
    'DAB': 'Customer Last Name',
    'DAE': 'Name Suffix',
    'DAF': 'Name Prefix',
    'DAL': 'Residence Street Address1',
    'DAM': 'Residence Street Address2',
    'DAN': 'Residence City',
    'DAO': 'Residence Jurisdiction Code',
    'DAR': 'License Classification Code',
    'DAS': 'License Restriction Code',
    'DAT': 'License Endorsements Code',
    'DAV': 'Height in CM',
    'DBE': 'Issue Timestamp',
    'DBF': 'Number of Duplicates',
    'DBH': 'Organ Donor',
    'DBI': 'Non-Resident Indicator',
    'DBJ': 'Unique Customer Identifier',
    'DBK': 'Social Security Number',
    'DBM': 'Social Security Number',
    'DCH': 'Federal Commercial Vehicle Codes',
    'DBR': 'Name Suffix',
    'PAA': 'Permit Classification Code',
    'PAB': 'Permit Expiration Date',
    'PAC': 'Permit Identifier',
    'PAD': 'Permit IssueDate',
    'PAE': 'Permit Restriction Code',
    'PAF': 'Permit Endorsement Code',
    'ZVA': 'Court Restriction Code',
    'DAD': 'Middle Name'
};
 
var parseDriverLicense = txt => {
    console.log(txt);
    let lines = txt.split('\n');
    let abbrs = Object.keys(DLAbbrDesMap);
    let map = {};
    lines.forEach((line, i) => {
        let abbr;
        let content;
        if(i === 1){
            abbr = 'DAQ';
            content = line.substring(line.indexOf(abbr) + 3);
        }else{
            abbr = line.substring(0, 3);
            content = line.substring(3).trim();
        } 
        if(abbrs.includes(abbr)){
            map[abbr] = {
                description: DLAbbrDesMap[abbr],
                content: content
            };
        }
    });
    return map;
};

Finally, we can show the result by calling the alert() function:

scanner.onUnduplicatedRead = txt => {
 
                // Get infos
                let licenseInfo = parseDriverLicense(txt);
                alert(JSON.stringify(licenseInfo));
};

Image 4

Source Code

https://github.com/Dynamsoft/driver-license

Technical Support

If you have any questions about the Dynamsoft Barcode Reader SDK, please feel free to contact support@dynamsoft.com.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Writer Dynamsoft
Canada Canada
Xiao Ling is A technical content writer at Dynamsoft, the leading company of document capture and image processing SDKs.
He is in charge of managing Dynamsoft blog site codepool.biz and Dynamsoft GitHub community.

Comments and Discussions

 
-- There are no messages in this forum --