Gắn nhãn hình ảnh bằng mô hình được huấn luyện bằng AutoML trên Android

Sau khi huấn luyện mô hình của riêng mình bằng AutoML Vision Edge, bạn có thể sử dụng mô hình đó trong ứng dụng của mình để gắn nhãn hình ảnh.

Trước khi bắt đầu

  1. Nếu bạn chưa thực hiện, hãy thêm Firebase vào dự án Android.
  2. Thêm các phần phụ thuộc cho thư viện Android của Bộ công cụ học máy vào tệp Gradle (ở cấp ứng dụng) của mô-đun (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-automl:18.0.5'
    }

1. Tải mô hình

Bộ công cụ học máy chạy các mô hình do AutoML tạo trên thiết bị. Tuy nhiên, bạn có thể định cấu hình Bộ công cụ học máy để tải mô hình từ xa từ Firebase, từ bộ nhớ cục bộ hoặc cả hai.

Bằng cách lưu trữ mô hình trên Firebase, bạn có thể cập nhật mô hình mà không cần phát hành phiên bản ứng dụng mới. Ngoài ra, bạn có thể sử dụng Remote ConfigA/B Testing để phân phát linh động các mô hình khác nhau cho các nhóm người dùng khác nhau.

Nếu chỉ cung cấp mô hình bằng cách lưu trữ mô hình đó trên Firebase và không đóng gói mô hình đó với ứng dụng, bạn có thể giảm kích thước tải xuống ban đầu của ứng dụng. Tuy nhiên, hãy lưu ý rằng nếu mô hình không được đóng gói với ứng dụng, thì mọi chức năng liên quan đến mô hình sẽ không hoạt động cho đến khi ứng dụng tải mô hình xuống lần đầu tiên.

Bằng cách đóng gói mô hình với ứng dụng, bạn có thể đảm bảo các tính năng học máy của ứng dụng vẫn hoạt động khi không có mô hình được lưu trữ trên Firebase.

Định cấu hình nguồn mô hình do Firebase lưu trữ

Để sử dụng mô hình được lưu trữ từ xa, hãy tạo một đối tượng FirebaseAutoMLRemoteModel, chỉ định tên mà bạn đã gán cho mô hình khi phát hành:

JavaKotlin
// Specify the name you assigned in the Firebase console.
FirebaseAutoMLRemoteModel remoteModel =
    new FirebaseAutoMLRemoteModel.Builder("your_remote_model").build();
// Specify the name you assigned in the Firebase console.
val remoteModel = FirebaseAutoMLRemoteModel.Builder("your_remote_model").build()

Sau đó, hãy bắt đầu tác vụ tải mô hình xuống, chỉ định các điều kiện mà bạn muốn cho phép tải xuống. Nếu mô hình không có trên thiết bị hoặc nếu có phiên bản mô hình mới hơn, thì tác vụ sẽ tải mô hình xuống không đồng bộ từ Firebase:

JavaKotlin
FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
        .requireWifi()
        .build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Success.
            }
        });
val conditions = FirebaseModelDownloadConditions.Builder()
    .requireWifi()
    .build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Success.
    }

Nhiều ứng dụng bắt đầu tác vụ tải xuống trong mã khởi chạy, nhưng bạn có thể làm như vậy bất cứ lúc nào trước khi cần sử dụng mô hình.

Định cấu hình nguồn mô hình cục bộ

