מדריך לזיהוי תנועות ל-Android

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

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

קוד לדוגמה

הקוד לדוגמה של משימות MediaPipe הוא יישום פשוט של אפליקציה לזיהוי תנועות ל-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/gesture_recognizer/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

מציינים את הנתיב של המודל בתוך הפרמטר ModelAssetPath. בקוד לדוגמה, המודל מוגדר בקובץ GestureRecognizerHelper.kt:

baseOptionBuilder.setModelAssetPath(MP_RECOGNIZER_TASK)

יצירת המשימה

המשימה 'מזהה תנועות של MediaPipe' משתמשת בפונקציה createFromOptions() כדי להגדיר את המשימה. הפונקציה createFromOptions() מקבלת ערכים לאפשרויות התצורה. למידע נוסף על אפשרויות ההגדרה, תוכלו לקרוא את המאמר אפשרויות תצורה.

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

תמונה

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder =
    GestureRecognizer.GestureRecognizerOptions.builder()
        .setBaseOptions(baseOptions)
        .setMinHandDetectionConfidence(minHandDetectionConfidence)
        .setMinTrackingConfidence(minHandTrackingConfidence)
        .setMinHandPresenceConfidence(minHandPresenceConfidence)
        .setRunningMode(RunningMode.IMAGE)

val options = optionsBuilder.build()
gestureRecognizer =
    GestureRecognizer.createFromOptions(context, options)
    

וידאו

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder =
    GestureRecognizer.GestureRecognizerOptions.builder()
        .setBaseOptions(baseOptions)
        .setMinHandDetectionConfidence(minHandDetectionConfidence)
        .setMinTrackingConfidence(minHandTrackingConfidence)
        .setMinHandPresenceConfidence(minHandPresenceConfidence)
        .setRunningMode(RunningMode.VIDEO)

val options = optionsBuilder.build()
gestureRecognizer =
    GestureRecognizer.createFromOptions(context, options)
    

שידור חי

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder =
    GestureRecognizer.GestureRecognizerOptions.builder()
        .setBaseOptions(baseOptions)
        .setMinHandDetectionConfidence(minHandDetectionConfidence)
        .setMinTrackingConfidence(minHandTrackingConfidence)
        .setMinHandPresenceConfidence(minHandPresenceConfidence)
        .setResultListener(this::returnLivestreamResult)
        .setErrorListener(this::returnLivestreamError)
        .setRunningMode(RunningMode.LIVE_STREAM)

val options = optionsBuilder.build()
gestureRecognizer =
    GestureRecognizer.createFromOptions(context, options)
    

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

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

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

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

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

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

