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

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

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

אני רוצה לנסות

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

  1. יש לכלול את הפודים הבאים של ערכת ML ב-Podfile:

    # If you want to use the base implementation:
    pod 'GoogleMLKit/PoseDetection', '3.2.0'
    
    # If you want to use the accurate implementation:
    pod 'GoogleMLKit/PoseDetectionAccurate', '3.2.0'
    
  2. אחרי שמתקינים או מעדכנים את רכיבי ה-pod של הפרויקט, פותחים את פרויקט Xcode באמצעות ה-xcworkspace שלו. ערכת ML נתמכת ב-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 ל-handler להשלמה או מחזיר את המערך, בהתאם לשיטה האסינכרונית או הסינכרונית שנקראה.

אם האדם לא היה בתוך התמונה, המודל מקצה את הקואורדינטות החסרות מחוץ למסגרת וערכי 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 כדי לקבל באופן סינכרוני תוצאות מהפריים הנתון. חשוב לשמור על AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames כנכון כדי לווסת קריאות לגלאי. אם מתאפשרת פריים חדש לסרטון בזמן שהמזהה פועל, היא תוסר.
  • אם משתמשים בפלט של הגלאי כדי להציג גרפיקה כשכבת-על בתמונת הקלט, קודם צריך לקבל את התוצאה מ-ML Kit, ואז לעבד את התמונה ושכבת-העל בשלב אחד. כך, עיבוד הנתונים מתבצע על פני המסך פעם אחת בלבד לכל מסגרת קלט מעובדת. לדוגמה, אפשר לעיין במחלקות previewOverlayView ו-MLKDetectionOverlayView באפליקציה לדוגמה של חלון הראווה.

השלבים הבאים