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

השלבים הבאים