How to Build a VIN Scanner in HTML5

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.

VIN code39

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 a VIN scanner in HTML5 to scan the barcode or the text to read the VIN code using the Dynamsoft Capture Vision SDK.

Online demo

Demo video:

New HTML Page

Create a new HTML page with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>VIN Scanner</title>
  <style>
  </style>
</head>
<body>
  <h1>VIN Scanner</h1>
  <script>
  </script>
</body>
</html>

Add Dependencies

Include the Dynamsoft Capture Vision SDK with the following line of code:

<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@2.4.2200/dist/dcv.bundle.js"></script>

Initialize the SDK

  1. Set the license. You can apply for a license here.

    async function init(){
      Dynamsoft.License.LicenseManager.initLicense(
        "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==" //one-day trial license
      );
    }
    
  2. Load resources.

    await Dynamsoft.Core.CoreModule.loadWasm(["DBR","DLR","DCP"]);
    await Dynamsoft.DLR.LabelRecognizerModule.loadRecognitionData("VIN");
    await Dynamsoft.DCP.CodeParserModule.loadSpec("VIN");
    
  3. Create an instance of Capture Vision Router to call various image processing tasks like barcode reading and OCR.

    router = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
    

Open the Camera

Next, let’s use Dynamsoft Camera Enhancer to open the camera.

  1. Add a container for the camera.

    <div id="cameraViewContainer"></div>
    <style>
      #cameraViewContainer {
        width: 100%;
        height: 320px;
        margin-top: 10px;
        margin-bottom: 10px;
      }
    </style>
    
  2. Create an instance of Camera Enhancer and bind it to the container.

    const cameraViewContainer = document.getElementById("cameraViewContainer");
    let cameraEnhancer;
    let view = await Dynamsoft.DCE.CameraView.createInstance();
    cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(
      view
    );
    cameraViewContainer.append(view.getUIElement());
    
  3. Add a button to open or close the camera.

    <button id="toggleButton">Start Detection</button>
    <script>
    async function toggleDetection() {
      if (cameraEnhancer.isOpen()) {
        await cameraEnhancer.close();
        toggleButton.innerText = "Start Detection";
      }else{
        await cameraEnhancer.open();
        toggleButton.innerText = "Stop Detection";
      }
    };
    </script>
    

Read Barcodes

Next, read the barcode on the VIN label.

  1. 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 VINBarcodeTemplate.json.

    {
      "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"],
          "ExpectedBarcodesCount": 1
        }
      ]
    }
    
  2. Load the template using the initSettings function:

    await router.initSettings("./VINBarcodeTemplate.json");
    
  3. Create a result receiver to retrieve the results.

    const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver();
    resultReceiver.onCapturedResultReceived = (result) => {
      console.log(result);
    }
    await router.addResultReceiver(resultReceiver);
    
  4. Set the camera as the input for the router.

    router.setInput(cameraEnhancer);
    
  5. Start processing the camera frames to read barcodes after the camera is opened.

     async function toggleDetection() {
       if (cameraEnhancer.isOpen()) {
    +    await router.stopCapturing();
         await cameraEnhancer.close();
         toggleButton.innerText = "Start Detection";
       }else{
         await cameraEnhancer.open();
    +    await router.startCapturing("ReadVINBarcode);
         toggleButton.innerText = "Stop Detection";
       }
     };
    

Read the Text

Next, read the text on the VIN label.

  1. Create a new JSON template to configure the router to read 17-character text. Save it as VINTextTemplate.json. 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 configure GrayscaleTransformationModes 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
        }
      ]
    }
    
  2. Pass the name ReadVINText for the router to recognize the text.

    let templateName = "ReadVINText";
    await router.initSettings("./VINTextTemplate.json");
    await router.startCapturing(templateName);
    

Parse the Result

Next, after getting the VIN code, we can extract the info from it using Dynamsoft Code Parser.

  1. Create an instance of Code Parser.

    let parser = await Dynamsoft.DCP.CodeParser.createInstance();
    
  2. Parse the VIN code when the VIN is recognized.

    const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver();
    resultReceiver.onCapturedResultReceived = (result) => {
      let VIN;
      if (result.items.length > 0) {
        let item = result.items[0];
        if (item.type === Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE) {
          VIN = item.text;
        }else if (item.type === Dynamsoft.Core.EnumCapturedResultItemType.CRIT_TEXT_LINE) {
          VIN = item.text;
        }
        if (VIN) {
          let parsedResultItem = await parser.parse(VIN);
          let modelYear = parsedResultItem.getFieldValue("modelYear");
          let worldManufacturerIndentifier = parsedResultItem.getFieldValue("WMI");
        }
      }
    }
    

Source Code

You can find the source code of the demo in the following repo: https://github.com/tony-xlh/VIN-Scanner-JavaScript/