Cách gói mô hình với ứng dụng:

  1. Trích xuất mô hình và siêu dữ liệu của mô hình đó từ tệp lưu trữ zip mà bạn đã tải xuống từ bảng điều khiển Firebase. Bạn nên sử dụng các tệp như khi tải xuống, không sửa đổi (bao gồm cả tên tệp).
  2. Đưa mô hình và các tệp siêu dữ liệu của mô hình vào gói ứng dụng:

    1. Nếu bạn không có thư mục tài sản trong dự án, hãy tạo thư mục đó bằng cách nhấp chuột phải vào thư mục app/, sau đó nhấp vào New > Folder > Assets Folder (Mới > Thư mục > Thư mục tài sản).
    2. Tạo một thư mục con trong thư mục tài sản để chứa các tệp mô hình.
    3. Sao chép các tệp model.tflite, dict.txtmanifest.json vào thư mục con (cả ba tệp phải nằm trong cùng một thư mục).
  3. Thêm nội dung sau vào tệp build.gradle của ứng dụng để đảm bảo Gradle không nén tệp mô hình khi tạo ứng dụng:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
    Tệp mô hình sẽ được đưa vào gói ứng dụng và cung cấp cho Bộ công cụ học máy dưới dạng một thành phần thô.
  4. Tạo đối tượng FirebaseAutoMLLocalModel, chỉ định đường dẫn đến tệp kê khai mô hình:
    JavaKotlin
    FirebaseAutoMLLocalModel localModel = new FirebaseAutoMLLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            .build();
    
    val localModel = FirebaseAutoMLLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            .build()
    

Tạo trình gắn nhãn hình ảnh từ mô hình

Sau khi bạn định cấu hình các nguồn mô hình, hãy tạo một đối tượng FirebaseVisionImageLabeler từ một trong các nguồn đó.

Nếu bạn chỉ có một mô hình được đóng gói cục bộ, bạn chỉ cần tạo một trình gắn nhãn từ đối tượng FirebaseAutoMLLocalModel và định cấu hình ngưỡng điểm số tin cậy mà bạn muốn yêu cầu (xem phần Đánh giá mô hình):

JavaKotlin
FirebaseVisionImageLabeler labeler;
try {
    FirebaseVisionOnDeviceAutoMLImageLabelerOptions options =
            new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
                    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                   // to determine an appropriate value.
                    .build();
    labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
    // ...
}
val options = FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0)  // Evaluate your model in the Firebase console
                                // to determine an appropriate value.
    .build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)

Nếu có một mô hình được lưu trữ từ xa, bạn sẽ phải kiểm tra để đảm bảo rằng mô hình đó đã được tải xuống trước khi chạy. Bạn có thể kiểm tra trạng thái của tác vụ tải mô hình xuống bằng phương thức isModelDownloaded() của trình quản lý mô hình.

Mặc dù bạn chỉ phải xác nhận điều này trước khi chạy trình gắn nhãn, nhưng nếu bạn có cả mô hình được lưu trữ từ xa và mô hình được đóng gói cục bộ, thì bạn nên thực hiện việc kiểm tra này khi tạo bản sao của trình gắn nhãn hình ảnh: tạo trình gắn nhãn từ mô hình từ xa nếu mô hình đó đã được tải xuống và từ mô hình cục bộ nếu không.

JavaKotlin
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener<Boolean>() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel);
                }
                FirebaseVisionOnDeviceAutoMLImageLabelerOptions options = optionsBuilder
                        .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                       // to determine an appropriate threshold.
                        .build();

                FirebaseVisionImageLabeler labeler;
                try {
                    labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
                } catch (FirebaseMLException e) {
                    // Error.
                }
            }
        });
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded -> 
    val optionsBuilder =
        if (isDownloaded) {
            FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel)
        } else {
            FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
        }
    // Evaluate your model in the Firebase console to determine an appropriate threshold.
    val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
    val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
}

Nếu chỉ có mô hình được lưu trữ từ xa, bạn nên tắt chức năng liên quan đến mô hình (ví dụ: làm mờ hoặc ẩn một phần giao diện người dùng) cho đến khi xác nhận rằng mô hình đã được tải xuống. Bạn có thể thực hiện việc này bằng cách đính kèm trình nghe vào phương thức download() của trình quản lý mô hình:

JavaKotlin
FirebaseModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void v) {
              // Download complete. Depending on your app, you could enable
              // the ML feature, or switch from the local model to the remote
              // model, etc.
            }
        });
FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.
    }

2. Chuẩn bị hình ảnh đầu vào

