Catch up on highlights from Firebase at Google I/O 2023. Learn more

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 ML Kit để nhận dạng và giải mã mã vạch.

Trước khi bắt đầu

  1. Nếu bạn chưa có, hãy thêm Firebase vào dự án Android của bạn .
  2. Thêm phần phụ thuộc cho các thư viện ML Kit Android vào tệp Gradle mô-đun (cấp ứng dụng) của bạn (thường là app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-barcode-model:16.0.1'
    }
    

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

  • Để ML Kit có thể đọc chính xác mã vạch, 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 về dữ liệu pixel cụ thể phụ thuộc vào cả loại mã vạch và lượng dữ liệu được mã hóa trong đó (vì hầu hết các mã vạch đều hỗ trợ trọng tải có độ dài thay đổi). Nói chung, đơn vị có ý nghĩa nhỏ nhất của mã vạch phải rộng ít nhất 2 pixel (và đối với mã 2 chiều, cao 2 pixel).

    Ví dụ: mã vạch EAN-13 được tạo thành từ các thanh và khoảng trố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 thanh và khoảng trống ít nhất là 2, 4, 6 và Rộng 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 nhất 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 là rộng ít nhất 1156 pixel.

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

  • Đối với các ứng dụng điển hình, 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), giúp mã vạch có thể phát hiện được từ khoảng cách xa máy ảnh hơn.

    Tuy nhiên, trong các ứng dụng có độ trễ là 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 thời gian thực .

1. Định cấu hình máy dò mã vạch

Nếu bạn biết định dạng mã vạch nào bạn muốn đọc, bạn có thể cải thiện tốc độ của máy dò mã vạch bằng cách định cấu hình 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 xây dựng đối tượng FirebaseVisionBarcodeDetectorOptions như trong ví dụ sau:

Java

FirebaseVisionBarcodeDetectorOptions options =
        new FirebaseVisionBarcodeDetectorOptions.Builder()
        .setBarcodeFormats(
                FirebaseVisionBarcode.FORMAT_QR_CODE,
                FirebaseVisionBarcode.FORMAT_AZTEC)
        .build();

Kotlin+KTX

val options = FirebaseVisionBarcodeDetectorOptions.Builder()
        .setBarcodeFormats(
                FirebaseVisionBarcode.FORMAT_QR_CODE,
                FirebaseVisionBarcode.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 )
  • 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 )
  • Ma trận dữ liệu ( FORMAT_DATA_MATRIX )

2. Chạy máy dò mã vạch

