Quét mã vạch bằng Bộ công cụ học máy trên Android

Bạn có thể dùng Bộ công cụ học máy để nhận dạng và giải mã mã vạch.

Tính năngKhông được kết hợpTheo cụm
Triển khaiMô hình được tải xuống linh hoạt thông qua Dịch vụ Google Play.Mô hình được liên kết tĩnh với ứng dụng của bạn tại thời điểm tạo.
Kích thước ứng dụngKích thước tăng khoảng 200 KB.Kích thước tăng khoảng 2,4 MB.
Thời gian khởi chạyCó thể phải đợi mô hình tải xuống trước khi sử dụng lần đầu.Mô hình này có sẵn ngay lập tức.

Dùng thử

Trước khi bắt đầu

  1. Trong tệp build.gradle cấp dự án, hãy nhớ thêm kho lưu trữ Maven của Google vào cả hai mục buildscriptallprojects.

  2. Thêm các phần phụ thuộc cho thư viện ML Kit trên Android vào tệp gradle cấp ứng dụng của mô-đun, thường là app/build.gradle. Chọn một trong các phần phụ thuộc sau đây dựa trên nhu cầu của bạn:

    Để gói mô hình với ứng dụng của bạn:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:barcode-scanning:17.3.0'
    }
    

    Để sử dụng mô hình này trong Dịch vụ Google Play:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1'
    }
    
  3. Nếu chọn sử dụng mô hình trong Dịch vụ Google Play, bạn có thể định cấu hình ứng dụng để tự động tải mô hình xuống thiết bị sau khi ứng dụng được cài đặt từ Cửa hàng Play. Để thực hiện việc này, hãy thêm khai báo sau vào tệp AndroidManifest.xml của ứng dụng:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="barcode" >
          <!-- To use multiple models: android:value="barcode,model2,model3" -->
    </application>
    

    Bạn cũng có thể kiểm tra rõ ràng phạm vi cung cấp của mô hình và yêu cầu tải xuống thông qua ModuleInstallClient API của Dịch vụ Google Play.

    Nếu bạn không bật tính năng tải mô hình xuống tại thời điểm cài đặt hoặc yêu cầu tải xuống rõ ràng, thì mô hình sẽ được tải xuống vào lần đầu tiên bạn chạy trình quét. Các yêu cầu bạn thực hiện trước khi quá trình tải xuống hoàn tất sẽ không có kết quả.

Nguyên tắc về hình ảnh đầu vào

  • Để ML Kit đọc mã vạch một cách chính xác, hình ảnh đầu vào phải chứa mã vạch được biểu thị bằng dữ liệu pixel đầy đủ.

    Các yêu cầu cụ thể về dữ liệu pixel phụ thuộc vào cả loại mã vạch và lượng dữ liệu được mã hoá trong đó, vì nhiều mã vạch hỗ trợ tải trọng có kích thước thay đổi. Nhìn chung, đơn vị có ý nghĩa nhỏ nhất của mã vạch phải có chiều rộng tối thiểu là 2 pixel và đối với mã 2 chiều, chiều cao tối thiểu là 2 pixel.

    Ví dụ: Mã vạch EAN-13 bao gồm các vạch và khoảng trống có chiều rộng 1, 2, 3 hoặc 4 đơn vị, vì vậy, hình ảnh mã vạch EAN-13 lý tưởng có các vạch và khoảng trống có chiều rộng ít nhất là 2, 4, 6 và 8 pixel. Vì mã vạch EAN-13 có tổng chiều rộng là 95 đơn vị, nên mã vạch phải có chiều rộng tối thiểu là 190 pixel.

    Các định dạng dày đặc hơn, chẳng hạn như PDF417, cần kích thước pixel lớn hơn để ML Kit có thể đọc chúng một cách đáng tin cậy. Ví dụ: mã PDF417 có thể có tối đa 34 "từ" rộng 17 đơn vị trong một hàng. Lý tưởng nhất là mã này phải rộng ít nhất 1156 pixel.

  • Hình ảnh không sắc nét có thể ảnh hưởng đến độ chính xác của quá trình quét. Nếu ứng dụng của bạn không nhận được kết quả chấp nhận được, hãy yêu cầu người dùng chụp lại hình ảnh.

  • Đối với các ứng dụng thông thường, bạn nên cung cấp hình ảnh có độ phân giải cao hơn, chẳng hạn như 1280x720 hoặc 1920x1080. Điều này giúp mã vạch có thể quét được từ khoảng cách xa hơn so với camera.

    Tuy nhiên, trong các ứng dụng mà độ trễ là yếu tố quan trọng, bạn có thể cải thiện hiệu suất bằng cách chụp ảnh ở độ phân giải thấp hơn, nhưng yêu cầu mã vạch chiếm phần lớn hình ảnh đầu vào. Ngoài ra, hãy xem Mẹo cải thiện hiệu suất theo thời gian thực.

