Segmentasi subjek dengan ML Kit untuk Android

Gunakan ML Kit untuk menambahkan fitur segmentasi subjek ke aplikasi Anda dengan mudah.

Fitur Detail
Nama sdk play-services-mlkit-subject-segmentation
Penerapan Tidak dipaketkan: model didownload secara dinamis menggunakan layanan Google Play.
Dampak ukuran aplikasi Peningkatan ukuran ~200 KB.
Waktu inisialisasi Pengguna mungkin harus menunggu model didownload sebelum digunakan pertama kali.

Cobalah

Sebelum memulai

  1. Dalam file build.gradle level project, pastikan Anda menyertakan repositori Maven Google di bagian buildscript dan allprojects.
  2. Tambahkan dependensi untuk library segmentasi subjek ML Kit ke file gradle level aplikasi modul Anda, biasanya app/build.gradle:
dependencies {
   implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}

Seperti yang disebutkan di atas, model ini disediakan oleh layanan Google Play. Anda dapat mengonfigurasi aplikasi untuk otomatis mendownload model ke perangkat setelah aplikasi Anda diinstal dari Play Store. Untuk melakukannya, tambahkan ke file AndroidManifest.xml aplikasi Anda:

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

Anda juga dapat memeriksa ketersediaan model secara eksplisit dan meminta download melalui layanan Google Play dengan ModuleInstallClient API.

Jika Anda tidak mengaktifkan download model waktu penginstalan atau meminta download eksplisit model diunduh saat pertama kali Anda menjalankan pengelompokkan. Permintaan yang Anda buat sebelum pengunduhan selesai, tidak akan memberikan hasil.

1. Menyiapkan gambar input

Untuk melakukan segmentasi pada gambar, buat objek InputImage dari Bitmap, media.Image, ByteBuffer, array byte, atau file di perangkat.

Anda dapat membuat InputImage dari berbagai sumber, masing-masing akan dijelaskan di bawah ini.

Menggunakan media.Image

Untuk membuat InputImage dari objek media.Image, seperti saat Anda mengambil gambar dari kamera perangkat, teruskan objek media.Image dan objek rotasi ke InputImage.fromMediaImage().

Jika Anda menggunakan library CameraX, OnImageCapturedListener dan Class ImageAnalysis.Analyzer menghitung nilai rotasi keamanan untuk Anda.

KotlinJava
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
            // ...
        }
    }
}
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
          // ...
        }
    }
}

Jika Anda tidak menggunakan pustaka kamera yang memberi derajat rotasi gambar, Anda bisa menghitungnya dari derajat rotasi perangkat dan orientasi kamera sensor di perangkat:

KotlinJava
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
}
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;
}

Lalu, teruskan objek media.Image dan nilai derajat rotasi ke InputImage.fromMediaImage():

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

Menggunakan URI file

Untuk membuat InputImage dari URI file, teruskan konteks aplikasi dan URI file ke InputImage.fromFilePath(). Hal ini berguna ketika Anda gunakan intent ACTION_GET_CONTENT untuk meminta pengguna memilih gambar dari aplikasi galeri mereka.

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

Menggunakan ByteBuffer atau ByteArray

Untuk membuat InputImage dari ByteBuffer atau ByteArray, hitung gambar terlebih dahulu derajat rotasi seperti yang dijelaskan sebelumnya untuk input media.Image. Lalu, buat objek InputImage dengan buffer atau array, beserta elemen tinggi, lebar, format encoding warna, dan derajat rotasi:

KotlinJava
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
)
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
);

Menggunakan Bitmap

Untuk membuat InputImage dari objek Bitmap, buat deklarasi berikut:

KotlinJava
val image = InputImage.fromBitmap(bitmap, 0)
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

Gambar direpresentasikan oleh objek Bitmap bersama dengan derajat rotasi.

2. Membuat instance SubjectSegmenter

Menentukan opsi segmentasi

Untuk menyegmentasikan gambar Anda, pertama-tama buat instance SubjectSegmenterOptions sebagai ikuti:

KotlinJava
val options = SubjectSegmenterOptions.Builder()
       // enable options
       .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        // enable options
        .build();

Berikut detail dari setiap opsi:

Masker keyakinan latar depan

Masker keyakinan latar depan memungkinkan Anda membedakan subjek latar depan dari latar belakang.

Panggil enableForegroundConfidenceMask() dalam opsi yang memungkinkan Anda mengambil mask latar depan dengan memanggil getForegroundMask() di Objek SubjectSegmentationResult yang ditampilkan setelah memproses gambar.

KotlinJava
val options = SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build();
Bitmap latar depan

Demikian pula, Anda juga bisa mendapatkan bitmap subjek latar depan.

Panggil enableForegroundBitmap() dalam opsi yang memungkinkan Anda mengambilnya nanti bitmap latar depan dengan memanggil getForegroundBitmap() Objek SubjectSegmentationResult yang ditampilkan setelah memproses gambar.

KotlinJava
val options = SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build();
Masker keyakinan multi-subjek

Seperti opsi latar depan, Anda dapat menggunakan SubjectResultOptions untuk mengaktifkan masker untuk setiap subjek latar depan sebagai berikut:

KotlinJava
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
    .enableConfidenceMask()
    .build()

