Building a Flutter VIN Scanner for Windows Using Dynamsoft Capture Vision C++ SDK
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
- License Key: Obtain a valid license key for the Dynamsoft Capture Vision SDK.
- Dynamsoft Capture Vision for Python: Download the
tar.gz
file and extract it. This source code package for Python includes all necessary header files and libraries for C++. - flutter_ocr_sdk: Clone the Flutter plugin project originally developed for MRZ recognition using the Dynamsoft Label Recognizer. Dynamsoft Label Recognizer is now a core part of Dynamsoft Capture Vision. We will modify the plugin to support VIN recognition.
Linking Dynamsoft Capture Vision C++ SDK to Flutter in CMakeLists.txt
-
Copy the
.h
,.lib
, and.dll
files from the Dynamsoft Capture Vision for Python package into the appropriate folders underflutter_ocr_sdk/windows
:- Place header files (
.h
) in theinclude
folder. - Place library files (
.lib
) in thelib
folder. - Place dynamic link libraries (
.dll
) in thebin
folder.
- Place header files (
-
Modify the
CMakeLists.txt
file to link the C++ SDK with your Flutter project.target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64")
-
Open the
windows/include/dlr_manager.h
file and update the included header files and namespaces to use the Dynamsoft Capture Vision SDK.#include "DynamsoftCaptureVisionRouter.h" #include "DynamsoftUtility.h" using namespace std; using namespace dynamsoft::license; using namespace dynamsoft::cvr; using namespace dynamsoft::dlr; using namespace dynamsoft::utility; using namespace dynamsoft::basic_structures;
Loading VIN Recognition Model
-
Copy the VIN model files from
dynamsoft_capture_vision_bundle-2.4.2100/CharacterModel/
to theflutter_ocr_sdk/lib/model/vin
folder. -
Use the
VINScanner.json
template fromdynamsoft_capture_vision_bundle-2.4.2100/resource/Templates/
as a reference to create a newVIN.json
file.{ "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
DirectoryPath
must be dynamically assigned. -
Add the model directories to the assets section of the
pubspec.yaml
file to package the resources with the app.assets: - lib/model/mrz/ - lib/model/vin/
-
In
lib/flutter_ocr_sdk_method_channel.dart
, update theloadModel
function to load the VIN template file and set theDirectoryPath
dynamically.Future<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
DirectoryPath
field in the JSON template.
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
:
-
Create a
MyCapturedResultReceiver
class to receive the VIN recognition results and send them back to Dart.class 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
MyImageSourceStateListener
class to observe the state of the recognition task:class 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
,CFileFetcher
andCCaptureVisionRouter
in theint Init(const char *license)
function:MyCapturedResultReceiver *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.
void 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.
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.
-
Create a toggle button to switch between MRZ and VIN recognition in
example/lib/home_page.dart
:bool 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:
flutter run -d windows
-
Press the
Image Scan
button to recognize VIN from an image file. -
Press the
Camera Scan
button to recognize VIN from a live camera feed.