如何在Android设备上识别读取美国驾照信息

根据美国机动车管理者协会(AAMVA https://www.aamva.org/DL-ID-Card-Design-Standard/)的规定,美国驾照使用PDF417编码,如下图:

如何在Android设备上识别读取美国驾照信息_第1张图片

Google Mobile Vision SDK

谷歌服务是支持驾照解析的,可以在Mobile Vision SDK中找到对应的类。要测试驾照识别,可以稍微改造下谷歌的示例代码https://github.com/googlesamples/android-vision/tree/master/visionSamples/barcode-reader。

onBarcodeDetected(Barcode barcode)回调中判断barcode类别是否是PDF417

if (barcode.format == Barcode.PDF417) {
 
    Barcode.DriverLicense driverLicense = barcode.driverLicense;
    if (driverLicense != null) {
        Intent intent = new Intent(BarcodeCaptureActivity.this, ResultActivity.class);
        intent.putExtra("DriverLicense", driverLicense);
        startActivity(intent);
    }
}

如果得到了驾照信息,就启动一个新的Activity显示结果:

public class ResultActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setVerticalScrollBarEnabled(true);
        tv.setText("");
        tv.setMovementMethod(new ScrollingMovementMethod());
 
        Intent intent = getIntent();
        if (intent != null) {
            Barcode.DriverLicense driverLicense = (Barcode.DriverLicense) intent.getParcelableExtra("DriverLicense");
            if (driverLicense != null) {
                String documentType = driverLicense.documentType;
                tv.append("Document Type:\n" + documentType + "\n\n");
                String firstName = driverLicense.firstName;
                tv.append("First Name:\n" + firstName + "\n\n");
                String middleName = driverLicense.middleName;
                tv.append("Middle Name:\n" + middleName + "\n\n");
                String lastName = driverLicense.lastName;
                tv.append("Last Name:\n" + lastName + "\n\n");
                String gender = driverLicense.gender;
                tv.append("Gender: \n" + gender + "\n\n");
                String addressStreet = driverLicense.addressStreet;
                tv.append("Street:\n" + addressStreet + "\n\n");
                String addressCity = driverLicense.addressCity;
                tv.append("City:\n" + addressCity + "\n\n");
                String addressState = driverLicense.addressState;
                tv.append("State:\n" + addressState + "\n\n");
                String addressZip = driverLicense.addressZip;
                tv.append("Zip:\n" + addressZip + "\n\n");
                String licenseNumber = driverLicense.licenseNumber;
                tv.append("License Number:\n" + licenseNumber + "\n\n");
                String issueDate = driverLicense.issueDate;
                tv.append("Issue Date:\n" + issueDate + "\n\n");
                String expiryDate = driverLicense.expiryDate;
                tv.append("Expiry Date:\n" + expiryDate + "\n\n");
                String birthDate = driverLicense.birthDate;
                tv.append("Birth Date:\n" + birthDate + "\n\n");
                String issuingCountry = driverLicense.issuingCountry;
                tv.append("Issue Country:\n" + issuingCountry + "\n\n");
            }
        }
 
        setContentView(tv);
    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }
}

程序的运行效果:

如何在Android设备上识别读取美国驾照信息_第2张图片

Dynamsoft Barcode Reader SDK

谷歌服务在很多的国内手机上用不了,比如华为。所以也可以根据定义自己写解析。

首先用fotoapparat快速创建一个摄像头应用:

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

通过帧回调函数,调用Dynamsoft Barcode Reader进行解码:

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);
      }
   }
}
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, "");

接下来仿照Google,定义一个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);
    }
}

再创建一个DBRDriverLicenseUtil类,定义字段:

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";

校验解码后的字符串是否符合AAMVA标准:

public static boolean ifDriverLicense(String barcodeText) {
    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' &amp;&amp; data[1] == 'N' &amp;&amp; data[2] == 'S' &amp;&amp; data[3] == 'I' &amp;&amp; data[4] == ' ') || (data[0] == 'A' &amp;&amp; data[1] == 'A' &amp;&amp; data[2] == 'M' &amp;&amp; data[3] == 'V' &amp;&amp; data[4] == 'A'))
                &amp;&amp; (data[5] >= '0' &amp;&amp; data[5] <= '9') &amp;&amp; (data[6] >= '0' &amp;&amp; data[6] <= '9') &amp;&amp; (data[7] >= '0' &amp;&amp; data[7] <= '9')
                &amp;&amp; (data[8] >= '0' &amp;&amp; data[8] <= '9') &amp;&amp; (data[9] >= '0' &amp;&amp; data[9] <= '9') &amp;&amp; (data[10] >= '0' &amp;&amp; data[10] <= '9')
                &amp;&amp; (data[11] >= '0' &amp;&amp; data[11] <= '9') &amp;&amp; (data[12] >= '0' &amp;&amp; data[12] <= '9')
                &amp;&amp; (data[13] >= '0' &amp;&amp; data[13] <= '9') &amp;&amp; (data[14] >= '0' &amp;&amp; data[14] <= '9')
        ) {
            return true;
        }
    }
    return false;
}

读取驾照信息,通过HashMap存储返回结果:

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;
}

把结果放到DriverLicense里用于显示:

HashMap<String, String> resultMaps = DBRDriverLicenseUtil.readUSDriverLicense(result.barcodeText);
Intent intent = new Intent(MainActivity.this, ResultActivity.class);
DriverLicense driverLicense = new DriverLicense();
driverLicense.documentType = "DL";
driverLicense.firstName = resultMaps.get(DBRDriverLicenseUtil.FIRST_NAME);
driverLicense.middleName = resultMaps.get(DBRDriverLicenseUtil.MIDDLE_NAME);
driverLicense.lastName = resultMaps.get(DBRDriverLicenseUtil.LAST_NAME);
driverLicense.gender = resultMaps.get(DBRDriverLicenseUtil.GENDER);
driverLicense.addressStreet = resultMaps.get(DBRDriverLicenseUtil.STREET);
driverLicense.addressCity = resultMaps.get(DBRDriverLicenseUtil.CITY);
driverLicense.addressState = resultMaps.get(DBRDriverLicenseUtil.STATE);
driverLicense.addressZip = resultMaps.get(DBRDriverLicenseUtil.ZIP);
driverLicense.licenseNumber = resultMaps.get(DBRDriverLicenseUtil.LICENSE_NUMBER);
driverLicense.issueDate = resultMaps.get(DBRDriverLicenseUtil.ISSUE_DATE);
driverLicense.expiryDate = resultMaps.get(DBRDriverLicenseUtil.EXPIRY_DATE);
driverLicense.birthDate = resultMaps.get(DBRDriverLicenseUtil.BIRTH_DATE);
driverLicense.issuingCountry = resultMaps.get(DBRDriverLicenseUtil.ISSUING_COUNTRY);
 
intent.putExtra("DriverLicense", driverLicense);
startActivity(intent);

最后运行这个不依赖谷歌服务的安卓驾照识别应用:

如何在Android设备上识别读取美国驾照信息_第3张图片

Google vs. Dynamsoft

除了驾照识别,还可以比较下Google和Dynamsoft的多码识别能力:

如何在Android设备上识别读取美国驾照信息_第4张图片

源码

https://github.com/yushulx/android-driver-license

你可能感兴趣的:(Barcode,Android)