Để nhận dạng mã vạch trong hình ảnh, hãy tạo đối tượng FirebaseVisionImage từ Bitmap , media.Image , ByteBuffer , mảng byte hoặc tệp trên thiết bị. Sau đó, chuyển đối tượng FirebaseVisionImage vào phương thức detectInImage FirebaseVisionBarcodeDetector

  1. Tạo một đối tượng FirebaseVisionImage từ hình ảnh của bạn.

    • Để tạo đối tượng FirebaseVisionImage từ đối tượng media.Image , chẳng hạn như khi chụp ảnh từ camera của thiết bị, hãy chuyển đối tượng media.Image và vòng quay của hình ảnh tới FirebaseVisionImage.fromMediaImage() .

      Nếu bạn sử dụng thư viện CameraX , các lớp OnImageCapturedListenerImageAnalysis.Analyzer tính toán giá trị xoay vòng cho bạn, vì vậy bạn chỉ cần chuyển đổi xoay vòng thành một trong các hằng số ROTATION_ của ML Kit trước khi gọi FirebaseVisionImage.fromMediaImage() :

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }
      

      Kotlin+KTX

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }
      

      Nếu bạn không sử dụng thư viện máy ảnh cung cấp cho bạn vòng quay của hình ảnh, bạn có thể tính toán nó từ vòng quay của thiết bị và hướng của cảm biến máy ảnh trong thiết bị:

      Java

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * 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, Context context)
              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);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      Kotlin+KTX

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }
      /**
       * 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, context: Context): 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)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      Sau đó, chuyển đối tượng media.Image và giá trị xoay vòng vào FirebaseVisionImage.fromMediaImage() :

      Java

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

      Kotlin+KTX

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Để tạo đối tượng FirebaseVisionImage từ URI tệp, hãy chuyển ngữ cảnh ứng dụng và URI tệp vào FirebaseVisionImage.fromFilePath() . Điều này hữu ích khi bạn sử dụng ý định ACTION_GET_CONTENT để nhắc người dùng chọn hình ảnh từ ứng dụng thư viện của họ.

      Java

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

      Kotlin+KTX

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • Để tạo một đối tượng FirebaseVisionImage từ một ByteBuffer hoặc một mảng byte, trước tiên hãy tính toán vòng quay hình ảnh như được mô tả ở trên cho đầu vào media.Image .

      Sau đó, tạo một đối tượng FirebaseVisionImageMetadata có chứa chiều cao, chiều rộng, định dạng mã hóa màu và xoay của hình ảnh:

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin+KTX

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      Sử dụng bộ đệm hoặc mảng và đối tượng siêu dữ liệu, để tạo đối tượng FirebaseVisionImage :

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
      // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • Để tạo đối tượng FirebaseVisionImage từ đối tượng Bitmap :

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Hình ảnh được đại diện bởi đối tượng Bitmap phải thẳng đứng, không cần xoay thêm.

  2. Nhận một phiên bản của FirebaseVisionBarcodeDetector :

    Java

    FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
            .getVisionBarcodeDetector();
    // Or, to specify the formats to recognize:
    // FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
    //        .getVisionBarcodeDetector(options);

    Kotlin+KTX

    val detector = FirebaseVision.getInstance()
            .visionBarcodeDetector
    // Or, to specify the formats to recognize:
    // val detector = FirebaseVision.getInstance()
    //        .getVisionBarcodeDetector(options)
  3. Cuối cùng, chuyển hình ảnh đến phương thức detectInImage :

    Java

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

    Kotlin+KTX

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

3. Nhận thông tin từ mã vạch

Nếu hoạt động nhận dạng mã vạch thành công, danh sách các đối tượng FirebaseVisionBarcode sẽ được chuyển đến trình nghe thành công. Mỗi đối tượng FirebaseVisionBarcode đạ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 tọa độ giới hạn của nó trong hình ảnh đầu vào, cũng như dữ liệu thô được mã hóa bởi mã vạch. Ngoài ra, nếu máy dò mã vạch có thể xác định loại dữ liệu được mã hóa bởi mã vạch, bạn có thể nhận được một đối tượng chứa dữ liệu được phân tích cú pháp.

Ví dụ:

Java

for (FirebaseVisionBarcode 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 FirebaseVisionBarcode.TYPE_WIFI:
            String ssid = barcode.getWifi().getSsid();
            String password = barcode.getWifi().getPassword();
            int type = barcode.getWifi().getEncryptionType();
            break;
        case FirebaseVisionBarcode.TYPE_URL:
            String title = barcode.getUrl().getTitle();
            String url = barcode.getUrl().getUrl();
            break;
    }
}

Kotlin+KTX

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) {
        FirebaseVisionBarcode.TYPE_WIFI -> {
            val ssid = barcode.wifi!!.ssid
            val password = barcode.wifi!!.password
            val type = barcode.wifi!!.encryptionType
        }
        FirebaseVisionBarcode.TYPE_URL -> {
            val title = barcode.url!!.title
            val url = barcode.url!!.url
        }
    }
}

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

Nếu bạn muốn quét mã vạch trong ứng dụng 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 chụp đầu vào ở độ phân giải gốc của máy ảnh. Trên một số thiết bị, chụp đầu vào ở độ phân giải gốc tạo ra hình ảnh cực lớn (10+ megapixel), dẫn đến độ trễ rất kém mà không có lợi cho độ chính xác. Thay vào đó, chỉ yêu cầu kích thước từ máy ảnh được yêu cầu để phát hiện mã vạch: thường không quá 2 megapixel.

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

  • Các cuộc gọi từ bướm ga đến máy dò. Nếu có một khung video mới trong khi trình dò ​​đang chạy, hãy thả khung đó xuống.
  • Nếu bạn đang sử dụng đầu ra của máy dò để phủ đồ họa lên hình ảnh đầu vào, trước tiên hãy lấy kết quả từ Bộ công cụ ML, sau đó hiển thị hình ảnh và lớp phủ trong một bước duy nhất. Bằng cách đó, bạn chỉ hiển thị trên bề mặt hiển thị một lần cho mỗi khung hình đầu vào.
  • 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 Camera cũ hơn, hãy chụp ảnh ở định dạng ImageFormat.NV21 .