אפשר להשתמש ב-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.
אם לא מפעילים הורדות של מודלים בזמן ההתקנה או לא מבקשים הורדה מפורשת, המודל יורד בפעם הראשונה שמריצים את הכלי לפילוח. בקשות ששולחים לפני שההורדה מסתיימת לא מניבות תוצאות.
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
הגדרת האפשרויות של הכלי לפילוח
כדי לפלח את התמונה, קודם יוצרים מופע של SubjectSegmenterOptions
באופן הבא:
Kotlin
val options = SubjectSegmenterOptions.Builder() // enable options .build()
Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() // enable options .build();
הנה פירוט של כל אפשרות:
מסכת מהימנות של חזית התמונה
מסכת הביטחון של החזית מאפשרת להבחין בין הנושא בחזית לבין הרקע.
האפשרות Call enableForegroundConfidenceMask()
מאפשרת לאחזר בהמשך את מסכת החזית על ידי קריאה ל-getForegroundMask()
באובייקט SubjectSegmentationResult
שמוחזר אחרי עיבוד התמונה.
Kotlin
val options = SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build()
Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build();
מפת סיביות של החזית
באופן דומה, אפשר גם לקבל מפת סיביות של הנושא בחזית.
האפשרות Call 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
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 פיקסלים לפחות.
- גם פוקוס לא טוב של התמונה יכול להשפיע על הדיוק. אם התוצאות לא מספיק טובות, מבקשים מהמשתמש לצלם מחדש את התמונה.