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

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

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

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

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

  1. כוללים ב-Podfile את רצפי ה-ML Kit הבאים:

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

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

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

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

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

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

השלבים הבאים