ใช้ ML Kit เพื่อเพิ่มฟีเจอร์การแบ่งกลุ่มวัตถุลงในแอปได้อย่างง่ายดาย
ฟีเจอร์ | รายละเอียด |
---|---|
ชื่อ SDK | play-services-mlkit-subject-segmentation |
การใช้งาน | ไม่รวม: ระบบจะดาวน์โหลดโมเดลแบบไดนามิกโดยใช้บริการ Google Play |
ผลกระทบต่อขนาดแอป | ขนาดเพิ่มขึ้นประมาณ 200 KB |
เวลาเริ่มต้น | ผู้ใช้อาจต้องรอให้ระบบดาวน์โหลดโมเดลก่อนจึงจะใช้งานได้เป็นครั้งแรก |
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อดูตัวอย่างการใช้งาน API นี้
ก่อนเริ่มต้น
- ในไฟล์
build.gradle
ระดับโปรเจ็กต์ ให้ตรวจสอบว่าได้รวมที่เก็บ Maven ของ Google ไว้ในทั้งส่วนbuildscript
และallprojects
- เพิ่มทรัพยากร 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()
ซึ่งจะมีประโยชน์เมื่อคุณ
ใช้ACTION_GET_CONTENT
Intent เพื่อแจ้งให้ผู้ใช้เลือก
รูปภาพจากแอปแกลเลอรี
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
กำหนดตัวเลือก Segmenter
หากต้องการแบ่งกลุ่มรูปภาพ ให้สร้างอินสแตนซ์ของ 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()
สร้าง Subject Segmenter
เมื่อระบุตัวเลือก 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
Listsubjects = 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
Listbitmaps = new ArrayList<>(); for (Subject subject : subjects) { bitmaps.add(subject.getBitmap()); }
เคล็ดลับในการปรับปรุงประสิทธิภาพ
สําหรับเซสชันแอปแต่ละเซสชัน การอนุมานครั้งแรกมักจะช้ากว่าการอนุมานครั้งต่อๆ ไปเนื่องจากการเริ่มต้นโมเดล หากเวลาในการตอบสนองต่ำเป็นสิ่งสำคัญ ให้พิจารณา เรียกใช้การอนุมาน "ดัมมี่" ล่วงหน้า
คุณภาพของผลลัพธ์ขึ้นอยู่กับคุณภาพของรูปภาพอินพุต
- รูปภาพควรมีขนาดอย่างน้อย 512x512 พิกเซลเพื่อให้ ML Kit ได้ผลการแบ่งกลุ่มที่แม่นยำ
- โฟกัสของรูปภาพที่ไม่ดีอาจส่งผลต่อความถูกต้องได้เช่นกัน หากไม่ได้รับผลลัพธ์ที่ยอมรับได้ ให้ขอให้ผู้ใช้ถ่ายภาพอีกครั้ง