การแบ่งกลุ่มเรื่องด้วย ML Kit สำหรับ Android

ใช้ ML Kit เพื่อเพิ่มฟีเจอร์การแบ่งกลุ่มเนื้อหาในแอปได้อย่างง่ายดาย

ฟีเจอร์ รายละเอียด
ชื่อ SDK play-services-mlkit-subject-segmentation
การใช้งาน ไม่ได้รวมกลุ่ม: โมเดลจะดาวน์โหลดแบบไดนามิกโดยใช้บริการ Google Play
ผลกระทบต่อขนาดแอป ขนาดเพิ่มขึ้นประมาณ 200 KB
เวลาเริ่มต้น ผู้ใช้อาจต้องรอให้ดาวน์โหลดโมเดลก่อนที่จะใช้งานครั้งแรก

ลองเลย

ก่อนเริ่มต้น

  1. ในไฟล์ build.gradle ระดับโปรเจ็กต์ โปรดตรวจสอบว่าได้รวมที่เก็บ Maven ของ Google ไว้ในส่วน buildscript และ allprojects แล้ว
  2. เพิ่มทรัพยากร Dependency สำหรับไลบรารีการแบ่งกลุ่มหัวข้อของ ML Kit ลงในไฟล์ Gradle ระดับแอปของโมดูล ซึ่งปกติจะอยู่ที่ app/build.gradle ดังนี้
dependencies {
   implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}

ดังที่กล่าวไว้ข้างต้น โมเดลดังกล่าวให้บริการโดยบริการ Google Play คุณสามารถกำหนดค่าแอปให้ดาวน์โหลดโมเดลลงในอุปกรณ์โดยอัตโนมัติหลังจากที่ติดตั้งแอปจาก Play Store แล้ว ในการดำเนินการดังกล่าว ให้เพิ่มการประกาศต่อไปนี้ลงในไฟล์ AndroidManifest.xml ของแอป

<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>

นอกจากนี้คุณยังตรวจสอบความพร้อมใช้งานของโมเดลอย่างชัดแจ้งและขอดาวน์โหลดผ่านบริการ Google Play ได้ด้วย ModuleInstallClient API

หากคุณไม่เปิดใช้การดาวน์โหลดโมเดลเวลาติดตั้งหรือขอดาวน์โหลดอย่างชัดแจ้ง ระบบจะดาวน์โหลดโมเดลในครั้งแรกที่คุณเรียกใช้การแบ่งกลุ่ม คำขอที่คุณส่งก่อนการดาวน์โหลดเสร็จสมบูรณ์จะไม่มีผลลัพธ์ใดๆ

1. เตรียมรูปภาพอินพุต

หากต้องการแบ่งกลุ่มรูปภาพ ให้สร้างออบเจ็กต์ InputImage จาก Bitmap, media.Image, ByteBuffer, ไบต์อาร์เรย์ หรือไฟล์ในอุปกรณ์

คุณสร้างออบเจ็กต์ InputImage จากแหล่งที่มาที่ต่างกันได้ โดยออบเจ็กต์แต่ละรายการมีคำอธิบายอยู่ด้านล่าง

กำลังใช้ media.Image

หากต้องการสร้างออบเจ็กต์ InputImage จากออบเจ็กต์ media.Image เช่น เมื่อคุณจับภาพจากกล้องของอุปกรณ์ ให้ส่งวัตถุ media.Image และการหมุนรูปภาพไปยัง InputImage.fromMediaImage()

หากคุณใช้ไลบรารี CameraX คลาส OnImageCapturedListener และ ImageAnalysis.Analyzer จะคำนวณค่าการหมุนเวียนให้คุณ

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

หากคุณไม่ได้ใช้คลังภาพกล้องที่ให้องศาการหมุนของรูปภาพ คุณจะคํานวณจากองศาการหมุนของอุปกรณ์และการวางแนวของเซ็นเซอร์กล้องในอุปกรณ์ได้ ดังนี้

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

จากนั้นส่งออบเจ็กต์ media.Image และค่าองศาการหมุนไปยัง InputImage.fromMediaImage()

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

การใช้ URI ของไฟล์

หากต้องการสร้างออบเจ็กต์ InputImage จาก URI ไฟล์ ให้ส่งบริบทของแอปและ URI ของไฟล์ไปยัง InputImage.fromFilePath() ซึ่งจะเป็นประโยชน์เมื่อคุณใช้ Intent ACTION_GET_CONTENT เพื่อแจ้งให้ผู้ใช้เลือกรูปภาพจากแอปแกลเลอรี

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();
}

การใช้ ByteBuffer หรือ ByteArray

หากต้องการสร้างออบเจ็กต์ InputImage จาก ByteBuffer หรือ ByteArray ก่อนอื่นให้คำนวณระดับการหมุนรูปภาพตามที่อธิบายไว้ก่อนหน้านี้สำหรับอินพุต media.Image จากนั้นสร้างออบเจ็กต์ InputImage ที่มีบัฟเฟอร์หรืออาร์เรย์ พร้อมกับความสูง ความกว้าง รูปแบบการเข้ารหัสสี และระดับการหมุนของรูปภาพ ดังนี้

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

กำลังใช้ Bitmap

หากต้องการสร้างออบเจ็กต์ InputImage จากออบเจ็กต์ Bitmap ให้ประกาศต่อไปนี้

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

รูปภาพจะแสดงด้วยวัตถุ Bitmap พร้อมกับองศาการหมุน

2. สร้างอินสแตนซ์ของ SubjectSegmenter

กำหนดตัวเลือกการแบ่งกลุ่มลูกค้า

หากต้องการแบ่งกลุ่มรูปภาพ ให้สร้างอินสแตนซ์ของ SubjectSegmenterOptions ก่อนได้ดังนี้

