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

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

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

קוד לדוגמה

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

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

הורדת הקוד

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

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

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

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

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

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

  • GestureRecognizerHelper.kt – הפונקציה הזו מאתחלת את הכלי לזיהוי תנועות ומטפלת בבחירת המודל והנציג.
  • MainActivity.kt – הטמעת האפליקציה, כולל קריאה ל-GestureRecognizerHelper ול-GestureRecognizerResultsAdapter.
  • GestureRecognizerResultsAdapter.kt – טיפול בתוצאות ויצירת פורמט להן.

הגדרה

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

יחסי תלות

המשימה 'זיהוי תנועות' משתמשת בספרייה 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 Gesture Recognizer משתמשת בפונקציה 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)
    

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

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

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

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

IMAGE: המצב להזנת תמונה אחת.

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

LIVE_STREAM: המצב של סטרימינג בשידור חי של נתוני קלט, למשל ממצלמה. במצב הזה, צריך להפעיל את resultListener כדי להגדיר מאזין שיקבל את התוצאות באופן אסינכרוני.
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
numHands המספר המקסימלי של ידיים שאפשר לזהות באמצעות GestureRecognizer הוא 10. 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, כל התוצאות הזמינות יחזרו.
  • רשימת ההיתרים של קטגוריות: רשימת ההיתרים של שמות הקטגוריות. אם הערך לא ריק, תוצאות הסיווג שהקטגוריה שלהן לא נכללת בקבוצה הזו יסוננו. לא ניתן להשתמש בו במקביל ל-denylist.
  • רשימת קטגוריות חסרות גישה: רשימת השמות של הקטגוריות שנחסמו. אם הערך לא ריק, תוצאות הסיווג שהקטגוריה שלהן נמצאת בקבוצה הזו יסוננו. לא ניתן להשתמש בו במקביל לרשימת היתרים.
    • אזור הלשון של שמות התצוגה: any string
    • מספר תוצאות מקסימלי: any integer
    • סף הציון: 0.0-1.0
    • רשימת ההיתרים של הקטגוריה: vector of strings
    • רשימת הישויות שנחסמו בקטגוריה: vector of strings
    • אזור הלשון של שמות התצוגה: "en"
    • מספר תוצאות מקסימלי: -1
    • סף הציון: 0
    • רשימת ההיתרים של הקטגוריה: ריקה
    • רשימת הישויות שנחסמו בקטגוריה: ריקה
    customGesturesClassifierOptions אפשרויות להגדרת ההתנהגות של הסיווג של התנועות בהתאמה אישית.
  • אזור הלשון של השמות המוצגים: אזור הלשון שבו נעשה שימוש בשמות המוצגים שצוינו באמצעות המטא-נתונים של מודל TFLite, אם יש כאלה.
  • מספר התוצאות המקסימלי: מספר התוצאות המקסימלי של סיווגים עם הדירוג הגבוה ביותר שיוחזר. אם הערך < 0, כל התוצאות הזמינות יחזרו.
  • סף הציון: הציון שמתחתיו התוצאות נדחות. אם הערך מוגדר ל-0, כל התוצאות הזמינות יחזרו.
  • רשימת ההיתרים של קטגוריות: רשימת ההיתרים של שמות הקטגוריות. אם הערך לא ריק, תוצאות הסיווג שהקטגוריה שלהן לא נכללת בקבוצה הזו יסוננו. לא ניתן להשתמש בו במקביל ל-denylist.
  • רשימת קטגוריות חסרות גישה: רשימת השמות של הקטגוריות שנחסמו. אם הערך לא ריק, תוצאות הסיווג שהקטגוריה שלהן נמצאת בקבוצה הזו יסוננו. לא ניתן להשתמש בו במקביל לרשימת היתרים.
    • אזור הלשון של שמות התצוגה: 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()
        

    בקוד לדוגמה של Gesture Recognizer, הכנת הנתונים מתבצעת בקובץ 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 מטפלת בתוצאות.