How to Scan High-Density QR Codes on Android: Google ML Kit vs. Dynamsoft Barcode Reader

The market offers numerous QR code tools and SDKs, but not all are created equal. For Android development, Google ML Kit is a popular and free option, providing reliable support for general QR code detection. However, it falls short when it comes to high-density QR code detection. On the other hand, Dynamsoft Barcode Reader, a commercial SDK, excels in handling specialized barcode types, including high-density QR codes. In this article, we’ll streamline the Google ML Kit sample and integrate it with Dynamsoft Barcode SDK to compare their performance in recognizing high-density QR codes.

high density QR code from Google

What you’ll build: A single Android app that decodes high-density version 40 QR codes (177×177 modules, 2,400-byte payload) side by side with Google ML Kit and the Dynamsoft Barcode Reader bundle (v11), using still images and a live CameraX stream.

Key Takeaways

  • Google ML Kit fails to read high-density QR codes rendered at roughly 2 pixels per module, while the Dynamsoft Barcode Reader bundle decodes the same codes on Android.
  • The app integrates com.dynamsoft:barcodereaderbundle:11.6.2000 (Capture Vision core with CaptureVisionRouter) alongside com.google.mlkit:barcode-scanning:17.0.0.
  • On the version 40 sample at 380×380 px, Dynamsoft decoded the QR code in about 80–180 ms, while ML Kit reported no barcode.
  • The comparison runs on still images and a live CameraX preview, so you can measure both engines’ result and latency in real time.

Common Developer Questions

Why can’t Google ML Kit scan high-density QR codes on Android?

Google ML Kit cannot decode high-density QR codes when each module is only a couple of pixels wide. In this benchmark it failed on all six BoofCV high_version images and on the version 40 sample at 380×380 px, succeeding only when the same code was displayed large and clear at 1080×1080 px.

How do I scan high-density QR codes on Android with Dynamsoft Barcode Reader?

Add com.dynamsoft:barcodereaderbundle:11.6.2000 to your build.gradle, initialize a license with LicenseManager.initLicense(), create a CaptureVisionRouter, and call router.capture(bitmap, EnumPresetTemplate.PT_READ_BARCODES) on a background thread.

Which barcode SDK is faster for high-density QR codes on Android?

Dynamsoft Barcode Reader decodes high-density QR codes that ML Kit cannot read, but it is not always the fastest. On the 1080×1080 version 40 sample Dynamsoft took about 380 ms while ML Kit took about 150–270 ms; on the 380×380 sample Dynamsoft took about 80–180 ms and ML Kit detected no barcode.

Prerequisites

  • ML Kit

    1. Add the following meta-data entry to your AndroidManifest.xml file:

       <meta-data
           android:name="com.google.mlkit.vision.DEPENDENCIES"
           android:value="barcode"/>
      
    2. Include the ML Kit Barcode Scanning library in your build.gradle file:

       dependencies {
           implementation 'com.google.mlkit:barcode-scanning:17.0.0'
           // Or comment the dependency above and uncomment the dependency below to
           // use unbundled model that depends on Google Play Services
           // implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:16.2.1'
       }
      

      Note: The model file is generated in the APK under apk/assets/mlkit_barcode_models/barcode_ssd_mobilenet_v1_dmp25_quant.tflite.

  • Dynamsoft Barcode Reader

    1. Configure the Maven repository in your settings.gradle file and add the Dynamsoft Barcode Reader bundle dependency in your build.gradle file:

       // settings.gradle
       dependencyResolutionManagement {
           repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
           repositories {
               google()
               mavenCentral()
               maven {
                   url "https://download2.dynamsoft.com/maven/aar"
               }
           }
       }
          
       // build.gradle (app module)
       dependencies {
           implementation 'com.dynamsoft:barcodereaderbundle:11.6.2000'
       }
      

      The barcodereaderbundle includes the Dynamsoft Capture Vision core (which provides CaptureVisionRouter), so no extra dependency is required.

    2. Get a 30-day free trial license to test Dynamsoft Barcode Reader.

Decode QR Codes from Still Images and Live Camera

Step 1: Set Up Google ML Kit for QR Code Scanning

