השתמשו בערכת ML כדי להוסיף בקלות תכונות לפילוח לפי נושא לאפליקציה.
Feature | פרטים |
---|---|
שם ה-Sdk | play-services-mlkit-subject-segmentation |
הטמעה | לא בחבילה: מתבצעת הורדה דינמית של המודל באמצעות Google Play Services. |
ההשפעה של גודל האפליקציה | הגדלה של כ-200KB. |
זמן אתחול | ייתכן שהמשתמשים יצטרכו להמתין להורדת המודל לפני השימוש הראשון. |
אני רוצה לנסות
- אפשר לנסות את האפליקציה לדוגמה כדי לראות דוגמה לשימוש ב-API הזה.
לפני שמתחילים
- בקובץ
build.gradle
ברמת הפרויקט, חשוב לכלול את מאגר Maven של Google בקטעbuildscript
וגם בקטעallprojects
. - מוסיפים את התלות בספריית הפילוחים לפי נושאים של ML Kit לקובץ ההדרגתיות ברמת האפליקציה של המודול, שהוא בדרך כלל
app/build.gradle
:
dependencies {
implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}
כפי שצוין למעלה, המודל מסופק על ידי Google Play Services.
אחרי שמתקינים את האפליקציה מחנות Play, אפשר להגדיר שהאפליקציה תוריד את המודל למכשיר באופן אוטומטי. כדי לעשות זאת, צריך להוסיף את ההצהרה הבאה לקובץ 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 Services באמצעות 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
המוכן ל-method 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()); }
טיפים לשיפור הביצועים
לכל סשן באפליקציה, ההסקה הראשונה בדרך כלל איטית יותר מהמסקנות הבאות בעקבות אתחול המודל. אם זמן האחזור נמוך הוא קריטי, כדאי לקרוא להסקה 'דמה' מראש.
איכות התוצאות תלויה באיכות של תמונת הקלט:
- כדי ש-ML Kit תקבל תוצאת פילוח מדויקת, התמונה צריכה להיות בגודל של 512x512 פיקסלים לפחות.
- גם מיקוד לא טוב של התמונה יכול להשפיע על הדיוק. אם לא מתקבלות תוצאות קבילות, יש לבקש מהמשתמש לצלם מחדש את התמונה.