LIVE_STREAM: המצב עבור סטרימינג בשידור חי של נתוני קלט, למשל ממצלמה. במצב הזה, יש להפעיל את resultListener כדי להגדיר מאזין לקבלת תוצאות באופן אסינכרוני.
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
numHands GestureRecognizer יכול לזהות את מספר הידיים המקסימלי. Any integer > 0 1
minHandDetectionConfidence ציון המהימנות המינימלי שזיהוי היד ייחשב כמוצלח במודל של זיהוי כף היד. 0.0 - 1.0 0.5
minHandPresenceConfidence ציון המהימנות המינימלי של ציון הנוכחות של היד במודל הזיהוי של ציון הדרך. במצב וידאו ובמצב שידור חי של מזהה התנועות, אם הציון המשוער של נוכחות היד של המודל של ציון הדרך של כף היד נמוכה מהסף הזה, מופעל מודל זיהוי כף היד. אחרת, נעשה שימוש באלגוריתם קליל למעקב אחר היד כדי לקבוע את המיקום של הידיים לצורך זיהוי של ציוני דרך. 0.0 - 1.0 0.5
minTrackingConfidence ציון הסמך המינימלי שצריך לעמוד בו כדי שהמעקב אחרי היד ייחשבו בהצלחה. זהו סף ה-IoU של התיבה התוחמת בין הידיים במסגרת הנוכחית לפריים האחרון. במצב וידאו ובמצב סטרימינג של מזהה תנועות, אם המעקב נכשל, מזהה התנועות מפעיל זיהוי ידיים. אחרת, המערכת תדלג על זיהוי היד. 0.0 - 1.0 0.5
cannedGesturesClassifierOptions אפשרויות להגדרת ההתנהגות של מסווג תנועות מוכנות מראש. התנועות המוכנה מראש הן ["None", "Closed_Fist", "Open_Palm", "Pointing_Up", "Thumb_Down", "Thumb_Up", "Victory", "ILoveYou"]
  • לוקאל של שמות תצוגה: הלוקאל של השמות לתצוגה שצוינו במטא-נתונים של דגם TFLite, אם רלוונטי.
  • מקסימום תוצאות: המספר המקסימלי של תוצאות סיווג מובילות שניתן להחזיר. אם הערך קטן מ-0, יוחזרו כל התוצאות הזמינות.
  • סף הניקוד: הציון שמתחתיו נדחות התוצאות. אם הערך שמוגדר הוא 0, כל התוצאות הזמינות יוחזרו.
  • רשימת היתרים של קטגוריות: רשימת ההיתרים של שמות הקטגוריות. אם הקטגוריה אינה ריקה, יסוננו תוצאות סיווג שהקטגוריה שלהן לא כלולה. בלעדי הדדית עם רשימת ישויות שנחסמו.
  • רשימת ישויות שנחסמו בקטגוריה: רשימת הישויות שנחסמו לשמות של קטגוריות. אם הקטגוריה אינה ריקה, תוצאות הסיווג שהקטגוריה הזו נמצאת בהן יסוננו. בלעדי (אופציונלי) עם רשימת היתרים.
    • הלוקאל של השמות לתצוגה: any string
    • מקסימום תוצאות: any integer
    • הסף לקבלת ניקוד: 0.0-1.0
    • רשימת היתרים של קטגוריות: vector of strings
    • רשימת הישויות שנחסמו בקטגוריה: vector of strings
    • הלוקאל של השמות לתצוגה: "en"
    • מקסימום תוצאות: -1
    • הסף לקבלת ניקוד: 0
    • רשימת ההיתרים של הקטגוריות: ריקה
    • רשימת הישויות שנחסמו של הקטגוריה: ריקה
    customGesturesClassifierOptions אפשרויות להגדרת ההתנהגות של מסווג התנועות בהתאמה אישית.
  • לוקאל של שמות תצוגה: הלוקאל של השמות לתצוגה שצוינו במטא-נתונים של דגם TFLite, אם רלוונטי.
  • מקסימום תוצאות: המספר המקסימלי של תוצאות סיווג מובילות שניתן להחזיר. אם הערך קטן מ-0, יוחזרו כל התוצאות הזמינות.
  • סף הניקוד: הציון שמתחתיו נדחות התוצאות. אם הערך שמוגדר הוא 0, כל התוצאות הזמינות יוחזרו.
  • רשימת היתרים של קטגוריות: רשימת ההיתרים של שמות הקטגוריות. אם הקטגוריה אינה ריקה, יסוננו תוצאות סיווג שהקטגוריה שלהן לא כלולה. בלעדי הדדית עם רשימת ישויות שנחסמו.
  • רשימת ישויות שנחסמו בקטגוריה: רשימת הישויות שנחסמו לשמות של קטגוריות. אם הקטגוריה אינה ריקה, תוצאות הסיווג שהקטגוריה הזו נמצאת בהן יסוננו. בלעדי (אופציונלי) עם רשימת היתרים.
    • הלוקאל של השמות לתצוגה: any string
    • מקסימום תוצאות: any integer
    • הסף לקבלת ניקוד: 0.0-1.0
    • רשימת היתרים של קטגוריות: vector of strings
    • רשימת הישויות שנחסמו בקטגוריה: vector of strings
    • הלוקאל של השמות לתצוגה: "en"
    • מקסימום תוצאות: -1
    • הסף לקבלת ניקוד: 0
    • רשימת ההיתרים של הקטגוריות: ריקה
    • רשימת הישויות שנחסמו של הקטגוריה: ריקה
    resultListener הגדרה זו מאפשרת למאזין התוצאות לקבל את תוצאות הסיווג באופן אסינכרוני כשמזהה התנועות נמצא במצב שידור חי. אפשר להשתמש רק כשמצב ריצה מוגדר ל-LIVE_STREAM ResultListener לא רלוונטי לא רלוונטי
    errorListener מגדירה האזנה לשגיאות כאופציונלי. ErrorListener לא רלוונטי לא רלוונטי

    הכנת הנתונים

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

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

    תמונה

    import com.google.mediapipe.framework.image.BitmapImageBuilder
    import com.google.mediapipe.framework.image.MPImage
    
    // Convert the input Bitmap object to an MPImage object to run inference
    val mpImage = BitmapImageBuilder(image).build()
        

    וידאו

    import com.google.mediapipe.framework.image.BitmapImageBuilder
    import com.google.mediapipe.framework.image.MPImage
    
    val argb8888Frame =
        if (frame.config == Bitmap.Config.ARGB_8888) frame
        else frame.copy(Bitmap.Config.ARGB_8888, false)
    
    // Convert the input Bitmap object to an MPImage object to run inference
    val mpImage = BitmapImageBuilder(argb8888Frame).build()
        

    שידור חי

    import com.google.mediapipe.framework.image.BitmapImageBuilder
    import com.google.mediapipe.framework.image.MPImage
    
    // Convert the input Bitmap object to an MPImage object to run inference
    val mpImage = BitmapImageBuilder(rotatedBitmap).build()
        

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

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

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

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

    תמונה

    val result = gestureRecognizer?.recognize(mpImage)
        

    וידאו

    val timestampMs = i * inferenceIntervalMs
    
    gestureRecognizer?.recognizeForVideo(mpImage, timestampMs)
        ?.let { recognizerResult ->
            resultList.add(recognizerResult)
        }
        

    שידור חי

    val mpImage = BitmapImageBuilder(rotatedBitmap).build()
    val frameTime = SystemClock.uptimeMillis()
    
    gestureRecognizer?.recognizeAsync(mpImage, frameTime)
        

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

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

    בקוד לדוגמה של מזהה התנועות, הפונקציות recognize, recognizeForVideo ו-recognizeAsync מוגדרות בקובץ GestureRecognizerHelper.kt.

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

    מזהה התנועות יוצר אובייקט תוצאה של זיהוי תנועה בכל הרצה של זיהוי. אובייקט התוצאה מכיל ציוני דרך של ידיים בקואורדינטות של תמונות, ציוני דרך שמזוהים עם קואורדינטות בעולם, קטגוריות של היד הדומיננטית(ידי שמאל/ימין) וקטגוריות של תנועות ידיים של הידיים שזוהו.

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

    ה-GestureRecognizerResult שהתקבל מכיל ארבעה רכיבים, וכל רכיב הוא מערך, שבו כל רכיב מכיל את התוצאה שזוהתה של יד אחת שזוהתה.

    • התאמה ליד הדומיננטית

      היד הדומיננטית מציינת אם הידיים שזוהו הן ידיים שמאליות או יד ימין.

    • תנועות

      קטגוריות התנועות שזוהו של הידיים שזוהו.

    • ציוני דרך

      יש 21 ציוני דרך לידיים, כל אחד מהם מורכב מקואורדינטות x, y ו-z. הקואורדינטות x ו-y מנורמלות ל-[0.0, 1.0] לפי הרוחב והגובה של התמונה, בהתאמה. הקואורדינטה z מייצגת את העומק של ציון הדרך, והעומק בפרק כף היד הוא המקור. ככל שהערך קטן יותר, כך היעד קרוב יותר למצלמה. העוצמה של z משתמשת בקנה מידה זהה לזה של x.

    • אתרים חשובים בעולם

      גם ציוני הדרך עם 21 היד מוצגים בקואורדינטות בעולם. כל ציון דרך מורכב מ-x, מ-y ומ-z, ומייצגים קואורדינטות תלת-ממדיות בעולם האמיתי, במטרים, שהמקור שלהן נמצא במרכז הגאומטרי של כף היד.

    GestureRecognizerResult:
      Handedness:
        Categories #0:
          index        : 0
          score        : 0.98396
          categoryName : Left
      Gestures:
        Categories #0:
          score        : 0.76893
          categoryName : Thumb_Up
      Landmarks:
        Landmark #0:
          x            : 0.638852
          y            : 0.671197
          z            : -3.41E-7
        Landmark #1:
          x            : 0.634599
          y            : 0.536441
          z            : -0.06984
        ... (21 landmarks for a hand)
      WorldLandmarks:
        Landmark #0:
          x            : 0.067485
          y            : 0.031084
          z            : 0.055223
        Landmark #1:
          x            : 0.063209
          y            : -0.00382
          z            : 0.020920
        ... (21 world landmarks for a hand)
    

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

    בקוד לדוגמה של מזהה התנועות, המחלקה GestureRecognizerResultsAdapter בקובץ GestureRecognizerResultsAdapter.kt מטפלת בתוצאות.