To get started with Google ML Kit, download the vision sample code, which demonstrates various features including object detection, face detection, text recognition, barcode scanning, image labeling, custom image labeling, pose detection, and selfie segmentation.

For the purpose of this article, we’ll focus on QR code scanning from still images. To streamline the app, we’ll retain only the StillImageActivity and set it as the launcher activity in the AndroidManifest.xml:

<activity
    android:name=".java.StillImageActivity"
    android:exported="true"
    android:theme="@style/AppTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Step 2: Set Up Dynamsoft Barcode Reader

Next, we create a dynamsoftbarcodescanner folder containing two class files: DynamsoftBarcodeGraphic.java and DynamsoftBarcodeProcessor.java.

  • DynamsoftBarcodeGraphic: This class is responsible for drawing the detection box on the screen. Here’s a snippet that shows how it works:

      import com.dynamsoft.dbr.BarcodeResultItem;
      ...
    
      @Override
      public void draw(Canvas canvas) {
          if (result == null) {
              throw new IllegalStateException("Attempting to draw a null barcode.");
          }
    
          // Draws the bounding box around the barcode.
          Point[] points = result.getLocation().points;
          int minx = points[0].x;
          int miny = points[0].y;
          int maxx = points[0].x;
          int maxy = points[0].y;
          for (int i = 1; i < 4; i++) {
              if (points[i].x < minx) {
                  minx = points[i].x;
              }
              else if (points[i].x > maxx) {
                  maxx = points[i].x;
              }
    
              if (points[i].y < miny) {
                  miny = points[i].y;
              }
              else if (points[i].y > maxy) {
                  maxy = points[i].y;
              }
          }
          RectF rect = new RectF(minx, miny, maxx, maxy);
          // If the image is flipped, the left will be translated to right, and the right to left.
          float x0 = translateX(rect.left);
          float x1 = translateX(rect.right);
          rect.left = min(x0, x1);
          rect.right = max(x0, x1);
          rect.top = translateY(rect.top);
          rect.bottom = translateY(rect.bottom);
          canvas.drawRect(rect, rectPaint);
      }
    

    The decoded text is displayed in the comparison panel below the image, so the graphic only draws the bounding box (in Dynamsoft green).

  • DynamsoftBarcodeProcessor: This class handles the decoding of QR codes. When an image frame is available, the processBitmap(Bitmap bitmap, final GraphicOverlay graphicOverlay) method is triggered. It then calls CaptureVisionRouter.capture(bitmap, ...) to recognize the QR code. Note that decoding runs on a background thread, and the result is reported to the UI through the DecodeListener callback:

      import com.dynamsoft.cvr.CaptureVisionRouter;
      import com.dynamsoft.cvr.CapturedResult;
      import com.dynamsoft.cvr.EnumPresetTemplate;
      import com.dynamsoft.dbr.BarcodeResultItem;
      import com.dynamsoft.dbr.DecodedBarcodesResult;
      import com.dynamsoft.license.LicenseManager;
      import com.dynamsoft.license.LicenseVerificationListener;
      ...
    
      /** Callback to report decoding results back to the UI. */
      public interface DecodeListener {
          void onDecoded(BarcodeResultItem[] items, long elapsedMs);
          void onError(String message);
      }
    
      public DynamsoftBarcodeProcessor(Context context) {
          super(context);
    
          // Get a license key from https://www.dynamsoft.com/customer/license/trialLicense?product=dbr
          LicenseManager.initLicense(
                  "LICENSE-KEY",
                  new LicenseVerificationListener() {
                      @Override
                      public void onLicenseVerified(boolean isSuccessful, Exception e) {
                      }
                  });
    
          router = new CaptureVisionRouter(context);
      }
    
      @Override
      public void processBitmap(Bitmap bitmap, final GraphicOverlay graphicOverlay) {
          executor.execute(() -> {
              long frameStartMs = SystemClock.elapsedRealtime();
              CapturedResult result = router.capture(bitmap, EnumPresetTemplate.PT_READ_BARCODES);
              long frameEndMs = SystemClock.elapsedRealtime();
    
              mainHandler.post(() -> {
                  if (result == null || result.getErrorCode() != 0) {
                      decodeListener.onError(result == null ? "null result" : result.getErrorMessage());
                      return;
                  }
                  DecodedBarcodesResult barcodeResult = result.getDecodedBarcodesResult();
                  BarcodeResultItem[] items = barcodeResult == null ? null : barcodeResult.getItems();
                  if (graphicOverlay != null) {
                      graphicOverlay.clear();
                      if (items != null) {
                          for (BarcodeResultItem barcode : items) {
                              graphicOverlay.add(new DynamsoftBarcodeGraphic(graphicOverlay, barcode));
                          }
                      }
                      graphicOverlay.postInvalidate();
                  }
                  decodeListener.onDecoded(items, frameEndMs - frameStartMs);
              });
          });
      }
    

