The VIN (Vehicle Identification Number) is a unique 17-character code assigned to every motor vehicle. It serves as a fingerprint for the vehicle, providing essential information such as the manufacturer, model, engine type, year of production, and country of origin. In this tutorial, we will demonstrate how to perform VIN recognition on Windows using Flutter and the Dynamsoft Capture Vision C++ SDK.
Flutter Windows VIN Scanner Demo
Prerequisites
: Download thetar.gzfile and extract it. This source code package for Python includes all necessary header files and libraries for C++.
Use the
VINScanner.jsontemplate fromdynamsoft_capture_vision_bundle-2.4.2100/resource/Templates/as a reference to create a newVIN.jsonfile.
CODE{
"LabelRecognizerTaskSettingOptions": [
{
"Name": "task_vin_text",
"TextLineSpecificationNameArray": [
"tls_vin_text"
],
"SectionImageParameterArray": [
{
"Section": "ST_REGION_PREDETECTION",
"ImageParameterName": "ip_recognize_text"
},
{
"Section": "ST_TEXT_LINE_LOCALIZATION",
"ImageParameterName": "ip_recognize_text"
},
{
"Section": "ST_TEXT_LINE_RECOGNITION",
"ImageParameterName": "ip_recognize_text"
}
]
}
],
"TextLineSpecificationOptions": [
{
"Name": "tls_vin_text",
"CharacterModelName": "VIN",
"StringRegExPattern": "[0-9A-HJ-NPR-Z]{9}[1-9A-HJ-NPR-TV-Y][0-9A-HJ-NPR-Z]{2}[0-9]{5}",
"CharHeightRange": [
5,
1000,
1
],
"StringLengthRange": [
17,
17
],
"OutputResults": 1
}
],
"CaptureVisionTemplates": [
{
"Name": "ReadVINText",
"ImageROIProcessingNameArray": [
"roi_vin_text"
]
}
],
"ImageParameterOptions": [
{
"Name": "ip_recognize_text",
"TextDetectionMode": {
"Mode": "TTDM_LINE",
"Direction": "HORIZONTAL",
"CharHeightRange": [
5,
1000,
1
],
"Sensitivity": 7
},
"GrayscaleTransformationModes": [
{
"Mode": "GTM_ORIGINAL"
},
{
"Mode": "GTM_INVERTED"
}
]
}
],
"TargetROIDefOptions": [
{
"Name": "roi_vin_text",
"TaskSettingNameArray": [
"task_vin_text"
]
}
],
"CharacterModelOptions": [
{
"Name": "VIN",
"DirectoryPath": "",
"CharSet": {
"ExcludeChars": [
"O",
"Q",
"I"
]
}
}
]
}
Note: The parameters in this template are tailored for OCR only. To successfully load the model, the
DirectoryPathmust be dynamically assigned.
Add the model directories to the assets section of the
pubspec.yamlfile to package the resources with the app.
CODEassets:
- lib/model/mrz/
- lib/model/vin/
In
lib/flutter_ocr_sdk_method_channel.dart, update theloadModelfunction to load the VIN template file and set theDirectoryPathdynamically.
CODEFuture<int?> loadModel({ModelType modelType = ModelType.mrz}) async {
var directory =
'data/flutter_assets/packages/flutter_ocr_sdk/lib/model/mrz/';
String modelPath = 'packages/flutter_ocr_sdk/lib/model/mrz/';
var fileName = "MRZ";
var templateName = 'MRZ.json';
if (modelType == ModelType.vin) {
fileName = "VIN";
templateName = 'VIN.json';
modelPath = 'packages/flutter_ocr_sdk/lib/model/vin/';
directory =
'./data/flutter_assets/packages/flutter_ocr_sdk/lib/model/vin/';
}
...
var templatePath = join(modelPath, templateName);
String template = await loadAssetString(templatePath);
if (isDesktop) {
String exePath = Platform.resolvedExecutable;
String exeDir = dirname(exePath);
String assetPath = join(exeDir, directory);
var templateMap = json.decode(template);
templateMap['CharacterModelOptions'][0]['DirectoryPath'] = assetPath;
await methodChannel
.invokeMethod('loadModel', {'template': json.encode(templateMap)});
}
}
Explanation
Platform.resolvedExecutable: Fetches the path of the executable file.
dirname: Retrieves the directory containing the executable.- The absolute path of the model directory is dynamically constructed and assigned to the
DirectoryPathfield in the JSON template.
Create a
MyCapturedResultReceiverclass to receive the VIN recognition results and send them back to Dart.
CODEclass MyCapturedResultReceiver : public CCapturedResultReceiver
{
public:
vector<CRecognizedTextLinesResult *> results;
vector<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>> pendingResults = {};
EncodableList out;
public:
void OnRecognizedTextLinesReceived(CRecognizedTextLinesResult *pResult) override
{
WrapResults(pResult);
}
void sendResult()
{
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result = std::move(pendingResults.front());
pendingResults.erase(pendingResults.begin());
result->Success(out);
out.clear();
}
void WrapResults(CRecognizedTextLinesResult *pResults)
{
if (!pResults)
{
return;
}
int count = pResults->GetItemsCount();
for (int i = 0; i < count; i++)
{
EncodableList area;
const CTextLineResultItem *result = pResults->GetItem(i);
CPoint *points = result->GetLocation().points;
int x1 = points[0][0];
int y1 = points[0][1];
int x2 = points[1][0];
int y2 = points[1][1];
int x3 = points[2][0];
int y3 = points[2][1];
int x4 = points[3][0];
int y4 = points[3][1];
EncodableMap map;
map[EncodableValue("confidence")] = EncodableValue(result->GetConfidence());
map[EncodableValue("text")] = EncodableValue(result->GetText());
map[EncodableValue("x1")] = EncodableValue(x1);
map[EncodableValue("y1")] = EncodableValue(y1);
map[EncodableValue("x2")] = EncodableValue(x2);
map[EncodableValue("y2")] = EncodableValue(y2);
map[EncodableValue("x3")] = EncodableValue(x3);
map[EncodableValue("y3")] = EncodableValue(y3);
map[EncodableValue("x4")] = EncodableValue(x4);
map[EncodableValue("y4")] = EncodableValue(y4);
area.push_back(map);
out.push_back(area);
}
}
};
Explanation
OnRecognizedTextLinesReceived: Callback invoked when recognition results are ready.
WrapResults: Converts the results into a Dart-compatible format.
sendResult: Sends results back to Dart using the Flutter method channel.
Create a
MyImageSourceStateListenerclass to observe the state of the recognition task:
CODEclass MyImageSourceStateListener : public CImageSourceStateListener
{
private:
CCaptureVisionRouter *m_router;
MyCapturedResultReceiver *m_receiver;
public:
MyImageSourceStateListener(CCaptureVisionRouter *router, MyCapturedResultReceiver *receiver)
{
m_router = router;
m_receiver = receiver;
}
void OnImageSourceStateReceived(ImageSourceState state)
{
if (state == ISS_EXHAUSTED)
{
m_router->StopCapturing();
m_receiver->sendResult();
}
}
};
Explanation
ISS_EXHAUSTED: Indicates the task is completed, and results can be sent back to Dart.
Set the license key, initialize
MyCapturedResultReceiver,CImageSourceStateListener,CFileFetcherandCCaptureVisionRouterin theint Init(const char *license)function:
CODEMyCapturedResultReceiver *capturedReceiver;
CImageSourceStateListener *listener;
CFileFetcher *fileFetcher;
CCaptureVisionRouter *cvr;
int Init(const char *license)
{
char errorMsgBuffer[512];
int ret = CLicenseManager::InitLicense(license, errorMsgBuffer, 512);
printf("InitLicense: %s\n", errorMsgBuffer);
cvr = new CCaptureVisionRouter;
fileFetcher = new CFileFetcher();
ret = cvr->SetInput(fileFetcher);
if (ret)
{
printf("SetInput error: %d\n", ret);
}
capturedReceiver = new MyCapturedResultReceiver;
ret = cvr->AddResultReceiver(capturedReceiver);
if (ret)
{
printf("AddResultReceiver error: %d\n", ret);
}
listener = new MyImageSourceStateListener(cvr, capturedReceiver);
ret = cvr->AddImageSourceStateListener(listener);
if (ret)
{
printf("AddImageSourceStateListener error: %d\n", ret);
}
return ret;
}
Explanation
CCaptureVisionRouter: Manages the recognition process.
CFileFetcher: Loads the image file or buffer.
Add functions to handle VIN recognition from an image file or a camera feed.
CODEvoid start()
{
char errorMsg[512] = {0};
int errorCode = cvr->StartCapturing("", false, errorMsg, 512);
if (errorCode != 0)
{
printf("StartCapturing: %s\n", errorMsg);
}
}
void RecognizeFile(std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> &pendingResult, const char *filename)
{
printf("RecognizeFile: %s\n", filename);
capturedReceiver->pendingResults.push_back(std::move(pendingResult));
fileFetcher->SetFile(filename);
start();
}
void RecognizeBuffer(std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> &pendingResult, const unsigned char *buffer, int width, int height, int stride, int format)
{
capturedReceiver->pendingResults.push_back(std::move(pendingResult));
CImageData *imageData = new CImageData(stride * height, buffer, width, height, stride, getPixelFormat(format));
fileFetcher->SetFile(imageData);
delete imageData;
start();
}
Explanation
StartCapturing: Starts a recognition task.
Create a toggle button to switch between MRZ and VIN recognition in
example/lib/home_page.dart:
CODEbool isMrzSelected = true;
FlutterOcrSdk detector = FlutterOcrSdk();
ModelType model = ModelType.mrz;
Future<void> switchModel(ModelType newModel) async {
model = newModel;
await detector.loadModel(modelType: newModel);
}
return Scaffold(
body: Column(
children: [
title,
description,
Center(
child: ToggleButtons(
borderRadius: BorderRadius.circular(10),
isSelected: [isMrzSelected, !isMrzSelected],
selectedColor: Colors.white,
fillColor: Colors.orange,
color: Colors.grey,
children: const [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('MRZ'),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('VIN'),
),
],
onPressed: (index) {
setState(() {
isMrzSelected = (index == 0);
if (isMrzSelected) {
switchModel(ModelType.mrz);
} else {
switchModel(ModelType.vin);
}
});
},
),
),
...
],
),
);
Build and run the Flutter Windows app:
CODEflutter run -d windows
Press the
Image Scanbutton to recognize VIN from an image file.
Implementing VIN Recognition in C++
When implementing VIN recognition in C++ for a Flutter application, it’s essential to ensure that CPU-intensive tasks are executed on separate threads to prevent blocking the main thread. Dynamsoft Capture Vision SDK simplifies this by providing asynchronous APIs, minimizing the need for manual threading.
Here are the steps to implement VIN recognition in dlr_manager.h:
Updating Dart Code for MRZ and VIN Recognition
With the Flutter plugin complete, you can now update the example app to support both MRZ and VIN recognition.
Source Code
https://github.com/yushulx/flutter_ocr_sdk/tree/DCV/example
SOCIAL SHARE CARD GENERATOR