1. Định cấu hình máy quét mã vạch

Nếu biết những định dạng mã vạch mà bạn muốn đọc, bạn có thể cải thiện tốc độ của trình phát hiện mã vạch bằng cách định cấu hình để trình phát hiện chỉ phát hiện những định dạng đó.

Ví dụ: để chỉ phát hiện mã Aztec và mã QR, hãy tạo một đối tượng BarcodeScannerOptions như trong ví dụ sau:

Kotlin

val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE,
                Barcode.FORMAT_AZTEC)
        .build()

Java

BarcodeScannerOptions options =
        new BarcodeScannerOptions.Builder()
        .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE,
                Barcode.FORMAT_AZTEC)
        .build();

Các định dạng sau được hỗ trợ:

  • Code 128 (FORMAT_CODE_128)
  • Code 39 (FORMAT_CODE_39)
  • Mã 93 (FORMAT_CODE_93)
  • Codabar (FORMAT_CODABAR)
  • EAN-13 (FORMAT_EAN_13)
  • EAN-8 (FORMAT_EAN_8)
  • ITF (FORMAT_ITF)
  • UPC-A (FORMAT_UPC_A)
  • UPC-E (FORMAT_UPC_E)
  • Mã QR (FORMAT_QR_CODE)
  • PDF417 (FORMAT_PDF417)
  • Aztec (FORMAT_AZTEC)
  • Data Matrix (FORMAT_DATA_MATRIX)

Kể từ mô hình đi kèm 17.1.0 và mô hình tách rời 18.2.0, bạn cũng có thể gọi enableAllPotentialBarcodes() để trả về tất cả mã vạch tiềm năng ngay cả khi không thể giải mã. Bạn có thể dùng thông tin này để hỗ trợ việc phát hiện thêm, chẳng hạn như bằng cách phóng to camera để có hình ảnh rõ ràng hơn về mọi mã vạch trong khung hình chữ nhật được trả về.

Kotlin

val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(...)
        .enableAllPotentialBarcodes() // Optional
        .build()

Java

BarcodeScannerOptions options =
        new BarcodeScannerOptions.Builder()
        .setBarcodeFormats(...)
        .enableAllPotentialBarcodes() // Optional
        .build();

Further on, starting from bundled library 17.2.0 and unbundled library 18.3.0, a new feature called auto-zoom has been introduced to further enhance the barcode scanning experience. With this feature enabled, the app is notified when all barcodes within the view are too distant for decoding. As a result, the app can effortlessly adjust the camera's zoom ratio to the recommended setting provided by the library, ensuring optimal focus and readability. This feature will significantly enhance the accuracy and success rate of barcode scanning, making it easier for apps to capture information precisely.

To enable auto-zooming and customize the experience, you can utilize the setZoomSuggestionOptions() method along with your own ZoomCallback handler and desired maximum zoom ratio, as demonstrated in the code below.

Kotlin

val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(...)
        .setZoomSuggestionOptions(
            new ZoomSuggestionOptions.Builder(zoomCallback)
                .setMaxSupportedZoomRatio(maxSupportedZoomRatio)
                .build()) // Optional
        .build()

Java

BarcodeScannerOptions options =
        new BarcodeScannerOptions.Builder()
        .setBarcodeFormats(...)
        .setZoomSuggestionOptions(
            new ZoomSuggestionOptions.Builder(zoomCallback)
                .setMaxSupportedZoomRatio(maxSupportedZoomRatio)
                .build()) // Optional
        .build();

zoomCallback is required to be provided to handle whenever the library suggests a zoom should be performed and this callback will always be called on the main thread.

The following code snippet shows an example of defining a simple callback.

Kotlin

fun setZoom(ZoomRatio: Float): Boolean {
    if (camera.isClosed()) return false
    camera.getCameraControl().setZoomRatio(zoomRatio)
    return true
}

Java

boolean setZoom(float zoomRatio) {
    if (camera.isClosed()) {
        return false;
    }
    camera.getCameraControl().setZoomRatio(zoomRatio);
    return true;
}

