根据美国机动车管理者协会(AAMVA https://www.aamva.org/DL-ID-Card-Design-Standard/)的规定,美国驾照使用PDF417编码,如下图:
谷歌服务是支持驾照解析的,可以在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();
}
}
程序的运行效果:
谷歌服务在很多的国内手机上用不了,比如华为。所以也可以根据定义自己写解析。
首先用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' && 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;
}
读取驾照信息,通过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);
最后运行这个不依赖谷歌服务的安卓驾照识别应用:
除了驾照识别,还可以比较下Google和Dynamsoft的多码识别能力:
https://github.com/yushulx/android-driver-license