מדריך לסיווג תמונות ב-Android

המשימה 'מסווג תמונות של MediaPipe' מאפשרת לכם לבצע סיווג של תמונות. תוכלו להשתמש במשימה הזאת כדי להבין מה תמונה מייצגת מתוך קבוצת קטגוריות שהוגדרה בזמן האימון. ההוראות הבאות מראות איך להשתמש במסווג התמונות באפליקציות ל-Android. דוגמת הקוד שמתוארת בהוראות האלו זמינה ב-GitHub.

אפשר לראות את המשימה הזו בהדגמה של האתר. בסקירה הכללית תוכלו לקרוא מידע נוסף על היכולות, המודלים ואפשרויות ההגדרה של המשימה הזו.

קוד לדוגמה

קוד הדוגמה ל-MediaPipe Tasks הוא הטמעה פשוטה של אפליקציית מסווג תמונות ל-Android. בדוגמה נעשה שימוש במצלמה של מכשיר Android פיזי כדי לסווג אובייקטים באופן רציף, ואפשר גם להשתמש בתמונות ובסרטונים מגלריית המכשיר כדי לסווג אובייקטים.

אפשר להשתמש באפליקציה כנקודת התחלה של אפליקציה משלכם ל-Android, או להתייחס אליה כשמשנים אפליקציה קיימת. הקוד לדוגמה של מסווג התמונות מתארח ב-GitHub.

להורדת הקוד

בהוראות הבאות מוסבר איך ליצור עותק מקומי של הקוד לדוגמה באמצעות כלי שורת הפקודה git.

כדי להוריד את הקוד לדוגמה:

  1. משכפלים את מאגר ה-Git באמצעות הפקודה הבאה:
    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. אפשר גם להגדיר את מכונת ה-Git לשימוש בקופה עם גישה מצומצמת, כך שיהיו לכם רק את הקבצים של האפליקציה לדוגמה של מסווג התמונות:
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_classification/android
    

אחרי שיוצרים גרסה מקומית של הקוד לדוגמה, אפשר לייבא את הפרויקט ל-Android Studio ולהריץ את האפליקציה. הוראות מופיעות במדריך ההגדרה ל-Android.

רכיבים מרכזיים

הקבצים הבאים מכילים את הקוד החשוב של האפליקציה לדוגמה לסיווג תמונות:

הגדרה

בקטע הזה מתוארים השלבים העיקריים להגדרת סביבת הפיתוח ופרויקטים בקוד לשימוש במסווג התמונות. במדריך ההגדרה ל-Android תוכלו לקרוא מידע כללי על הגדרת סביבת הפיתוח לשימוש במשימות של MediaPipe, כולל הדרישות לגרסת הפלטפורמה.

יחסי תלות

מסווג התמונות משתמש בספרייה com.google.mediapipe:tasks-vision. מוסיפים את התלות הזאת לקובץ build.gradle של פרויקט פיתוח האפליקציות ל-Android. מייבאים את יחסי התלות הנדרשים באמצעות הקוד הבא:

dependencies {
    ...
    implementation 'com.google.mediapipe:tasks-vision:latest.release'
}

דגם

כדי לבצע את המשימה של מסווג תמונות של MediaPipe צריך מודל מאומן שתואם למשימה הזו. מידע נוסף על מודלים זמינים מאומנים של מסווג התמונות זמין בקטע 'מודלים', בסקירה הכללית על המשימות.

בוחרים את המודל, מורידים אותו ושומרים אותו בספריית הפרויקט:

<dev-project-root>/src/main/assets

משתמשים ב-method BaseOptions.Builder.setModelAssetPath() כדי לציין את הנתיב שבו המודל משתמש. אפשר לקרוא לשיטה הזו בקטע הקוד הבא.

בקוד לדוגמה של מסווג התמונות, המודל מוגדר בקובץ ImageClassifierHelper.kt.

יצירת המשימה

אפשר להשתמש בפונקציה createFromOptions כדי ליצור את המשימה. הפונקציה createFromOptions מקבלת אפשרויות הגדרה, כולל מצב הרצה, הלוקאל שבו השמות מוצגים, מספר התוצאות המקסימלי, סף הסמך ורשימת הרשאות של קטגוריות או רשימת דחייה. למידע נוסף על אפשרויות ההגדרה, ראו סקירה כללית של ההגדרות.

במשימה של מסווג התמונות יש 3 סוגים של נתוני קלט: תמונות סטילס, קובצי וידאו וסטרימינג של וידאו בשידור חי. בזמן יצירת המשימה, צריך לציין את מצב הריצה שמתאים לסוג נתוני הקלט. בחרו את הכרטיסייה שתואמת לסוג של נתוני הקלט שלכם כדי לראות איך יוצרים את המשימה ומריצים את ההסקה.

