אתם יכולים להשתמש ב-ML Kit כדי להוסיף בקלות לאפליקציה תכונות של פילוח נושאים.
| תכונה | פרטים |
|---|---|
| שם ה-SDK | play-services-mlkit-subject-segmentation |
| הטמעה | לא מקובץ: המודל יורד באופן דינמי באמצעות Google Play Services. |
| ההשפעה של גודל האפליקציה | הגדלה של כ-200KB. |
| זמן האתחול | יכול להיות שהמשתמשים יצטרכו לחכות עד שהמודל יסיים לרדת לפני השימוש הראשון. |
רוצה לנסות?
- כדי לראות דוגמה לשימוש ב-API הזה, אתם יכולים להתנסות באפליקציה לדוגמה.
לפני שמתחילים
- בקובץ
build.gradleברמת הפרויקט, צריך לכלול את מאגר ה-Maven של Google בקטעיםbuildscriptו-allprojects. - מוסיפים את התלות בספריית פילוח הנושאים של ML Kit לקובץ ה-Gradle ברמת האפליקציה של המודול, שבדרך כלל נמצא ב-
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.
אם לא מפעילים הורדות של מודלים בזמן ההתקנה או מבקשים הורדה מפורשת, המודל יורד בפעם הראשונה שמפעילים את ה-segmenter. בקשות שתשלחו לפני שההורדה תסתיים לא יניבו תוצאות.
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. קבלת התוצאה של פילוח הנושא
אחזור מסכות חזית ומפות סיביות (bitmap)
אחרי העיבוד, אפשר לאחזר את מסכת החזית של התמונה באמצעות קריאה ל-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 );
אפשר גם לאחזר מפת סיביות (bitmap) של החזית של התמונה באמצעות קריאה ל-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 פיקסלים.
- גם תמונה לא ממוקדת יכולה להשפיע על הדיוק. אם לא מתקבלות תוצאות קבילות, מבקשים מהמשתמש לצלם את התמונה מחדש.