לאחר שתאמן את הדגם שלך באמצעות AutoML Vision Edge , תוכל להשתמש בו באפליקציה שלך כדי לתייג תמונות.
ישנן שתי דרכים לשלב מודלים מאומנים מ-AutoML Vision Edge: אתה יכול לאגד את המודל על ידי הכנסתו לתיקיית הנכסים של האפליקציה שלך, או שאתה יכול להוריד אותו באופן דינמי מ-Firebase.
אפשרויות חבילת דגמים | |
---|---|
מאגד באפליקציה שלך |
|
מתארח עם Firebase |
|
לפני שאתה מתחיל
הוסף את התלות של ספריות אנדרואיד של ML Kit לקובץ Gradle ברמת האפליקציה שלך, שהוא בדרך כלל
app/build.gradle
:לאגד דגם עם האפליקציה שלך:
dependencies { // ... // Image labeling feature with bundled automl model implementation 'com.google.mlkit:image-labeling-custom:16.3.1' }
להורדה דינמית של מודל מ-Firebase, הוסף את התלות של
linkFirebase
:dependencies { // ... // Image labeling feature with automl model downloaded // from firebase implementation 'com.google.mlkit:image-labeling-custom:16.3.1' implementation 'com.google.mlkit:linkfirebase:16.1.0' }
אם ברצונך להוריד דגם , הקפד להוסיף את Firebase לפרויקט האנדרואיד שלך , אם עדיין לא עשית זאת. זה לא נדרש כאשר אתה מצרף את הדגם.
1. טען את הדגם
הגדר מקור דגם מקומי
כדי לאגד את הדגם עם האפליקציה שלך:
חלץ את המודל ואת המטא נתונים שלו מארכיון ה-zip שהורדת ממסוף Firebase. אנו ממליצים להשתמש בקבצים כפי שהורדתם אותם, ללא שינוי (כולל שמות הקבצים).
כלול את הדגם שלך וקובצי המטא נתונים שלו בחבילת האפליקציה שלך:
- אם אין לך תיקיית נכסים בפרויקט שלך, צור אחת על ידי לחיצה ימנית על
app/
תיקיה ולאחר מכן לחיצה על חדש > תיקיה > תיקיית נכסים . - צור תיקיית משנה תחת תיקיית הנכסים שתכיל את קבצי הדגם.
- העתק את הקבצים
model.tflite
,dict.txt
ו-manifest.json
לתיקיית המשנה (כל שלושת הקבצים חייבים להיות באותה תיקיה).
- אם אין לך תיקיית נכסים בפרויקט שלך, צור אחת על ידי לחיצה ימנית על
הוסף את הדברים הבאים לקובץ
build.gradle
של האפליקציה שלך כדי להבטיח ש-Gradle לא ידחוס את קובץ הדגם בעת בניית האפליקציה:android { // ... aaptOptions { noCompress "tflite" } }
קובץ הדגם ייכלל בחבילת האפליקציה וזמין ל-ML Kit כנכס גולמי.
צור אובייקט
LocalModel
, תוך ציון הנתיב לקובץ המניפסט של המודל:Java
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();
קוטלין
val localModel = LocalModel.Builder() .setAssetManifestFilePath("manifest.json") // or .setAbsoluteManifestFilePath(absolute file path to manifest file) .build()
הגדר מקור מודל שמתארח ב-Firebase
כדי להשתמש במודל המתארח מרחוק, צור אובייקט CustomRemoteModel
, תוך ציון השם שהקצית למודל כשפרסמת אותו:
Java
// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
new CustomRemoteModel.Builder(firebaseModelSource).build();
קוטלין
// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
.build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).build()
לאחר מכן, התחל את משימת הורדת הדגם, תוך ציון התנאים שבהם ברצונך לאפשר הורדה. אם הדגם אינו במכשיר, או אם זמינה גרסה חדשה יותר של הדגם, המשימה תוריד את הדגם באופן אסינכרוני מ-Firebase:
Java
DownloadConditions downloadConditions = new DownloadConditions.Builder()
.requireWifi()
.build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(@NonNull Task<Void> task) {
// Success.
}
});
קוטלין
val downloadConditions = DownloadConditions.Builder()
.requireWifi()
.build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener {
// Success.
}
אפליקציות רבות מתחילות את משימת ההורדה בקוד האתחול שלהן, אך תוכל לעשות זאת בכל שלב לפני שתצטרך להשתמש במודל.
צור תוויות תמונה מהדגם שלך
לאחר שתגדיר את מקורות המודל שלך, צור אובייקט ImageLabeler
מאחד מהם.
אם יש לך רק מודל מקובץ מקומית, פשוט צור תוויות מאובייקט CustomImageLabelerOptions
שלך והגדר את סף ציון הביטחון שאתה רוצה לדרוש (ראה הערכת המודל שלך ):
Java
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
קוטלין
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)
אם יש לך דגם שמתארח מרחוק, תצטרך לבדוק שהוא הורד לפני שתפעיל אותו. אתה יכול לבדוק את המצב של משימת הורדת המודל באמצעות שיטת isModelDownloaded()
של מנהל המודלים.
למרות שעליך לאשר זאת רק לפני הפעלת התווית, אם יש לך גם דגם שמתארח מרחוק וגם דגם עם חבילה מקומית, ייתכן שיהיה הגיוני לבצע את הבדיקה הזו בעת יצירת תווית התמונה: צור תוויות מהדגם המרוחק אם זה הורד, ובדרך אחרת מהדגם המקומי.
Java
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@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.0f) // Evaluate your model in the Cloud console
// to determine an appropriate threshold.
.build();
ImageLabeler labeler = ImageLabeling.getClient(options);
}
});
קוטלין
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
CustomImageLabelerOptions.Builder(remoteModel)
} else {
CustomImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Cloud console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = ImageLabeling.getClient(options)
}
אם יש לך רק דגם שמתארח מרחוק, עליך להשבית את הפונקציונליות הקשורה לדגם - לדוגמה, לאפור או להסתיר חלק מהממשק שלך - עד שתאשר שהדגם הורד. אתה יכול לעשות זאת על ידי חיבור מאזין לשיטת download()
של מנהל המודלים:
Java
RemoteModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@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.
}
});
קוטלין
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.
}
2. הכן את תמונת הקלט
לאחר מכן, עבור כל תמונה שברצונך לסמן, צור אובייקט InputImage
מהתמונה שלך. תווית התמונות פועלת הכי מהר כשאתה משתמש ב- Bitmap
או, אם אתה משתמש בממשק ה-API של camera2, ב-YUV_420_888 media.Image
, המומלצים במידת האפשר.
אתה יכול ליצור InputImage
ממקורות שונים, כל אחד מהם מוסבר להלן.
שימוש media.Image
כדי ליצור אובייקט InputImage
מאובייקט media.Image
, כגון כאשר אתה מצלם תמונה ממצלמה של מכשיר, העבר את אובייקט media.Image
וסיבוב התמונה ל- InputImage.fromMediaImage()
.
אם אתה משתמש בספריית CameraX , המחלקות OnImageCapturedListener
ו- ImageAnalysis.Analyzer
מחשבות עבורך את ערך הסיבוב.
Kotlin+KTX
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) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees); // Pass image to an ML Kit Vision API // ... } }
אם אינך משתמש בספריית מצלמה הנותנת לך את דרגת הסיבוב של התמונה, תוכל לחשב אותה מדרגת הסיבוב של המכשיר ומכיוון חיישן המצלמה במכשיר:
Kotlin+KTX
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * 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, context: Context): 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) // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360 // Return the corresponding FirebaseVisionImageMetadata rotation value. val result: Int when (rotationCompensation) { 0 -> result = FirebaseVisionImageMetadata.ROTATION_0 90 -> result = FirebaseVisionImageMetadata.ROTATION_90 180 -> result = FirebaseVisionImageMetadata.ROTATION_180 270 -> result = FirebaseVisionImageMetadata.ROTATION_270 else -> { result = FirebaseVisionImageMetadata.ROTATION_0 Log.e(TAG, "Bad rotation value: $rotationCompensation") } } return result }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 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, Context context) 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); // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360; // Return the corresponding FirebaseVisionImageMetadata rotation value. int result; switch (rotationCompensation) { case 0: result = FirebaseVisionImageMetadata.ROTATION_0; break; case 90: result = FirebaseVisionImageMetadata.ROTATION_90; break; case 180: result = FirebaseVisionImageMetadata.ROTATION_180; break; case 270: result = FirebaseVisionImageMetadata.ROTATION_270; break; default: result = FirebaseVisionImageMetadata.ROTATION_0; Log.e(TAG, "Bad rotation value: " + rotationCompensation); } return result; }
לאחר מכן, העבר את אובייקט media.Image
ואת ערך דרגת הסיבוב ל- InputImage.fromMediaImage()
:
Kotlin+KTX
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
שימוש ב-URI של קובץ
כדי ליצור אובייקט InputImage
מ-URI של קובץ, העבר את ההקשר של האפליקציה ו-URI של הקובץ ל- InputImage.fromFilePath()
. זה שימושי כאשר אתה משתמש בכוונה ACTION_GET_CONTENT
כדי לבקש מהמשתמש לבחור תמונה מאפליקציית הגלריה שלו.
Kotlin+KTX
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+KTX
val image = InputImage.fromByteBuffer( byteBuffer, /* 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 );
שימוש Bitmap
כדי ליצור אובייקט InputImage
מאובייקט Bitmap
, בצע את ההצהרה הבאה:
Kotlin+KTX
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
התמונה מיוצגת על ידי אובייקט Bitmap
יחד עם דרגות סיבוב.
3. הפעל את תווית התמונות
כדי לתייג אובייקטים בתמונה, העבירו את אובייקט image
לשיטת process()
של ImageLabeler
.
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
// ...
}
});
קוטלין
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
4. קבל מידע על אובייקטים מסומנים
אם פעולת תיוג התמונה מצליחה, רשימה של אובייקטי ImageLabel
מועברת למאזין ההצלחה. כל אובייקט ImageLabel
מייצג משהו שסומן בתמונה. אתה יכול לקבל את תיאור הטקסט של כל תווית, את ציון הביטחון של ההתאמה ואינדקס ההתאמה. לדוגמה:
Java
for (ImageLabel label : labels) {
String text = label.getText();
float confidence = label.getConfidence();
int index = label.getIndex();
}
קוטלין
for (label in labels) {
val text = label.text
val confidence = label.confidence
val index = label.index
}
טיפים לשיפור הביצועים בזמן אמת
אם ברצונך לסמן תמונות ביישום בזמן אמת, פעל לפי ההנחיות הבאות כדי להשיג את קצבי המסגרות הטובים ביותר:
- קריאות מצערת לתיוג התמונה. אם מסגרת וידאו חדשה הופכת לזמינה בזמן שמתווית התמונה פועלת, שחרר את המסגרת. ראה את המחלקה
VisionProcessorBase
באפליקציה לדוגמה להתחלה מהירה לדוגמא. - אם אתה משתמש בפלט של מתווית התמונה כדי לשכב גרפיקה על תמונת הקלט, תחילה קבל את התוצאה, ולאחר מכן עבד את התמונה ואת שכבת העל בשלב אחד. על ידי כך, אתה מעבד למשטח התצוגה רק פעם אחת עבור כל מסגרת קלט. ראה את המחלקות
CameraSourcePreview
ו-GraphicOverlay
באפליקציה לדוגמה להתחלה מהירה לדוגמא. אם אתה משתמש בממשק ה-API של Camera2, צלם תמונות בפורמט
ImageFormat.YUV_420_888
.אם אתה משתמש ב-Camera API הישן יותר, צלם תמונות בפורמט
ImageFormat.NV21
.