How to Build an Android VIN Scanner
VIN stands for vehicle identification number. It is a unique 17-character code to identify individual vehicles. The VIN contains information about your car’s make, model, and year, as well as the country where it was manufactured and other important details.
You may find the VIN label on your car’s dashboard, near the windshield on the driver’s side or on your car’s chassis.
Most VIN labels only contain the text. Some labels also have a barcode in the following format: Code 39, QR code and Data Matrix.
In this article, we are going to build an Android VIN scanner to scan the barcode or the text to read the VIN code using the Dynamsoft Capture Vision SDK on your mobile devices.
Demo video:
New Project
Use Android Studio to create a new empty activity project with Java.
Add Dependencies
-
Add the Dynamsoft’s maven repo.
repositories { maven { url "https://download2.dynamsoft.com/maven/aar" } }
-
Add the Dynamsoft Capture Vision SDK.
implementation 'com.dynamsoft:dynamsoftcapturevisionbundle:2.6.1001'
-
Add the OCR model for VIN.
implementation 'com.dynamsoft:dynamsoftvin:3.4.20'
Initialize the License
Initialize the license to use the SDK. You can apply for a license here.
//use one-day trial
LicenseManager.initLicense("LICENSE-KEY", this, (isSuccess, error) -> {
error.printStackTrace();
});
Access Cameras
-
Add a CameraView to display the camera preview by adding the following to
activity_main.xml
:<com.dynamsoft.dce.CameraView android:id="@+id/camera_view" android:layout_width="match_parent" android:layout_height="match_parent"> </com.dynamsoft.dce.CameraView>
-
Get the CameraView by its ID.
CameraView cameraView = findViewById(R.id.camera_view);
-
Create a Camera Enhancer instance to control the camera and bind the CameraView to it.
private CameraEnhancer mCamera; mCamera = new CameraEnhancer(cameraView, this);
-
Request camera permission.
PermissionUtil.requestCameraPermission(this);
-
Open or close the camera.
mCamera.open(); //mCamera.close();
-
Set a scan region to fetch only a part of the frame.
DSRect region = new DSRect(0,0.4f,1,0.6f,true); mCamera.setScanRegion(region);
Read the Barcode
Next, read the barcode on the VIN label.
-
Create a Capture Vision Router to call various image processing tasks.
private CaptureVisionRouter mRouter; mRouter = new CaptureVisionRouter(this);
-
Create a new JSON template to configure the router to read barcodes in the Code 39, QR code and Data Matrix formats. Save it as
vin_barcode_template.json
inres/raw
.{ "CaptureVisionTemplates": [ { "Name": "ReadVINBarcode", "ImageROIProcessingNameArray": [ "roi-read-vin-barcodes" ] } ], "TargetROIDefOptions": [ { "Name": "roi-read-vin-barcodes", "TaskSettingNameArray": ["task-read-vin-barcodes"] } ], "BarcodeReaderTaskSettingOptions": [ { "Name": "task-read-vin-barcodes", "BarcodeFormatIds": ["BF_CODE_39", "BF_QR_CODE", "BF_DATAMATRIX"], "LocalizationModes": [ { "Mode": "LM_CONNECTED_BLOCKS" }, { "Mode": "LM_SCAN_DIRECTLY", "ScanStride": 0, "ScanDirection": 0, "IsOneDStacked": 0 }, { "Mode": "LM_STATISTICS" }, { "Mode": "LM_LINES" } ], "ExpectedBarcodesCount": 1 } ] }
-
Update the runtime settings of Capture Vision Router using the template.
@Override protected void onCreate(Bundle savedInstanceState) { String template = readTemplate(R.raw.vin_barcode_template); mRouter.initSettings(template); } private String readTemplate(int ID) throws IOException { InputStream inp = this.getResources().openRawResource(ID); BufferedReader reader = new BufferedReader(new InputStreamReader(inp)); StringBuilder out = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { out.append(line); } String content = out.toString(); reader.close(); return content; }
-
Set the camera enhancer as the image source for Capture Vision Router.
mRouter.setInput(mCamera);
-
Add a result receiver to get the detected barcode results.
mRouter.addResultReceiver(new CapturedResultReceiver() { @Override public void onCapturedResultReceived(@NonNull CapturedResult result) { String barcode = ""; for (CapturedResultItem item:result.getItems()) { if (item.getType() == EnumCapturedResultItemType.CRIT_BARCODE) { barcode = ((BarcodeResultItem) item).getText(); } } } });
-
Start fetching frames from the camera and detect the barcodes.
mRouter.startCapturing("ReadVINBarcode");
Read the Text
Next, read the text on the VIN label.
-
Create a new JSON template to configure the router to read 17-character text. Save it as
vin_text_template.json
inres/raw
. We need to specify the VIN model and image processing parameters. For example, the text is often inverted (white text on black background), so we need to configureGrayscaleTransformationModes
to read inverted images.{ "CaptureVisionTemplates": [ { "Name": "ReadVINText", "ImageROIProcessingNameArray": [ "roi-read-vin-text" ], "ImageSource": "", "MaxParallelTasks": 4, "MinImageCaptureInterval": 0, "OutputOriginalImage": 0, "Timeout": 10000 } ], "TargetROIDefOptions": [ { "Name": "roi-read-vin-text", "TaskSettingNameArray": ["task-read-vin-text"] } ], "CharacterModelOptions": [ { "CharSet": { "ExcludeChars": ["O", "Q", "I"] }, "DirectoryPath": "", "Name": "VIN" } ], "ImageParameterOptions": [ { "BaseImageParameterName": "", "BinarizationModes": [ { "BinarizationThreshold": -1, "BlockSizeX": 0, "BlockSizeY": 0, "EnableFillBinaryVacancy": 1, "GrayscaleEnhancementModesIndex": -1, "Mode": "BM_LOCAL_BLOCK", "MorphOperation": "Close", "MorphOperationKernelSizeX": -1, "MorphOperationKernelSizeY": -1, "MorphShape": "Rectangle", "ThresholdCompensation": 10 } ], "ColourConversionModes": [ { "BlueChannelWeight": -1, "GreenChannelWeight": -1, "Mode": "CICM_GENERAL", "RedChannelWeight": -1, "ReferChannel": "H_CHANNEL" } ], "GrayscaleEnhancementModes": [ { "Mode": "GEM_GENERAL", "Sensitivity": -1, "SharpenBlockSizeX": -1, "SharpenBlockSizeY": -1, "SmoothBlockSizeX": -1, "SmoothBlockSizeY": -1 } ], "GrayscaleTransformationModes": [ { "Mode": "GTM_ORIGINAL" }, { "Mode": "GTM_INVERTED" } ], "IfEraseTextZone": 0, "Name": "ip_recognize_text", "RegionPredetectionModes": [ { "AspectRatioRange": "[]", "FindAccurateBoundary": 0, "ForeAndBackgroundColours": "[]", "HeightRange": "[]", "ImageParameterName": "", "MeasuredByPercentage": 1, "MinImageDimension": 262144, "Mode": "RPM_GENERAL", "RelativeRegions": "[]", "Sensitivity": 1, "SpatialIndexBlockSize": 5, "WidthRange": "[]" } ], "ScaleDownThreshold": 2300, "ScaleUpModes": [ { "AcuteAngleWithXThreshold": -1, "LetterHeightThreshold": 0, "Mode": "SUM_AUTO", "ModuleSizeThreshold": 0, "TargetLetterHeight": 0, "TargetModuleSize": 0 } ], "TextDetectionMode": { "CharHeightRange": [5, 1000, 1], "Direction": "HORIZONTAL", "MaxSpacingInALine": -1, "Mode": "TTDM_LINE", "Sensitivity": 7, "StringLengthRange": null }, "TextureDetectionModes": [ { "Mode": "TDM_GENERAL_WIDTH_CONCENTRATION", "Sensitivity": 5 } ] } ], "LabelRecognizerTaskSettingOptions": [ { "Name": "task-read-vin-text", "TextLineSpecificationNameArray": ["tls_vin_text"], "SectionImageParameterArray": [ { "ContinueWhenPartialResultsGenerated": 1, "ImageParameterName": "ip_recognize_text", "Section": "ST_REGION_PREDETECTION" }, { "ContinueWhenPartialResultsGenerated": 1, "ImageParameterName": "ip_recognize_text", "Section": "ST_TEXT_LINE_LOCALIZATION" }, { "ContinueWhenPartialResultsGenerated": 1, "ImageParameterName": "ip_recognize_text", "Section": "ST_TEXT_LINE_RECOGNITION" } ] } ], "TextLineSpecificationOptions": [ { "BinarizationModes": [ { "BinarizationThreshold": -1, "BlockSizeX": 11, "BlockSizeY": 11, "EnableFillBinaryVacancy": 1, "GrayscaleEnhancementModesIndex": -1, "Mode": "BM_LOCAL_BLOCK", "MorphOperation": "Erode", "MorphOperationKernelSizeX": -1, "MorphOperationKernelSizeY": -1, "MorphShape": "Rectangle", "ThresholdCompensation": 10 } ], "CharHeightRange": [5, 1000, 1], "CharacterModelName": "VIN", "CharacterNormalizationModes": [ { "Mode": "CNM_AUTO", "MorphArgument": "3", "MorphOperation": "Close" } ], "ConcatResults": 0, "ConcatSeparator": "\n", "ConcatStringLengthRange": [3, 200], "ExpectedGroupsCount": 1, "GrayscaleEnhancementModes": [ { "Mode": "GEM_GENERAL", "Sensitivity": -1, "SharpenBlockSizeX": -1, "SharpenBlockSizeY": -1, "SmoothBlockSizeX": -1, "SmoothBlockSizeY": -1 } ], "Name": "tls_vin_text", "OutputResults": 1, "StringLengthRange": [17, 17], "StringRegExPattern": "[0-9A-HJ-NPR-Z]{9}[1-9A-HJ-NPR-TV-Y][0-9A-HJ-NPR-Z]{2}[0-9]{5}", "SubGroups": null, "TextLinesCount": 1 } ] }
-
Get the text result in the result receiver.
mRouter.addResultReceiver(new CapturedResultReceiver() { @Override public void onCapturedResultReceived(@NonNull CapturedResult result) { String barcode = ""; String textLine = ""; for (CapturedResultItem item:result.getItems()) { if (item.getType() == EnumCapturedResultItemType.CRIT_BARCODE) { barcode = ((BarcodeResultItem) item).getText(); } if (item.getType() == EnumCapturedResultItemType.CRIT_TEXT_LINE) { textLine = ((TextLineResultItem) item).getText(); } } } });
-
Load the template and start recognizing the text.
String template = readTemplate(R.raw.vin_text_template); mRouter.initSettings(template); mRouter.startCapturing("ReadVINText");
Read the Barcode and the Text at the Same Time
We can merge the two JSON templates and create a new CaptureVisionTemplate
object to run the barcode and text detection at the same time.
{
"CaptureVisionTemplates": [
{
"Name": "ReadVINBarcodeAndText",
"ImageROIProcessingNameArray": [
"roi-read-vin-barcodes","roi-read-vin-text"
]
}
]
}
Then use it by passing the name for the router.
mRouter.startCapturing("ReadVINBarcodeAndText");
Filter Misreadings
In a live scanning scenario, misreadings may happen. We can enable the result filter to filter out misreadings. It works by checking the multiple reading results.
MultiFrameResultCrossFilter filter = new MultiFrameResultCrossFilter();
filter.enableResultCrossVerification(EnumCapturedResultItemType.CRIT_TEXT_LINE, true);
filter.enableResultCrossVerification(EnumCapturedResultItemType.CRIT_BARCODE, true);
mRouter.addResultFilter(filter);
Handle Lifecycle
Stop the scanning when the application is put in the background and resume scanning when it is in the foreground.
@Override
public void onResume() {
super.onResume();
startScanning();
}
@Override
public void onPause() {
super.onPause();
stopScanning();
}
Source Code
Check out the source code of the demo to have a try: https://github.com/tony-xlh/Android-VIN-Scanner