אתם יכולים להשתמש ב-ML Kit כדי לזהות ישויות בתמונה ולתייג אותן. ה-API הזה תומך במגוון רחב של מודלים לסיווג תמונות בהתאמה אישית. נשמח אם תוכלו לסמן בכוכב הנחיות לגבי מודלים מותאמים אישית עם ML Kit תאימות למודלים, היכן למצוא מודלים שעברו אימון מראש, ואיך לאמן את המודלים שלכם.
יש שתי דרכים לשלב תוויות לתמונות עם מודלים מותאמים אישית: על ידי יצירת קיבוץ את צינור עיבוד הנתונים כחלק מהאפליקציה, או על ידי שימוש בצינור עיבוד נתונים לא ארוז ב-Google Play Services. אם תבחרו את צינור עיבוד הנתונים הלא ארוז, האפליקציה שלכם קטן יותר. הפרטים מופיעים בטבלה שבהמשך.
בחבילה | לא חלק מהחבילה | |
---|---|---|
שם הספרייה | com.google.mlkit:image-labeling-custom | com.google.android.gms:play-services-mlkit-image-labeling-custom |
הטמעה | צינור עיבוד הנתונים מקושר באופן סטטי לאפליקציה בזמן ה-build. | מתבצעת הורדה דינמית של צינור עיבוד הנתונים דרך Google Play Services. |
גודל האפליקציה | הגדלה של כ-3.8MB. | הגדלה של כ-200KB. |
זמן האתחול | צינור עיבוד הנתונים זמין באופן מיידי. | יכול להיות שתצטרכו להמתין להורדת צינור עיבוד הנתונים לפני השימוש הראשון. |
שלב במחזור החיים של API | זמינות לכלל המשתמשים (GA) | בטא |
יש שתי דרכים לשלב מודל מותאם אישית: לקבץ את המודל באמצעות להוסיף אותו לתיקיית הנכסים של האפליקציה, או להוריד אותו באופן דינמי. מ-Firebase. בטבלה הבאה ניתן לראות השוואה בין שתי האפשרויות האלה.
מודל בחבילה | מודל מתארח |
---|---|
המודל הוא חלק מה-APK של האפליקציה, ולכן הוא מגדיל את הגודל שלו. | המודל אינו חלק מה-APK שלך. כדי לארח את הסרטון, צריך להעלות אותו אל למידת מכונה ב-Firebase. |
המודל זמין באופן מיידי, גם כשמכשיר Android במצב אופליין | הורדת המודל מתבצעת על פי דרישה |
אין צורך בפרויקט Firebase | נדרש פרויקט Firebase |
צריך לפרסם מחדש את האפליקציה כדי לעדכן את המודל | דחיפת עדכוני מודל בלי לפרסם מחדש את האפליקציה |
אין בדיקות A/B מובנות | לבצע בדיקת A/B קלה ופשוטה באמצעות הגדרת תצורה מרחוק ב-Firebase |
רוצה לנסות?
- לעיון באפליקציית המדריך למתחילים לראייה לשימוש לדוגמה במודל החבילה, אפליקציית המדריך למתחילים של Automl עבור שימוש לדוגמה במודל המתארח.
לפני שמתחילים
בקובץ
build.gradle
ברמת הפרויקט, חשוב לכלול מאגר Maven של Google גם ב-buildscript
וגםallprojects
קטעים.הוספת יחסי התלות של ספריות ML Kit Android למודול של המודול ברמת האפליקציה, שהוא בדרך כלל
app/build.gradle
. יש לבחור אחת מהאפשרויות בהתאם לצרכים שלכם:כדי לקבץ את צינור עיבוד הנתונים עם האפליקציה:
dependencies { // ... // Use this dependency to bundle the pipeline with your app implementation 'com.google.mlkit:image-labeling-custom:17.0.3' }
כדי להשתמש בצינור עיבוד הנתונים ב-Google Play Services:
dependencies { // ... // Use this dependency to use the dynamically downloaded pipeline in Google Play Services implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5' }
אם בחרת להשתמש בצינור עיבוד הנתונים של Google Play Services, יש לך אפשרות להגדיר לאפליקציה להוריד את צינור עיבוד הנתונים למכשיר באופן אוטומטי אחרי שהאפליקציה מותקנת מחנות Play. כדי לעשות את זה, צריך להוסיף את הפרטים הבאים: הצהרה לקובץ
AndroidManifest.xml
של האפליקציה:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="custom_ica" /> <!-- To use multiple downloads: android:value="custom_ica,download2,download3" --> </application>
אפשר גם לבדוק באופן מפורש את הזמינות של צינור עיבוד הנתונים ולבקש הורדה דרך API של ModuleInstallClient ב-Google Play Services.
אם לא מפעילים הורדות של צינורות עיבוד נתונים בזמן ההתקנה או מבקשים הורדה מפורשת, מתבצעת הורדה של צינור עיבוד הנתונים בפעם הראשונה שתפעילו את המתייג. הבקשות שלכם לפני שההורדה הסתיימה, לא נמצאו תוצאות.
צריך להוסיף את התלות של
linkFirebase
אם רוצים להוריד באופן דינמי את מ-Firebase:כדי להוריד מודל באופן דינמי מ-Firebase, מוסיפים את הקוד
linkFirebase
תלות:dependencies { // ... // Image labeling feature with model downloaded from Firebase implementation 'com.google.mlkit:image-labeling-custom:17.0.3' // Or use the dynamically downloaded pipeline in Google Play Services // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5' implementation 'com.google.mlkit:linkfirebase:17.0.0' }
אם אתם רוצים להוריד מודל, צריך לוודא מוסיפים את Firebase לפרויקט Android, אם עדיין לא עשיתם זאת. אין צורך לעשות זאת כשהמודל הוא חלק מחבילה.
1. טעינת המודל
הגדרת מקור למודל מקומי
כדי לצרף את המודל לאפליקציה:
מעתיקים את קובץ המודל (בדרך כלל מסתיים ב-
.tflite
או ב-.lite
) אל האפליקציה תיקייה אחת (assets/
). (ייתכן שקודם תצטרכו ליצור את התיקייה עד לוחצים לחיצה ימנית על התיקייהapp/
ואז לוחצים על חדש > תיקייה > תיקיית הנכסים).אחר כך צריך להוסיף את הפרטים הבאים לקובץ
build.gradle
של האפליקציה כדי לוודא Gradle לא דוחסת את קובץ המודל כשמפתחים את האפליקציה:android { // ... aaptOptions { noCompress "tflite" // or noCompress "lite" } }
קובץ המודל ייכלל בחבילת האפליקציה ויהיה זמין ל-ML Kit בתור נכס גולמי.
יוצרים אובייקט
LocalModel
, מציינים את הנתיב לקובץ המודל:Kotlin
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build()
Java
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build();
הגדרת מקור מודל שמתארח ב-Firebase
כדי להשתמש במודל שמתארח מרחוק, צריך ליצור אובייקט RemoteModel
באמצעות
FirebaseModelSource
, לציון השם שהקציתם למודל כש
פרסמה:
Kotlin
// Specify the name you assigned in the Firebase console. val remoteModel = CustomRemoteModel .Builder(FirebaseModelSource.Builder("your_model_name").build()) .build()
Java
// Specify the name you assigned in the Firebase console. CustomRemoteModel remoteModel = new CustomRemoteModel .Builder(new FirebaseModelSource.Builder("your_model_name").build()) .build();
לאחר מכן, מתחילים את המשימה של הורדת המודל, ומציינים את התנאים שבהם שרוצים לאפשר את ההורדה שלהם. אם הדגם לא נמצא במכשיר, או אם של המודל זמינה, המשימה תוריד באופן אסינכרוני מ-Firebase:
Kotlin
val downloadConditions = DownloadConditions.Builder() .requireWifi() .build() RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener { // Success. }
Java
DownloadConditions downloadConditions = new DownloadConditions.Builder() .requireWifi() .build(); RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(@NonNull Task task) { // Success. } });
אפליקציות רבות מתחילות את משימת ההורדה בקוד האתחול שלהן, אבל תוכלו לעשות זאת בכל שלב לפני שתצטרכו להשתמש במודל.
הגדרת מתייג התמונות
אחרי שמגדירים את מקורות המודלים, יוצרים אובייקט ImageLabeler
מ-
אחת מהן.
אלה האפשרויות הזמינות:
אפשרויות | |
---|---|
confidenceThreshold
|
ציון הסמך המינימלי של התוויות שזוהו. אם היא לא מוגדרת, כל ייעשה שימוש בסף המסווג שנקבע על ידי המטא-נתונים של המודל. אם המודל לא מכיל מטא-נתונים, או אם המטא-נתונים לא לציין סף סיווג, סף ברירת מחדל של 0.0 יהיה בשימוש. |
maxResultCount
|
מספר התוויות המקסימלי להחזרה. אם לא מוגדר, ערך ברירת המחדל של יהיו 10. |
אם יש לכם רק מודל באריזה מקומית, פשוט יוצרים מתייגים
אובייקט LocalModel
:
Kotlin
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(customImageLabelerOptions)
Java
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
אם יש לך מודל שמתארח מרחוק, עליך לבדוק שהוא
שהורדתם לפני שהפעלתם אותו. אפשר לבדוק את סטטוס ההורדה של המודל
באמצעות השיטה isModelDownloaded()
של מנהל המודלים.
למרות שצריך לאשר זאת רק לפני הפעלת המתייג, אם יש להם גם מודל שמתארח מרחוק וגם מודל בחבילות מקומיות, זה עלול ליצור מספיק לבצע את הבדיקה הזו כשיוצרים את מתייג התמונות: את המתייג מהמודל המרוחק אם הוא הורד, אחרת.
Kotlin
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener { isDownloaded -> val optionsBuilder = if (isDownloaded) { CustomImageLabelerOptions.Builder(remoteModel) } else { CustomImageLabelerOptions.Builder(localModel) } val options = optionsBuilder .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(options) }
Java
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Boolean isDownloaded) { CustomImageLabelerOptions.Builder optionsBuilder; if (isDownloaded) { optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel); } else { optionsBuilder = new CustomImageLabelerOptions.Builder(localModel); } CustomImageLabelerOptions options = optionsBuilder .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(options); } });
אם יש לך רק מודל שמתארח מרחוק, עליך להשבית את התכונה שקשורה למודלים
פונקציונליות - לדוגמה, הצגה באפור או הסתרה של חלק מממשק המשתמש - עד
מוודאים שבוצעה הורדה של המודל. אפשר לעשות זאת על ידי צירוף listen
ל-method download()
של מנהל המודלים:
Kotlin
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener { // Download complete. Depending on your app, you could enable the ML // feature, or switch from the local model to the remote model, etc. }
Java
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void v) { // Download complete. Depending on your app, you could enable // the ML feature, or switch from the local model to the remote // model, etc. } });
2. הכנת תמונת הקלט
לאחר מכן, יוצריםInputImage
לכל תמונה שרוצים להוסיף לה תווית.
מהתמונה. מתייג התמונות פועל הכי מהר כשמשתמשים ב-Bitmap
או, אם משתמשים ב- Camera2 API, ב-media.Image
YUV_420_888,
מומלץ כשהדבר אפשרי.
אפשר ליצור 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
ביחד עם מעלות סיבוב.
3. הפעלה של מתייג התמונה
כדי להוסיף תווית לאובייקטים בתמונה, צריך להעביר את האובייקט image
ImageLabeler
אמצעי תשלום אחד (process()
).
Kotlin
labeler.process(image) .addOnSuccessListener { labels -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
labeler.process(image) .addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() { @Override public void onSuccess(List<ImageLabel> labels) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. קבלת מידע על ישויות שסומנו בתווית
אם הפעולה של הוספת תוויות לתמונה תתבצע בהצלחה, רשימה שלImageLabel
האובייקטים מועברים למאזינים להצלחה. כל אובייקט ImageLabel
מייצג משהו שתויג בתמונה. אפשר לקבל את הטקסט של כל תווית
תיאור (אם זמין במטא-נתונים של קובץ המודל TensorFlow Lite), ציון המהימנות והאינדקס. לדוגמה:
Kotlin
for (label in labels) { val text = label.text val confidence = label.confidence val index = label.index }
Java
for (ImageLabel label : labels) { String text = label.getText(); float confidence = label.getConfidence(); int index = label.getIndex(); }
טיפים לשיפור הביצועים בזמן אמת
כדי להוסיף תווית לתמונות באפליקציה בזמן אמת, צריך לפעול לפי השלבים הבאים כדי להשיג את קצב הפריימים הטוב ביותר:
- אם משתמשים
Camera
אוcamera2
API, ויסות קריאות למתייג התמונות. אם מדובר בסרטון חדש הופכת לזמינה בזמן שמתייג התמונות פועל, צריך לשחרר את המסגרת. לצפייהVisionProcessorBase
באפליקציה לדוגמה של המדריך למתחילים. - אם אתם משתמשים ב-API של
CameraX
, יש לוודא שאסטרטגיית הלחץ החוזר מוגדרת לערך ברירת המחדל שלהImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
כך אפשר להבטיח שרק תמונה אחת תוצג לניתוח בכל פעם. אם עוד תמונות שנוצרות כשהכלי לניתוח נתונים עמוס, הוא יוסר באופן אוטומטי ולא ימתין בתור משלוח. לאחר שהתמונה שמנתחת נסגרת על ידי קריאה ImageProxy.close(), התמונה האחרונה הבאה תישלח. - אם משתמשים בפלט של מתייג התמונות כדי להציג גרפיקה בשכבת-על
מקבלים קודם את התוצאה מ-ML Kit ואז מעבדים את התמונה
וליצור שכבת-על בשלב אחד. הוא מוצג לפני השטח של המסך
פעם אחת בלבד לכל מסגרת קלט. לצפייה
CameraSourcePreview
וגםGraphicOverlay
, באפליקציה לדוגמה של המדריך למתחילים. - אם משתמשים ב- Camera2 API, מצלמים תמונות ב
פורמט של
ImageFormat.YUV_420_888
. אם משתמשים בגרסה הישנה של ממשק ה-API של המצלמה, מצלמים תמונות ב פורמט שלImageFormat.NV21
.