maxSupportedZoomRatio is related to the camera hardware, and different camera libraries have different ways to fetch it (see the javadoc of the setter method). In case this is not provided, an unbounded zoom ratio might be produced by the library which might not be supported. Refer to the setMaxSupportedZoomRatio() method introduction to see how to get the max supported zoom ratio with different Camera libraries.

When auto-zooming is enabled and no barcodes are successfully decoded within the view, BarcodeScanner triggers your zoomCallback with the requested zoomRatio. If the callback correctly adjusts the camera to this zoomRatio, it is highly probable that the most centered potential barcode will be decoded and returned.

A barcode may remain undecodable even after a successful zoom-in. In such cases, BarcodeScanner may either invoke the callback for another round of zoom-in until the maxSupportedZoomRatio is reached, or provide an empty list (or a list containing potential barcodes that were not decoded, if enableAllPotentialBarcodes() was called) to the OnSuccessListener (which will be defined in step 4. Process the image).

2. Prepare the input image

To recognize barcodes in an image, create an InputImage object from either a Bitmap, media.Image, ByteBuffer, byte array, or a file on the device. Then, pass the InputImage object to the BarcodeScanner's process method.

You can create an InputImage object from different sources, each is explained below.

Using a media.Image

To create an InputImage object from a media.Image object, such as when you capture an image from a device's camera, pass the media.Image object and the image's rotation to InputImage.fromMediaImage().

If you use the CameraX library, the OnImageCapturedListener and ImageAnalysis.Analyzer classes calculate the rotation value for you.

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