Sau đó, đối với mỗi hình ảnh mà bạn muốn gắn nhãn, hãy tạo một đối tượng FirebaseVisionImage bằng cách sử dụng một trong các tuỳ chọn được mô tả trong phần này và chuyển đối tượng đó đến một thực thể của FirebaseVisionImageLabeler (được mô tả trong phần tiếp theo).

Bạn có thể tạo đối tượng FirebaseVisionImage từ đối tượng media.Image, tệp trên thiết bị, mảng byte hoặc đối tượng Bitmap:

  • Để tạo đối tượng FirebaseVisionImage từ đối tượng media.Image, chẳng hạn như khi chụp ảnh từ máy ảnh của thiết bị, hãy truyền đối tượng media.Image và độ xoay của hình ảnh đến FirebaseVisionImage.fromMediaImage().

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

    JavaKotlin
    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
            // ...
        }
    }
    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 không sử dụng thư viện máy ảnh cung cấp độ xoay của hình ảnh, bạn có thể tính toán độ xoay đó từ độ xoay của thiết bị và hướng của cảm biến máy ảnh trong thiết bị:

    JavaKotlin
    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;
    }
    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 đó, truyền đối tượng media.Image và giá trị xoay vào FirebaseVisionImage.fromMediaImage():

    JavaKotlin
    FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
    val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
  • Để tạo đối tượng FirebaseVisionImage từ URI tệp, hãy truyền ngữ cảnh ứng dụng và URI tệp đến 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 một hình ảnh trong ứng dụng thư viện.
    JavaKotlin
    FirebaseVisionImage image;
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri);
    } catch (IOException e) {
        e.printStackTrace();
    }
    val image: FirebaseVisionImage
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri)
    } catch (e: IOException) {
        e.printStackTrace()
    }
  • Để tạo đối tượng FirebaseVisionImage từ ByteBuffer hoặc mảng byte, trước tiên, hãy tính toán độ xoay hình ảnh như mô tả ở trên cho dữ liệu đầu vào media.Image.

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

    JavaKotlin
    FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
            .setWidth(480)   // 480x360 is typically sufficient for
            .setHeight(360)  // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build();
    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:

    JavaKotlin
    FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
    // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
    val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
    // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
  • Cách tạo đối tượng FirebaseVisionImage từ đối tượng Bitmap:
    JavaKotlin
    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
    val image = FirebaseVisionImage.fromBitmap(bitmap)
    Hình ảnh do đối tượng Bitmap biểu thị phải thẳng đứng, không cần xoay thêm.

3. Chạy trình gắn nhãn hình ảnh

Để gắn nhãn cho các đối tượng trong hình ảnh, hãy truyền đối tượng FirebaseVisionImage vào phương thức processImage() của FirebaseVisionImageLabeler.

JavaKotlin
labeler.processImage(image)
        .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionImageLabel>>() {
            @Override
            public void onSuccess(List<FirebaseVisionImageLabel> labels) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });
labeler.processImage(image)
        .addOnSuccessListener { labels ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Nếu việc gắn nhãn hình ảnh thành công, một mảng các đối tượng FirebaseVisionImageLabel sẽ được truyền đến trình nghe thành công. Từ mỗi đối tượng, bạn có thể lấy thông tin về một đặc điểm được nhận dạng trong hình ảnh.

Ví dụ:

JavaKotlin
for (FirebaseVisionImageLabel label: labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
}
for (label in labels) {
    val text = label.text
    val confidence = label.confidence
}

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

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

Firebase ML lets you add powerful machine learning features to your app with ready-to-use APIs and support for custom model deployment.

Ngày cập nhật: Feb 28, 2025

Firebase ML lets you add powerful machine learning features to your app with ready-to-use APIs and support for custom model deployment.

Ngày cập nhật: Feb 28, 2025

ML Kit for Firebase provided ready-to-use ML solutions for app developers. New apps should use the standalone ML Kit library for on-device ML and Firebase ML for cloud-based ML.

Ngày cập nhật: Feb 28, 2025