תמונה

ImageClassifierOptions options =
  ImageClassifierOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.IMAGE)
    .setMaxResults(5)
    .build();
imageClassifier = ImageClassifier.createFromOptions(context, options);
    

וידאו

ImageClassifierOptions options =
  ImageClassifierOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.VIDEO)
    .setMaxResults(5)
    .build();
imageClassifier = ImageClassifier.createFromOptions(context, options);
    

שידור חי

ImageClassifierOptions options =
  ImageClassifierOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setMaxResults(5)
    .setResultListener((result, inputImage) -> {
         // Process the classification result here.
    })
    .setErrorListener((result, inputImage) -> {
         // Process the classification errors here.
    })
    .build()
imageClassifier = ImageClassifier.createFromOptions(context, options)
    

הטמעת הקוד לדוגמה של מסווג התמונות מאפשרת למשתמש לעבור בין מצבי עיבוד. הגישה הזו הופכת את הקוד ליצירת משימה למורכב יותר ויכול להיות שהוא לא יתאים לתרחיש לדוגמה שלכם. הקוד הזה מופיע בפונקציה setupImageClassifier() של הקובץ ImageClassifierHelper.kt.

אפשרויות תצורה

המשימה הזו כוללת את אפשרויות ההגדרה הבאות לאפליקציות ל-Android:

שם האפשרות תיאור טווח ערכים ערך ברירת מחדל
runningMode מגדיר את מצב הריצה של המשימה. יש שלושה מצבים:

IMAGE: המצב שמאפשר קלט של תמונה יחידה.

סרטון: המצב של פריימים מפוענחים בסרטון.

LIVE_STREAM: המצב לשידור חי של נתוני קלט, למשל ממצלמה. במצב הזה, יש להפעיל את resultListener כדי להגדיר האזנה לקבלת תוצאות באופן אסינכרוני.
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
displayNamesLocale ההגדרה הזו מגדירה את השפה של התוויות שישמשו בשמות המוצגים שבמטא-נתונים של מודל המשימה, אם זמין. ברירת המחדל היא en לאנגלית. אפשר להוסיף תוויות שמותאמות לשוק המקומי למטא-נתונים של מודל מותאם אישית באמצעות TensorFlow Lite Metadata Writer API קוד שפה en
maxResults מגדיר את המספר המקסימלי האופציונלי של תוצאות סיווג עם התוצאות המובילות. אם הערך הוא פחות מ-0, יוחזרו כל התוצאות הזמינות. מספרים חיוביים כלשהם -1
scoreThreshold מגדירה את סף הציון של החיזוי (אם יש כזה) שמבטל את הסף שצוין במטא-נתונים של המודל. תוצאות מתחת לערך הזה נדחות. כל מספר ממשי (float) לא הוגדר
categoryAllowlist מגדיר את הרשימה האופציונלית של שמות קטגוריות מותרות. אם השדה לא ריק, תוצאות הסיווג שבהן שם הקטגוריה לא נמצא בקבוצה הזו יסוננו. המערכת מתעלמת משמות קטגוריות כפולים או לא ידועים. האפשרות הזו קיימת רק ב-categoryDenylist ומשתמשת בשתיהן כדי ליצור שגיאה. כל מחרוזת לא הוגדר
categoryDenylist מגדיר את הרשימה האופציונלית של שמות קטגוריות שאינם מותרים. אם השדה לא ריק, תוצאות הסיווג שבהן שם הקטגוריה נמצא בקבוצה הזו יסוננו. המערכת מתעלמת משמות קטגוריות כפולים או לא ידועים. האפשרות הזו בלעדית באופן הדדי ל-categoryAllowlist, והשימוש בשתי האפשרויות יוביל לשגיאה. כל מחרוזת לא הוגדר
resultListener מגדיר את אוזן התוצאות לקבל את תוצאות הסיווג באופן אסינכרוני כאשר מסווג התמונות נמצא במצב של שידור חי. אפשר להשתמש באפשרות הזו רק כשמצב הריצה מוגדר ל-LIVE_STREAM לא רלוונטי לא הוגדר
errorListener הגדרת האזנה לשגיאות אופציונלית. לא רלוונטי לא הוגדר

הכנת נתונים

הכלי סיווג תמונות פועל עם תמונות, קובצי וידאו וסרטונים בשידור חי. המשימה מטפלת בעיבוד מראש של קלט הנתונים, כולל שינוי הגודל, הסיבוב ונירמול הערכים.

צריך להמיר את תמונת הקלט או את המסגרת לאובייקט com.google.mediapipe.framework.image.MPImage לפני העברתם למסווג התמונות.