val options = SubjectSegmenterOptions.Builder()
    .enableMultipleSubjects(subjectResultOptions)
    .build()
SubjectResultOptions subjectResultOptions =
        new SubjectSegmenterOptions.SubjectResultOptions.Builder()
            .enableConfidenceMask()
            .build()

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
      .enableMultipleSubjects(subjectResultOptions)
      .build()
Bitmap multi-subjek

Begitu juga, Anda dapat mengaktifkan bitmap untuk setiap subjek:

KotlinJava
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
    .enableSubjectBitmap()
    .build()

val options = SubjectSegmenterOptions.Builder()
    .enableMultipleSubjects(subjectResultOptions)
    .build()
SubjectResultOptions subjectResultOptions =
      new SubjectSegmenterOptions.SubjectResultOptions.Builder()
        .enableSubjectBitmap()
        .build()

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
      .enableMultipleSubjects(subjectResultOptions)
      .build()

Membuat pembagi subjek

Setelah menentukan opsi SubjectSegmenterOptions, buat Instance SubjectSegmenter yang memanggil getClient() dan meneruskan opsi sebagai :

KotlinJava
val segmenter = SubjectSegmentation.getClient(options)
SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

3. Memproses gambar

Lulus InputImage yang sudah disiapkan ke metode process SubjectSegmenter:

KotlinJava
segmenter.process(inputImage)
    .addOnSuccessListener { result ->
        // Task completed successfully
        // ...
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }
segmenter.process(inputImage)
    .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(SubjectSegmentationResult result) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

4. Dapatkan hasil segmentasi subjek

Mengambil mask latar depan dan bitmap

Setelah diproses, Anda dapat mengambil mask latar depan untuk panggilan gambar getForegroundConfidenceMask() sebagai berikut:

KotlinJava
val colors = IntArray(image.width * image.height)

val foregroundMask = result.foregroundConfidenceMask
for (i in 0 until image.width * image.height) {
  if (foregroundMask[i] > 0.5f) {
    colors[i] = Color.argb(128, 255, 0, 255)
  }
}

val bitmapMask = Bitmap.createBitmap(
  colors, image.width, image.height, Bitmap.Config.ARGB_8888
)
int[] colors = new int[image.getWidth() * image.getHeight()];

FloatBuffer foregroundMask = result.getForegroundConfidenceMask();
for (int i = 0; i < image.getWidth() * image.getHeight(); i++) {
  if (foregroundMask.get() > 0.5f) {
    colors[i] = Color.argb(128, 255, 0, 255);
  }
}

Bitmap bitmapMask = Bitmap.createBitmap(
      colors, image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888
);

Anda juga dapat mengambil bitmap latar depan gambar yang memanggil getForegroundBitmap():

KotlinJava
val foregroundBitmap = result.foregroundBitmap
Bitmap foregroundBitmap = result.getForegroundBitmap();

Mengambil mask dan bitmap untuk setiap subjek

Demikian pula, Anda bisa mengambil {i>mask<i} untuk subjek yang tersegmentasi dengan memanggil getConfidenceMask() untuk setiap subjek sebagai berikut:

KotlinJava
val subjects = result.subjects

val colors = IntArray(image.width * image.height)
for (subject in subjects) {
  val mask = subject.confidenceMask
  for (i in 0 until subject.width * subject.height) {
    val confidence = mask[i]
    if (confidence > 0.5f) {
      colors[image.width * (subject.startY - 1) + subject.startX] =
          Color.argb(128, 255, 0, 255)
    }
  }
}

val bitmapMask = Bitmap.createBitmap(
  colors, image.width, image.height, Bitmap.Config.ARGB_8888
)
List subjects = result.getSubjects();

int[] colors = new int[image.getWidth() * image.getHeight()];
for (Subject subject : subjects) {
  FloatBuffer mask = subject.getConfidenceMask();
  for (int i = 0; i < subject.getWidth() * subject.getHeight(); i++) {
    float confidence = mask.get();
    if (confidence > 0.5f) {
      colors[width * (subject.getStartY() - 1) + subject.getStartX()]
          = Color.argb(128, 255, 0, 255);
    }
  }
}

Bitmap bitmapMask = Bitmap.createBitmap(
  colors, image.width, image.height, Bitmap.Config.ARGB_8888
);

Anda juga dapat mengakses bitmap dari setiap subjek yang disegmentasikan seperti berikut:

KotlinJava
val bitmaps = mutableListOf()
for (subject in subjects) {
  bitmaps.add(subject.bitmap)
}
List bitmaps = new ArrayList<>();
for (Subject subject : subjects) {
  bitmaps.add(subject.getBitmap());
}

Tips untuk meningkatkan performa

Untuk setiap sesi aplikasi, inferensi pertama sering kali lebih lambat daripada berikutnya inferensi karena inisialisasi model. Jika latensi rendah sangat penting, pertimbangkan memanggil "dummy" inferensi sebelumnya.

Kualitas hasil bergantung pada kualitas gambar input:

  • Agar ML Kit mendapatkan hasil segmentasi yang akurat, gambar harus berukuran minimal 512x512 piksel.
  • Fokus gambar yang buruk juga dapat memengaruhi akurasi. Jika Anda tidak mendapatkan hasil yang dapat diterima, minta pengguna untuk mengambil ulang gambar.