מדריך לזיהוי אובייקטים ב-Android

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

קוד לדוגמה

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

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

הורדת הקוד

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

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

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

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

רכיבים עיקריים

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

  • ObjectDetectorHelper.kt – הפעלה של מזהה האובייקטים ומטפלת במודל ובבחירת המודל
  • MainActivity.kt – יישום והרכבה של רכיבי ממשק המשתמש
  • OverlayView.kt – טיפול והצגת התוצאות

הגדרה

בקטע הזה מתוארים שלבים עיקריים להגדרת סביבת הפיתוח ופרויקטים של קוד לשימוש בגלאי אובייקטים. במדריך ההגדרה ל-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() כדי לציין את הנתיב שהמודל משתמש בו. דוגמה לקוד זמינה בקטע הבא.

יצירת המשימה

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

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

תמונה

ObjectDetectorOptions options =
  ObjectDetectorOptions.builder()
    .setBaseOptions(BaseOptions.builder().setModelAssetPath(‘model.tflite’).build())
    .setRunningMode(RunningMode.IMAGE)
    .setMaxResults(5)
    .build();
objectDetector = ObjectDetector.createFromOptions(context, options);
    

וידאו

ObjectDetectorOptions options =
  ObjectDetectorOptions.builder()
    .setBaseOptions(BaseOptions.builder().setModelAssetPath(‘model.tflite’).build())
    .setRunningMode(RunningMode.VIDEO)
    .setMaxResults(5)
    .build();
objectDetector = ObjectDetector.createFromOptions(context, options);
    

שידור חי

ObjectDetectorOptions options =
  ObjectDetectorOptions.builder()
    .setBaseOptions(BaseOptions.builder().setModelAssetPath(‘model.tflite’).build())
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setMaxResults(5)
    .setResultListener((result, inputImage) -> {
      // Process the detection result here.
    })
    .setErrorListener((result, inputImage) -> {
      // Process the classification errors here.
    })
   .build();
objectDetector = ObjectDetector.createFromOptions(context, options);
    

הטמעת הקוד לדוגמה של מזהה האובייקטים מאפשרת למשתמש לעבור בין מצבי עיבוד. הגישה הזו יוצרת את הקוד ליצירת משימה ועשויה לא להתאים לתרחיש שלכם לדוגמה. אפשר לראות את הקוד הזה בפונקציה setupObjectDetector() במחלקה ObjectDetectorHelper.

אפשרויות הגדרה

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

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

IMAGE: המצב שבו ניתן להזין תמונה יחידה.

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

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

הכנת הנתונים

לפני שמעבירים את התמונה או את המסגרת של הקלט לאובייקט 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 values. Use these values
// 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()
MPImage mpImage = new MediaImageBuilder(mediaImage).build();
    

בקוד לדוגמה של מזהה האובייקטים, הכנת הנתונים מטופלת במחלקה ObjectDetectorHelper בתוך הפונקציות detectImage(), detectVideoFile(), detectLivestreamFrame().

מריצים את המשימה.

בהתאם לסוג הנתונים שאתם עובדים איתם, צריך להשתמש בשיטה ObjectDetector.detect...() שספציפית לסוג הנתונים הזה. עליך להשתמש ב-detect() לתמונות בודדות, ב-detectForVideo() לפריימים בקובצי וידאו וב-detectAsync() לסטרימינג של וידאו. כשמבצעים זיהוי בשידור וידאו, חשוב להפעיל את הזיהוי בשרשור נפרד כדי לא לחסום את השרשור של ממשק המשתמש.

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

תמונה

ObjectDetectorResult detectionResult = objectDetector.detect(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.
ObjectDetectorResult detectionResult =
    objectDetector.detectForVideo(image, frameTimestampMs);
    

שידור חי

// Run inference on the frame. The detection results will be available
// via the `resultListener` provided in the `ObjectDetectorOptions` when
// the object detector was created.
objectDetector.detectAsync(image, frameTimestampMs);
    

בדוגמה לקוד מזהה האובייקטים אפשר לראות בצורה מפורטת יותר את ההטמעות של כל אחד מהמצבים האלה: detect(), detectVideoFile() ו-detectAsync(). הקוד לדוגמה מאפשר למשתמשים לעבור בין מצבי עיבוד, שאולי לא נחוצים בתרחיש לדוגמה שלכם.

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

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

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

לאחר הרצה של מסקנות, המשימה 'מזהה אובייקטים' מחזירה אובייקט ObjectDetectorResult שמתאר את האובייקטים שהוא מצא בתמונת הקלט.

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

ObjectDetectorResult:
 Detection #0:
  Box: (x: 355, y: 133, w: 190, h: 206)
  Categories:
   index       : 17
   score       : 0.73828
   class name  : dog
 Detection #1:
  Box: (x: 103, y: 15, w: 138, h: 369)
  Categories:
   index       : 17
   score       : 0.73047
   class name  : dog

התמונה הבאה מציגה המחשה של פלט המשימה:

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