Step 3: Build and Run the App

After integrating the necessary components, build and run the project in Android Studio. The app comes with a built-in high-density QR code sample (version 40, 177×177 modules, carrying a 2,400-byte payload). The sample bitmap is only 380×380 pixels (about 2 pixels per module) and is displayed scaled up to screen width — so it is easy to see, but its original resolution is already too dense for Google ML Kit, which fails to decode it, while Dynamsoft Barcode Reader recognizes it. The comparison panel below the image shows each engine’s result: format, truncated content, and latency. Two other entry points are available:

  • SELECT IMAGE / TAKE PHOTO: test with your own photos or a captured image.
  • Live Camera: a CameraX preview (CameraLiveActivity) where each frame is rotated to the display orientation and then fed to both engines, so the result panel updates in real time and the overlay stays aligned with the barcode on screen.

The image/preview area is kept fixed — the result panel has a fixed height with its own scrolling — and the overlay is transformed to match the displayed image (fit-center), so the detection box stays aligned no matter which image is shown. Here’s an example of high-density QR code detection in action:

high density QR code detection

Benchmark High-Density QR Code Detection on the BoofCV Dataset

To evaluate the performance of ML Kit and Dynamsoft Barcode Reader for high-density QR codes, we conducted tests using the public image dataset provided by BoofCV.

The test images are located in the qrcodes_v3/qrcodes/detection/high_version directory. We selected six images from this dataset for the tests.

The results were quite revealing: Google ML Kit struggled with these high-density QR codes, taking longer to process and failing to recognize any of them. In contrast, Dynamsoft Barcode Reader successfully recognized all the high-density QR code images with greater speed and accuracy.

high density QR detection comparison

Verify with a Generated High-Density QR Code

To make sure the updated project works with the latest Dynamsoft Barcode Reader bundle, we generated a high-density QR code used for verification. It is a version 40 QR code (177×177 modules, the highest density) carrying a 2,400-byte payload. The same payload is rendered at two resolutions:

  • 1080×1080 pixels — the built-in sample shown in the app; the decoded content is a readable URL with a 2,400-character payload.
  • 380×380 pixels — about 2 pixels per module, like the tough high_version images from the BoofCV dataset:

    generated high-density QR code

Measured on a real device with the example app:

Sample Dynamsoft Barcode Reader (v11) Google ML Kit
1080×1080 (clear, displayed large) ✓ decoded, ~380 ms ✓ decoded, ~150-270 ms
380×380 (~2 px/module) ✓ decoded, ~80-180 ms ✗ no barcode detected

This shows both engines can read the highest-density (version 40) QR code when it is displayed large and clear, while at the small, module-level-size scale only Dynamsoft Barcode Reader succeeds — matching the results from the BoofCV dataset comparison above. The generation and decoding script used for this validation is available at tools/dense-qr-high-density-demo.py.

Common Issues & Edge Cases

  • License activation requires network access. The public trial key in DynamsoftBarcodeProcessor.java needs a network connection for the first online license validation; if it cannot reach the license server, decoding fails. Replace it with your own key.
  • Very small modules defeat general-purpose detectors. At roughly 2 pixels per module, Google ML Kit reported no barcode on the 380×380 version 40 sample while Dynamsoft Barcode Reader decoded it, so keep the code in focus and fill a good portion of the frame.
  • Keep the overlay aligned with the image. The preview uses fit-center scaling with a fixed result panel; if you change the layout, update the overlay transform so detection contours stay on the code.

Source Code

Get the complete sample project source code on GitHub