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

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

Tính năngKhông nhómGộp chung
Triển khaiMô hình được tải xuống động thông qua Dịch vụ Google Play.Mô hình được liên kết tĩnh với ứng dụng tại thời điểm xây dựng.
Kích thước ứng dụngKhoảng tăng kích thước khoảng 200 KB.Dung lượng 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 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ớ đưa kho lưu trữ Maven của Google vào cả hai phần buildscriptallprojects.

  2. Thêm các phần phụ thuộc cho thư viện Android ML Kit 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:

    Để nhóm mô hình với ứng dụng:

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

    Để sử dụng mô hình 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.2.0'
    }
    
  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 nội dung 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 tính khả dụng của mô hình một cách rõ ràng và yêu cầu tải xuống thông qua API ModuleInstallClient của Dịch vụ Google Play.

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

Nhập nguyên tắc về hình ảnh

  • Để Bộ công cụ máy học đọc đượ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 thể hiện bằng đủ dữ liệu pixel.

    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ã hóa, vì nhiều mã vạch hỗ trợ trọng tải có thể thay đổi. Nhìn chung, đơn vị nhỏ nhất và có ý nghĩa nhất của mã vạch phải là chiều rộng tối thiểu là 2 pixel và đối với mã 2 chiều là 2 pixel.

    Ví dụ: mã vạch EAN-13 được tạo thành từ các thanh và khoảng 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 í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 mật độ, chẳng hạn như PDF417, cần kích thước pixel lớn hơn để Bộ công cụ máy học có thể đọc được một cách đáng tin cậy. Ví dụ: một 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à chiều rộng tối thiểu là 1156 pixel.

  • Tiêu điểm ảnh không tốt có thể ảnh hưởng đến độ chính xác khi quét. Nếu ứng dụng của bạn không thu được kết quả có thể 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 để quét được mã vạch từ khoảng cách xa máy ảnh hơn.

    Tuy nhiên, trong các ứng dụng quan trọng về độ trễ, 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 phần 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 bạn biết mình muốn đọc định dạng mã vạch nào, 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 các đị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ợ:

  • Mã 128 (FORMAT_CODE_128)
  • Mã 39 (FORMAT_CODE_39)
  • Mã 93 (FORMAT_CODE_93)
  • Thanh 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)
  • Tiếng Aztec (FORMAT_AZTEC)
  • Ma trận dữ liệu (FORMAT_DATA_MATRIX)

Kể từ mô hình đi kèm 17.1.0 và mô hình 18.2.0 không gói, 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ể sử dụng tính năng này để tạo điều kiện cho việc phát hiện thêm, chẳng hạn như bằng cách phóng to máy ảnh để có được hình ảnh rõ ràng hơn về bất kỳ mã vạch nào trong hộp giới hạn được trả về.

Kotlin

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

Java

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

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 sử dụng thư viện máy ảnh cung cấp cho bạn độ xoay của hình ảnh, bạn có thể tính toán độ này từ độ xoay của thiết bị và hướng của cảm biến máy ảnh 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 vòng đế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ừ URI tệp, hãy truyền ngữ cảnh ứng dụng và URI tệp cho InputImage.fromFilePath(). Điều này sẽ hữu ích khi bạn sử dụng ý định ACTION_GET_CONTENT để nhắc người dùng chọn một hình ảnh từ ứng dụng thư viện.

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 hình ảnh như mô tả trước đó cho phương thức nhập media.Image. Sau đó, hãy tạo đối tượng InputImage kèm theo 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:

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 một đố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 này được biểu thị bằng một đối tượng Bitmap cùng với độ xoay.

3. Nhận một bản sao của 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. Nhận 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 chuyển đến trình nghe thành công. Mỗi đối tượng Barcode biểu thị 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 các toạ độ giới hạn của mã đó 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ã hóa bằng mã vạch, bạn có thể nhận được đối tượng chứa dữ liệu được 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 ứng dụng theo thời gian thực, hãy làm theo các hướng dẫn sau để đạt được tốc độ khung hình tốt nhất:

  • Không chụp ảnh đầu vào ở độ phân giải gốc của máy ảnh. Trên một số thiết bị, việc thu thập dữ liệu đầu vào ở độ phân giải gốc tạo ra hình ảnh cực lớn (10 megapixel) nên độ trễ rất kém mà không đem lại độ chính xác. Thay vào đó, chỉ yêu cầu kích thước từ máy ảnh bắt buộc để phát hiện mã vạch, thường không quá 2 megapixel.

    Nếu tốc độ qué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 tối thiểu về kích thước mã vạch nêu trên.

    Nếu bạn đang cố gắng nhận dạng mã vạch từ một chuỗi các khung hình video truyền trực tuyến, thì trình nhận dạng có thể tạo ra nhiều 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ùng giá trị thì mới có thể tự tin trả về kết quả phù hợp.

    Chữ số Checksum không được hỗ trợ cho ITF và CODE-39.

  • Nếu bạn sử dụng API Camera hoặc API camera2, hãy điều chỉnh các lệnh gọi đến trình phát hiện. Nếu khung video mới hoạt động trong khi trình 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 khởi động 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 lực ngược được đặt thành giá trị mặc định ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Việc này đảm bảo mỗi lần bạn chỉ gửi được một hình ảnh để phân tích. Nếu hệ thống phân tích nhiều hình ảnh hơn khi trình phân tích đang bận, chúng sẽ tự động được thả 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 được đó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 sử dụng đầu ra của trình phát hiện để chồng nội dung đồ hoạ lên hình ảnh đầu vào, trước tiên, hãy lấy kết quả từ Bộ công cụ máy học, sau đó kết xuất hình ảnh và lớp phủ trong một bước duy nhất. Thao tác này sẽ chỉ kết xuất với bề mặt hiển thị một lần cho mỗi khung đầu vào. Hãy xem ví dụ về các lớp CameraSourcePreview GraphicOverlay trong ứng dụng mẫu bắt đầu nhanh.
  • 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 API Máy ảnh cũ hơn, hãy chụp ảnh ở định dạng ImageFormat.NV21.