זיהוי תנוחות באמצעות ML Kit ב-iOS

ב-ML Kit יש שתי ערכות SDK לאופטימיזציה של זיהוי תנוחות.

שם ה-SDKPoseDetectionPoseDetectionAccurate
הטמעההנכסים של הגלאי הבסיסי מקושרים באופן סטטי לאפליקציה שלכם בזמן ה-build.הנכסים של הגלאי המדויק מקושרים באופן סטטי לאפליקציה שלכם בזמן ה-build.
גודל האפליקציהעד 29.6MBעד 33.2MB
ביצועיםiPhone X: ‏ ~45FPSiPhone X: ‏ ~29FPS

רוצה לנסות?

לפני שמתחילים

  1. מוסיפים את ה-pods הבאים של ML Kit ל-Podfile:

    # If you want to use the base implementation:
    pod 'GoogleMLKit/PoseDetection', '7.0.0'
    
    # If you want to use the accurate implementation:
    pod 'GoogleMLKit/PoseDetectionAccurate', '7.0.0'
    
  2. אחרי שמתקינים או מעדכנים את ה-pods של הפרויקט, פותחים את פרויקט Xcode באמצעות ה-xcworkspace שלו. ML Kit נתמך ב-Xcode בגרסה 13.2.1 ואילך.

1. יצירת מכונה של PoseDetector

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

PoseDetector אפשרויות

מצב זיהוי

ה-PoseDetector פועל בשני מצבי זיהוי. חשוב לבחור את האפשרות שמתאימה לתרחיש השימוש שלכם.

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

מציינים את האפשרויות של גלאי התנוחה:

Swift

// Base pose detector with streaming, when depending on the PoseDetection SDK
let options = PoseDetectorOptions()
options.detectorMode = .stream

// Accurate pose detector on static images, when depending on the
// PoseDetectionAccurate SDK
let options = AccuratePoseDetectorOptions()
options.detectorMode = .singleImage

Objective-C

// Base pose detector with streaming, when depending on the PoseDetection SDK
MLKPoseDetectorOptions *options = [[MLKPoseDetectorOptions alloc] init];
options.detectorMode = MLKPoseDetectorModeStream;

// Accurate pose detector on static images, when depending on the
// PoseDetectionAccurate SDK
MLKAccuratePoseDetectorOptions *options =
    [[MLKAccuratePoseDetectorOptions alloc] init];
options.detectorMode = MLKPoseDetectorModeSingleImage;

לבסוף, מקבלים מופע של PoseDetector. מעבירים את האפשרויות שציינתם:

Swift

let poseDetector = PoseDetector.poseDetector(options: options)

Objective-C

MLKPoseDetector *poseDetector =
    [MLKPoseDetector poseDetectorWithOptions:options];

2. הכנת קובץ הקלט

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

יוצרים אובייקט VisionImage באמצעות UIImage או CMSampleBuffer.

אם אתם משתמשים ב-UIImage, עליכם לפעול לפי השלבים הבאים:

  • יוצרים אובייקט VisionImage באמצעות UIImage. חשוב לציין את הערך הנכון של .orientation.

    Swift

    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation

    Objective-C

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

אם אתם משתמשים ב-CMSampleBuffer, עליכם לפעול לפי השלבים הבאים:

  • מציינים את הכיוון של נתוני התמונה שמכיל השדה CMSampleBuffer.

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

    Swift

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    Objective-C

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • יוצרים אובייקט VisionImage באמצעות האובייקט CMSampleBuffer והכיוון:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    Objective-C

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. עיבוד התמונה

מעבירים את VisionImage לאחת משיטות העיבוד של התמונה במזהה התנוחה. אפשר להשתמש בשיטה האסינכרונית process(image:) או בשיטה הסינכרונית results().

כדי לזהות אובייקטים באופן סינכרוני:

Swift

var results: [Pose]
do {
  results = try poseDetector.results(in: image)
} catch let error {
  print("Failed to detect pose with error: \(error.localizedDescription).")
  return
}
guard let detectedPoses = results, !detectedPoses.isEmpty else {
  print("Pose detector returned no results.")
  return
}

// Success. Get pose landmarks here.

Objective-C

NSError *error;
NSArray *poses = [poseDetector resultsInImage:image error:&error];
if (error != nil) {
  // Error.
  return;
}
if (poses.count == 0) {
  // No pose detected.
  return;
}

// Success. Get pose landmarks here.

כדי לזהות אובייקטים באופן אסינכרוני:

Swift

poseDetector.process(image) { detectedPoses, error in
  guard error == nil else {
    // Error.
    return
  }
  guard !detectedPoses.isEmpty else {
    // No pose detected.
    return
  }

  // Success. Get pose landmarks here.
}

Objective-C

[poseDetector processImage:image
                completion:^(NSArray * _Nullable poses,
                             NSError * _Nullable error) {
                    if (error != nil) {
                      // Error.
                      return;
                    }
                    if (poses.count == 0) {
                      // No pose detected.
                      return;
                    }

                    // Success. Get pose landmarks here.
                  }];

4. קבלת מידע על התנוחה שזוהתה

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

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

אם לא זוהה אדם, המערך יהיה ריק.

Swift

for pose in detectedPoses {
  let leftAnkleLandmark = pose.landmark(ofType: .leftAnkle)
  if leftAnkleLandmark.inFrameLikelihood > 0.5 {
    let position = leftAnkleLandmark.position
  }
}

Objective-C

for (MLKPose *pose in detectedPoses) {
  MLKPoseLandmark *leftAnkleLandmark =
      [pose landmarkOfType:MLKPoseLandmarkTypeLeftAnkle];
  if (leftAnkleLandmark.inFrameLikelihood > 0.5) {
    MLKVision3DPoint *position = leftAnkleLandmark.position;
  }
}

טיפים לשיפור הביצועים

איכות התוצאות תלויה באיכות של קובץ הקלט:

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

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

  • משתמשים ב-SDK הבסיסי של PoseDetection ובמצב הזיהוי stream.
  • כדאי לצלם תמונות ברזולוציה נמוכה יותר. עם זאת, חשוב לזכור גם את הדרישות לגבי מידות התמונות ב-API הזה.
  • כדי לעבד פריימים של סרטונים, משתמשים ב-API הסינכרוני results(in:) של הגלאי. כדי לקבל תוצאות מסונכרנות מהפריים הנתון של הסרטון, צריך להפעיל את השיטה הזו מהפונקציה captureOutput(_, didOutput:from:) של AVCaptureVideoDataOutputSampleBufferDelegate. כדי להגביל את הקריאות לגלאי, משאירים את הערך של alwaysDiscardsLateVideoFrames ב-AVCaptureVideoDataOutput כ-true. אם פריים חדש של סרטון יהפוך לזמין בזמן שהגלאי פועל, הוא יידחה.
  • אם משתמשים בפלט של הגלאי כדי להוסיף שכבת-על של גרפיקה לתמונה הקלט, קודם צריך לקבל את התוצאה מ-ML Kit ואז לבצע עיבוד (רנדור) של התמונה והשכבה העליונה בשלב אחד. כך מבצעים רינדור למשטח התצוגה רק פעם אחת לכל מסגרת קלט שעברה עיבוד. לדוגמה, אפשר לעיין במחלקות previewOverlayView ו-MLKDetectionOverlayView באפליקציית הדוגמה.

השלבים הבאים