Gắn nhãn hình ảnh bằng Bộ công cụ máy học trên Android

Bạn có thể dùng Bộ công cụ máy học để gắn nhãn những đối tượng được nhận dạng trong một hình ảnh. Mô hình mặc định đi kèm với Bộ công cụ máy học hỗ trợ hơn 400 nhãn.

Có hai cách để tích hợp tính năng gắn nhãn hình ảnh: một mô hình đi kèm là một phần của ứng dụng và một mô hình chưa nhóm phụ thuộc vào Dịch vụ Google Play. Hai mô hình giống nhau. Nếu bạn chọn mô hình chưa nhóm, ứng dụng sẽ nhỏ hơn. Hãy xem bảng dưới đây để biết chi tiết.

Tính năngChưa nhómGộp chung
Triển khaiMô hình được tải xuống tự động thông qua Dịch vụ Google Play.Mô hình được liên kết tĩnh với thời gian tạo bản dựng của bạn.
Kích thước ứng dụngMức tăng kích thước khoảng 200 KB.Dung lượng tăng khoảng 5,7 MB.
Thời gian khởi chạyBạn có thể phải đợi mô hình tải xuống trước khi sử dụng lần đầu tiên.Mẫu có sẵn ngay lập tức

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 Android của Bộ công cụ máy học vào tệp gradle cấp ứng dụng của bạn, thường là app/build.gradle. Hãy chọn một trong những phần phụ thuộc sau đây dựa trên nhu cầu của bạn:

    Để đóng gói mô hình bằng ứng dụng:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:image-labeling:17.0.7'
    }
    

    Cách 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-image-labeling:16.0.8'
    }
    
  3. Nếu chọn sử dụng mô hình này 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="ica" >
          <!-- To use multiple models: android:value="ica,model2,model3" -->
    </application>
    

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

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

Giờ thì bạn đã sẵn sàng gắn nhãn cho hình ảnh.

1. Chuẩn bị hình ảnh nhập

Tạo đối tượng InputImage từ hình ảnh của bạn. Trình gắn nhãn hình ảnh sẽ chạy nhanh nhất khi bạn sử dụng Bitmap hoặc nếu bạn sử dụng API camera2, bạn nên sử dụng YUV_420_888 media.Image. Bạn nên sử dụng trình này khi có thể.

Bạn có thể tạo một đối tượng InputImage từ nhiều nguồn, mỗi nguồn được giải thích ở bên dưới.

Sử dụng media.Image

Để tạo một đối tượng InputImage 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à chế độ xoay của hình ảnh đến InputImage.fromMediaImage().

Nếu bạn sử dụng thư viện CameraX, thì các lớp OnImageCapturedListenerImageAnalysis.Analyzer sẽ tính toán giá trị xoay vòng cho bạn.

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 mức độ xoay của hình ảnh, bạn có thể tính toán mức độ đó 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 cho 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 chuyển ngữ cảnh ứng dụng và URI tệp cho InputImage.fromFilePath(). Tính năng này rất hữu ích khi bạn sử dụng ý định ACTION_GET_CONTENT để nhắc người dùng chọn hình ảnh trong ứng dụng thư viện ảnh.

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 đầu vào media.Image. Sau đó, 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ã hóa 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 một đối tượng InputImage từ đối tượng Bitmap, hãy khai báo 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.

2. Định cấu hình và chạy trình gắn nhãn hình ảnh

Để gắn nhãn các đối tượng trong một hình ảnh, hãy chuyển đối tượng InputImage đến phương thức ImageLabeler#39;s process.

  1. Trước tiên, hãy tải một thực thể của ImageLabeler.

    Nếu bạn muốn sử dụng trình gắn nhãn hình ảnh trên thiết bị, hãy khai báo sau:

Kotlin

// To use default options:
val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)

// Or, to set the minimum confidence required:
// val options = ImageLabelerOptions.Builder()
//     .setConfidenceThreshold(0.7f)
//     .build()
// val labeler = ImageLabeling.getClient(options)

Java

// To use default options:
ImageLabeler labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS);

// Or, to set the minimum confidence required:
// ImageLabelerOptions options =
//     new ImageLabelerOptions.Builder()
//         .setConfidenceThreshold(0.7f)
//         .build();
// ImageLabeler labeler = ImageLabeling.getClient(options);
  1. Sau đó, hãy truyền hình ảnh đến phương thức process():

Kotlin

labeler.process(image)
        .addOnSuccessListener { labels ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Java

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

3. Nhận thông tin về đối tượng được gắn nhãn

Nếu thao tác gắn nhãn hình ảnh thành công, danh sách các đối tượng ImageLabel sẽ được truyền đến trình nghe thành công. Mỗi đối tượng ImageLabel biểu thị một mục được gắn nhãn trong hình ảnh. Mô hình cơ sở hỗ trợ hơn 400 nhãn khác nhau. Bạn có thể xem mô tả văn bản của từng nhãn, lập chỉ mục tất cả các nhãn mà mô hình hỗ trợ và điểm số tin cậy của kết quả khớp. Ví dụ:

Kotlin

for (label in labels) {
    val text = label.text
    val confidence = label.confidence
    val index = label.index
}

Java

for (ImageLabel label : labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
    int index = label.getIndex();
}

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

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

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