תמונה

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load an image on the user’s device as a Bitmap object using BitmapFactory.

// Convert an Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(bitmap).build();
    

וידאו

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load a video file on the user's device using MediaMetadataRetriever

// From the video’s metadata, load the METADATA_KEY_DURATION and
// METADATA_KEY_VIDEO_FRAME_COUNT value. You’ll need them
// to calculate the timestamp of each frame later.

// Loop through the video and load each frame as a Bitmap object.

// Convert the Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(frame).build();
    

שידור חי

import com.google.mediapipe.framework.image.MediaImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Create a CameraX’s ImageAnalysis to continuously receive frames 
// from the device’s camera. Configure it to output frames in RGBA_8888
// format to match with what is required by the model.

// For each Android’s ImageProxy object received from the ImageAnalysis, 
// extract the encapsulated Android’s Image object and convert it to 
// a MediaPipe’s Image object.
android.media.Image mediaImage = imageProxy.getImage()
Image mpImage = new MediaImageBuilder(mediaImage).build();
    

בקוד לדוגמה של מסווג התמונות, הכנת הנתונים מטופלת בקובץ ImageClassifierHelper.kt.

הרצת המשימה

אפשר להפעיל את הפונקציה classify שתואמת למצב הריצה כדי להפעיל מסקנות. ה-Image Classifier API מחזיר את הקטגוריות האפשריות של האובייקט בתמונה או במסגרת הקלט.

תמונה

ImageClassifierResult classifierResult = imageClassifier.classify(image);
    

וידאו

// Calculate the timestamp in milliseconds of the current frame.
long frame_timestamp_ms = 1000 * video_duration * frame_index / frame_count;

// Run inference on the frame.
ImageClassifierResult classifierResult =
    imageClassifier.classifyForVideo(image, frameTimestampMs);
    

שידור חי


// Run inference on the frame. The classifications results will be available 
// via the `resultListener` provided in the `ImageClassifierOptions` when 
// the image classifier was created.
imageClassifier.classifyAsync(image, frameTimestampMs);
    

שימו לב לנקודות הבאות:

  • כשעובדים במצב וידאו או במצב שידור חי, צריך גם לציין את חותמת הזמן של מסגרת הקלט למשימה של מסווג התמונות.
  • כשמריצים את התמונה או את מצב הווידאו, המשימה 'מסווג תמונות' חוסמת את השרשור הנוכחי עד לסיום העיבוד של תמונת הקלט או הפריים. כדי לא לחסום את ממשק המשתמש, צריך לבצע את העיבוד ב-thread ברקע.
  • כשהתכונה 'מסווג תמונות' פועלת במצב שידור חי, היא לא חוסמת את השרשור הנוכחי, אבל חוזרת מיד. בכל פעם שהוא מסיים לעבד מסגרת קלט, הוא יפעיל את מזיני התוצאות שלו עם תוצאת הזיהוי. אם נשלחת קריאה לפונקציה classifyAsync כשמשימה של מסווג תמונות עסוקה בעיבוד פריים אחר, המשימה מתעלמת ממסגרת הקלט החדשה.

בקוד לדוגמה של מסווג התמונות, הפונקציות classify מוגדרות בקובץ ImageClassifierHelper.kt.

טיפול בתוצאות והצגתן

כשמריצים את ההסקה, המשימה של מסווג התמונות מחזירה אובייקט ImageClassifierResult שמכיל רשימת קטגוריות אפשריות של האובייקטים בתמונה או במסגרת הקלט.

בדוגמה הבאה אפשר לראות את נתוני הפלט מהמשימה:

ImageClassifierResult:
 Classifications #0 (single classification head):
  head index: 0
  category #0:
   category name: "/m/01bwb9"
   display name: "Passer domesticus"
   score: 0.91406
   index: 671
  category #1:
   category name: "/m/01bwbt"
   display name: "Passer montanus"
   score: 0.00391
   index: 670

התוצאה הזו התקבלה על ידי הפעלת Bird Classifier על:

בקוד לדוגמה של מסווג התמונות, המחלקה ClassificationResultsAdapter בקובץ ClassificationResultsAdapter.kt מטפלת בתוצאות:

fun updateResults(imageClassifierResult: ImageClassifierResult? = null) {
    categories = MutableList(adapterSize) { null }
    if (imageClassifierResult != null) {
        val sortedCategories = imageClassifierResult.classificationResult()
            .classifications()[0].categories().sortedBy { it.index() }
        val min = kotlin.math.min(sortedCategories.size, categories.size)
        for (i in 0 until min) {
            categories[i] = sortedCategories[i]
        }
    }
}