Nếu không dùng thư viện máy ảnh cho bạn biết độ xoay của hình ảnh, bạn có thể tính độ xoay đó từ độ xoay của thiết bị và hướng của cảm biến camera trong thiết bị:

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Throws(CameraAccessException::class)
private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    val deviceRotation = activity.windowManager.defaultDisplay.rotation
    var rotationCompensation = ORIENTATIONS.get(deviceRotation)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing)
        throws CameraAccessException {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int rotationCompensation = ORIENTATIONS.get(deviceRotation);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

Sau đó, hãy truyền đối tượng media.Image và giá trị độ xoay đến InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

Sử dụng URI tệp

Để tạo một đối tượng InputImage từ một URI tệp, hãy truyền ngữ cảnh ứng dụng và URI tệp đến InputImage.fromFilePath(). Điều này hữu ích khi bạn dùng ý định ACTION_GET_CONTENT để nhắc người dùng chọn một hình ảnh trong ứng dụng thư viện của họ.

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

Sử dụng ByteBuffer hoặc ByteArray

Để tạo một đối tượng InputImage từ ByteBuffer hoặc ByteArray, trước tiên, hãy tính độ xoay của hình ảnh như mô tả trước đó cho dữ liệu đầu vào media.Image. Sau đó, hãy tạo đối tượng InputImage bằng vùng đệm hoặc mảng, cùng với chiều cao, chiều rộng, định dạng mã hoá màu và độ xoay của hình ảnh:

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Sử dụng Bitmap

Để tạo đối tượng InputImage từ đối tượng Bitmap, hãy khai báo như sau:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

Hình ảnh được biểu thị bằng một đối tượng Bitmap cùng với độ xoay.

3. Nhận một thực thể BarcodeScanner

Kotlin

val scanner = BarcodeScanning.getClient()
// Or, to specify the formats to recognize:
// val scanner = BarcodeScanning.getClient(options)

Java

BarcodeScanner scanner = BarcodeScanning.getClient();
// Or, to specify the formats to recognize:
// BarcodeScanner scanner = BarcodeScanning.getClient(options);

4. Xử lý hình ảnh

Truyền hình ảnh đến phương thức process:

Kotlin

val result = scanner.process(image)
        .addOnSuccessListener { barcodes ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener {
            // Task failed with an exception
            // ...
        }

Java

Task<List<Barcode>> result = scanner.process(image)
        .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() {
            @Override
            public void onSuccess(List<Barcode> barcodes) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

5. Lấy thông tin từ mã vạch

Nếu thao tác nhận dạng mã vạch thành công, một danh sách các đối tượng Barcode sẽ được truyền đến trình nghe thành công. Mỗi đối tượng Barcode đại diện cho một mã vạch được phát hiện trong hình ảnh. Đối với mỗi mã vạch, bạn có thể lấy toạ độ đường viền của mã vạch trong hình ảnh đầu vào, cũng như dữ liệu thô được mã hoá bằng mã vạch. Ngoài ra, nếu trình quét mã vạch có thể xác định loại dữ liệu được mã hoá bằng mã vạch, bạn có thể nhận được một đối tượng chứa dữ liệu đã phân tích cú pháp.

Ví dụ:

Kotlin

for (barcode in barcodes) {
    val bounds = barcode.boundingBox
    val corners = barcode.cornerPoints

    val rawValue = barcode.rawValue

    val valueType = barcode.valueType
    // See API reference for complete list of supported types
    when (valueType) {
        Barcode.TYPE_WIFI -> {
            val ssid = barcode.wifi!!.ssid
            val password = barcode.wifi!!.password
            val type = barcode.wifi!!.encryptionType
        }
        Barcode.TYPE_URL -> {
            val title = barcode.url!!.title
            val url = barcode.url!!.url
        }
    }
}

Java

for (Barcode barcode: barcodes) {
    Rect bounds = barcode.getBoundingBox();
    Point[] corners = barcode.getCornerPoints();

    String rawValue = barcode.getRawValue();

    int valueType = barcode.getValueType();
    // See API reference for complete list of supported types
    switch (valueType) {
        case Barcode.TYPE_WIFI:
            String ssid = barcode.getWifi().getSsid();
            String password = barcode.getWifi().getPassword();
            int type = barcode.getWifi().getEncryptionType();
            break;
        case Barcode.TYPE_URL:
            String title = barcode.getUrl().getTitle();
            String url = barcode.getUrl().getUrl();
            break;
    }
}

Mẹo cải thiện hiệu suất theo thời gian thực

Nếu bạn muốn quét mã vạch trong một ứng dụng theo thời gian thực, hãy làm theo các nguyên tắc sau để đạt được tốc độ khung hình tốt nhất:

  • Không ghi lại dữ liệu đầu vào ở độ phân giải gốc của camera. Trên một số thiết bị, việc ghi lại dữ liệu đầu vào ở độ phân giải gốc sẽ tạo ra những hình ảnh cực lớn (hơn 10 megapixel), dẫn đến độ trễ rất cao mà không mang lại lợi ích nào về độ chính xác. Thay vào đó, chỉ yêu cầu kích thước từ camera cần thiết cho tính năng phát hiện mã vạch, thường không quá 2 megapixel.

    Nếu tốc độ quét là yếu tố quan trọng, bạn có thể giảm thêm độ phân giải chụp ảnh. Tuy nhiên, hãy lưu ý các yêu cầu về kích thước tối thiểu của mã vạch như đã nêu ở trên.

    Nếu bạn đang cố gắng nhận dạng mã vạch từ một chuỗi khung hình video phát trực tuyến, thì trình nhận dạng có thể tạo ra các kết quả khác nhau giữa các khung hình. Bạn nên đợi cho đến khi nhận được một chuỗi liên tiếp các giá trị giống nhau để tự tin rằng bạn đang trả về một kết quả tốt.

    Chữ số Tổng kiểm không được hỗ trợ cho ITF và CODE-39.

  • Nếu bạn dùng API Camera hoặc camera2, hãy điều chỉnh tốc độ gọi đến trình phát hiện. Nếu có khung hình video mới trong khi bộ phát hiện đang chạy, hãy thả khung hình đó. Hãy xem lớp VisionProcessorBase trong ứng dụng mẫu bắt đầu nhanh để biết ví dụ.
  • Nếu bạn sử dụng API CameraX, hãy đảm bảo rằng chiến lược áp suất ngược được đặt thành giá trị mặc định ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Điều này đảm bảo rằng chỉ có một hình ảnh được phân phối để phân tích tại một thời điểm. Nếu có nhiều hình ảnh được tạo ra khi trình phân tích đang bận, thì những hình ảnh đó sẽ tự động bị loại bỏ và không được đưa vào hàng đợi để phân phối. Sau khi hình ảnh đang được phân tích bị đóng bằng cách gọi ImageProxy.close(), hình ảnh mới nhất tiếp theo sẽ được phân phối.
  • Nếu bạn dùng đầu ra của bộ nhận diện để phủ đồ hoạ lên hình ảnh đầu vào, trước tiên, hãy lấy kết quả từ ML Kit, sau đó kết xuất hình ảnh và phủ trong một bước. Thao tác này chỉ kết xuất vào bề mặt hiển thị một lần cho mỗi khung hình đầu vào. Hãy xem các lớp CameraSourcePreview GraphicOverlay trong ứng dụng mẫu bắt đầu nhanh để biết ví dụ.
  • Nếu bạn sử dụng API Camera2, hãy chụp ảnh ở định dạng ImageFormat.YUV_420_888. Nếu bạn sử dụng Camera API cũ, hãy chụp ảnh ở định dạng ImageFormat.NV21.