Catch up on everthing we announced at this year's Firebase Summit. Learn more

תייג תמונות עם דגם מאומן AutoML באנדרואיד

לאחר רכבת דגם משלך באמצעות אדג חזון AutoML , אתה יכול להשתמש בו באפליקצית תמונות תווית.

ישנן שתי דרכים לשלב מודלים שהוכשרו מ- AutoML Vision Edge: תוכל לאגד את המודל על ידי הכנסת אותו לתיקיית הנכסים של האפליקציה שלך, או שאתה יכול להוריד אותו באופן דינמי מ- Firebase.

אפשרויות קיבוץ דגמים
מקובץ באפליקציה שלך
  • המודל הוא חלק מה- APK של האפליקציה שלך
  • הדגם זמין באופן מיידי, גם כאשר מכשיר האנדרואיד אינו מקוון
  • אין צורך בפרויקט Firebase
מתארח אצל Firebase
  • ארח את המודל על ידי העלאתו Firebase מכונית למידה
  • מקטין את גודל ה- APK
  • המודל יורד על פי דרישה
  • דחוף עדכוני דגמים מבלי לפרסם מחדש את האפליקציה שלך
  • Easy A / B ובדיקה עם Firebase מרחוק Config
  • דורש פרויקט Firebase

לפני שאתה מתחיל

  1. מוסיפים את התלות עבור ספריות ML קיט אנדרואיד בקובץ 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'
    }
    
  2. אם אתה רוצה להוריד מודל, הקפד להוסיף Firebase לפרויקט Android שלך , אם אתה לא עשית זאת. זה לא נדרש כאשר אתה מאגד את הדגם.

1. טען את הדגם

הגדר מקור דגם מקומי

כדי לאגד את הדגם עם האפליקציה שלך:

  1. חלץ את הדגם ומטא הנתונים שלו מארכיון ה- zip שהורדת ממסוף Firebase. אנו ממליצים להשתמש בקבצים כפי שהורדת אותם, ללא שינויים (כולל שמות הקבצים).

  2. כלול את המודל שלך וקבצי המטא -נתונים שלו בחבילת האפליקציות שלך:

    1. אם אין לך נכסים התיקייה בפרויקט שלך, ליצור אחד על ידי לחיצה ימנית על app/ תיקייה, ולאחר מכן לחיצה על New> Folder> נכסים תיקיה.
    2. צור תיקיית משנה תחת תיקיית הנכסים שתכיל את קבצי המודל.
    3. העתק את הקבצים model.tflite , dict.txt , ו manifest.json לתיקיית משנה (כל שלושת הקבצים חייבים להיות באותה תיקייה).
  3. הוסף את הקוד הבא של האפליקציה שלך build.gradle הקובץ כדי לוודא Gradle לא לדחוס את קובץ המודל בעת בניית האפליקציה:

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    

    קובץ הדגם ייכלל בחבילת האפליקציות וזמין ל- ML Kit כנכס גולמי.

  4. צור LocalModel אובייקט, לציין את הנתיב אל קובץ מניפסט דגם:

    ג'אווה

    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 אובייקט, המפרט את השם שהקצית המודל כשאתה שפורסמת זה:

ג'אווה

// 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:

ג'אווה

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 חפץ מאחד מהם.

אם יש לך רק מודל מקומי ארוז, פשוט ליצור labeler מ שלך CustomImageLabelerOptions להתנגד ולהגדיר סף ביטחון הציון ברצונך לדרוש (ראה הערכת המודל שלך ):

ג'אווה

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() שיטה.

למרות שאתה רק צריך לאשר זאת לפני הפעלת הלייבלר, אם יש לך גם דגם שמארח מרחוק וגם מודל מקובץ מקומי, זה עשוי להיות הגיוני לבצע בדיקה זו בעת יישום תווית התמונות: צור תיוג מהדגם המרוחק אם הוא הורד, ומהדגם המקומי אחרת.

ג'אווה

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() שיטה:

ג'אווה

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, A YUV_420_888 media.Image , אשר מומלץ במידת האפשר.

אתה יכול ליצור InputImage ממקורות שונים, כל אחד מהם הסביר בהמשך.

באמצעות media.Image

כדי ליצור InputImage האובייקט מנקודת media.Image אובייקט, כגון כאשר אתה ללכוד תמונה מתוך המצלמה של המכשיר, להעביר את media.Image האובייקט ואת הסיבוב של התמונה InputImage.fromMediaImage() .

אם אתה משתמש CameraX הספריה, OnImageCapturedListener ו ImageAnalysis.Analyzer כיתות לחשב את ערך סיבוב בשבילך.

ג'אווה

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
        // ...
    }
}

קוטלין+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
            // ...
        }
    }
}

אם אינך משתמש בספריית מצלמות המעניקה לך את מידת הסיבוב של התמונה, תוכל לחשב אותה לפי מידת הסיבוב של המכשיר ומכיוון חיישן המצלמה במכשיר:

ג'אווה

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;
}

קוטלין+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
}

ואז, להעביר את media.Image אובייקט וערך סיבוב המידה InputImage.fromMediaImage() :

ג'אווה

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

קוטלין+KTX

val image = InputImage.fromMediaImage(mediaImage, rotation)

שימוש בקובץ URI

כדי ליצור InputImage אובייקט מקובץ URI, להעביר את הקשר האפליקציה ולהגיש אורי InputImage.fromFilePath() . תכונה זו שימושית כאשר אתה משתמש ACTION_GET_CONTENT כוונה מהמשתמש לבחור תמונה מאפליקציית הגלריה שלהם.

ג'אווה

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

קוטלין+KTX

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

באמצעות ByteBuffer או ByteArray

כדי ליצור InputImage אובייקט מנקודה ByteBuffer או ByteArray , ראשון לחשב את מידת תמונת הרוטציה כפי שתואר לעיל עבור media.Image קלט. ואז, ליצור את InputImage אובייקט עם חיץ או מערך, יחד עם גובה של תמונה, רוחב, פורמט קידוד צבע, ומידת סיבוב:

ג'אווה

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

קוטלין+KTX

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

באמצעות Bitmap

כדי ליצור InputImage האובייקט מנקודת Bitmap אובייקט, להפוך את ההצהרה הבאה:

ג'אווה

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

קוטלין+KTX

val image = InputImage.fromBitmap(bitmap, 0)

התמונה מיוצגת על ידי Bitmap יחד אובייקט עם מעלות סיבוב.

3. הפעל את תווית התמונות

לאובייקטי תווית בתמונה, להעביר את image האובייקט אל ImageLabeler של process() השיטה.

ג'אווה

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 אובייקט מייצג משהו שהיה מסומן בתמונה. אתה יכול לקבל את תיאור הטקסט של כל תווית, את ציון הביטחון של ההתאמה ואת אינדקס ההתאמה. לדוגמה:

ג'אווה

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 בכיתה ביישום מדגם QuickStart עבור דוגמה.
  • אם אתה משתמש בפלט של תווית התמונות כדי לכסות גרפיקה על תמונת הקלט, תחילה קבל את התוצאה ולאחר מכן עיבד את התמונה ואת שכבת העל בשלב אחד. בכך אתה מעבד למשטח התצוגה רק פעם אחת עבור כל מסגרת קלט. עיין CameraSourcePreview ו GraphicOverlay כיתות ביישום מדגם QuickStart עבור דוגמה.
  • אם אתה משתמש ב- API Camera2, ללכוד תמונות ב ImageFormat.YUV_420_888 פורמט.

    אם אתה משתמש ב- API המצלמה המבוגרת, ללכוד תמונות ב ImageFormat.NV21 פורמט.