Kotlin

val options = SubjectSegmenterOptions.Builder()
       // enable options
       .build()

Java

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

รายละเอียดของตัวเลือกแต่ละรายการมีดังนี้

มาสก์ความเชื่อมั่นในเบื้องหน้า

มาสก์ความเชื่อมั่นในเบื้องหน้าช่วยให้คุณแยกแยะวัตถุที่อยู่เบื้องหน้ากับพื้นหลังได้

การเรียกใช้ enableForegroundConfidenceMask() ในตัวเลือกจะช่วยให้คุณเรียกข้อมูลมาสก์เบื้องหน้าได้ในภายหลังด้วยการเรียกใช้ getForegroundMask() ในออบเจ็กต์ SubjectSegmentationResult ที่แสดงผลหลังจากประมวลผลรูปภาพ

Kotlin

val options = SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build()

Java

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build();
บิตแมปเบื้องหน้า

ในทำนองเดียวกัน คุณยังสามารถรับบิตแมปของวัตถุที่อยู่เบื้องหน้าได้ด้วย

การเรียกใช้ enableForegroundBitmap() ในตัวเลือกจะช่วยให้คุณเรียกข้อมูลบิตแมปเบื้องหน้าได้ในภายหลังด้วยการเรียกใช้ getForegroundBitmap() ในออบเจ็กต์ SubjectSegmentationResult ที่แสดงผลหลังจากประมวลผลรูปภาพ

Kotlin

val options = SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build()

Java

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build();
หน้ากากวัดความเชื่อมั่นหลายวิชา

เช่นเดียวกับตัวเลือกเบื้องหน้า คุณสามารถใช้ SubjectResultOptions เพื่อเปิดใช้มาสก์ความเชื่อมั่นสำหรับหัวเรื่องที่ทำงานอยู่เบื้องหน้าแต่ละรายการดังนี้

Kotlin

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

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

Java

SubjectResultOptions subjectResultOptions =
        new SubjectSegmenterOptions.SubjectResultOptions.Builder()
            .enableConfidenceMask()
            .build()

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
      .enableMultipleSubjects(subjectResultOptions)
      .build()
บิตแมปหลายเรื่อง

และในทำนองเดียวกัน คุณสามารถเปิดใช้บิตแมปสำหรับแต่ละเรื่องได้ ดังนี้

Kotlin

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

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

Java

SubjectResultOptions subjectResultOptions =
      new SubjectSegmenterOptions.SubjectResultOptions.Builder()
        .enableSubjectBitmap()
        .build()

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

สร้างการแบ่งกลุ่มเรื่อง

เมื่อระบุตัวเลือก SubjectSegmenterOptions แล้ว ให้สร้างอินสแตนซ์ SubjectSegmenter ที่เรียกใช้ getClient() และส่งตัวเลือกเป็นพารามิเตอร์ ดังนี้

Kotlin

val segmenter = SubjectSegmentation.getClient(options)

Java

SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

3. ประมวลผลรูปภาพ

ส่งออบเจ็กต์ InputImage ที่จัดเตรียมไว้ไปยังเมธอด process ของ SubjectSegmenter

Kotlin

segmenter.process(inputImage)
    .addOnSuccessListener { result ->
        // Task completed successfully
        // ...
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }

Java

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. ดูผลลัพธ์การแบ่งกลุ่มเรื่อง

เรียกดูมาสก์เบื้องหน้าและบิตแมป

เมื่อประมวลผลแล้ว คุณจะดึงข้อมูลมาสก์เบื้องหน้าสำหรับการเรียกรูปภาพ getForegroundConfidenceMask() ได้ดังนี้

Kotlin

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
)

Java

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

คุณยังเรียกบิตแมปของเบื้องหน้าของอิมเมจที่เรียกใช้ getForegroundBitmap() ได้ด้วย โดยทำดังนี้

Kotlin

val foregroundBitmap = result.foregroundBitmap

Java

Bitmap foregroundBitmap = result.getForegroundBitmap();

เรียกข้อมูลมาสก์และบิตแมปของแต่ละวิชา

ในทำนองเดียวกัน คุณดึงข้อมูลมาสก์สำหรับวัตถุที่แบ่งเป็นกลุ่มๆ ได้โดยเรียกใช้ getConfidenceMask() ในแต่ละเรื่องดังนี้

Kotlin

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
)

Java

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

นอกจากนี้ คุณยังสามารถเข้าถึงบิตแมปของหัวเรื่องที่แบ่งเป็นกลุ่มๆ ได้ดังนี้

Kotlin

val bitmaps = mutableListOf()
for (subject in subjects) {
  bitmaps.add(subject.bitmap)
}

Java

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

เคล็ดลับในการปรับปรุงประสิทธิภาพ

สำหรับแต่ละเซสชันแอป การอนุมานครั้งแรกมักจะช้ากว่าการอนุมานที่ตามมาเนื่องจากมีการเริ่มต้นโมเดล หากเวลาในการตอบสนองต่ำคือสิ่งจำเป็น ให้เรียกใช้การอนุมานแบบ "จำลอง" ล่วงหน้า

คุณภาพของผลลัพธ์ขึ้นอยู่กับคุณภาพของรูปภาพที่ป้อน ดังนี้

  • รูปภาพควรมีขนาดอย่างน้อย 512x512 พิกเซล เพื่อให้ ML Kit ได้ผลลัพธ์การแบ่งกลุ่มที่แม่นยำ
  • การโฟกัสรูปภาพที่ไม่ดีอาจส่งผลต่อความแม่นยำได้เช่นกัน หากคุณไม่ได้รับผลลัพธ์ที่ยอมรับ โปรดขอให้ผู้ใช้เรียกคืนรูปภาพ