זיהוי פנים באמצעות ערכת ML ב-iOS

אפשר להשתמש ב-ML Kit כדי לזהות פנים בתמונות ובסרטונים.

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

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

  1. יש לכלול את רצפי ה-ML Kit ב-Podfile:
    pod 'GoogleMLKit/FaceDetection', '3.2.0'
    
  2. אחרי שמתקינים או מעדכנים את ה-Pods של הפרויקט, צריך לפתוח את פרויקט ה-Xcode באמצעות .xcworkspace שלו. ערכת ML Kit נתמכת ב-Xcode מגרסה 12.4 ואילך.

הנחיות להזנת תמונה

עבור זיהוי פנים, עליך להשתמש בתמונה במידות של 480x360 פיקסלים לפחות. כדי ש-ML Kit יזהה באופן מדויק את הפנים, תמונות הקלט חייבות להכיל פנים שמיוצגות על ידי כמות מספיקה של נתוני פיקסלים. באופן כללי, כל פנים שרוצים לזהות בתמונה צריכות להיות בגודל של 100x100 פיקסלים לפחות. כדי לזהות את קווי המתאר של פנים, מערכת ML Kit מחייבת קלט ברזולוציה גבוהה יותר: כל פנים צריכות להיות בגודל של 200x200 פיקסלים לפחות.

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

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

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

1. הגדרה של מזהה הפנים

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

הגדרות
performanceMode fast (ברירת מחדל) | accurate

העדפה של מהירות או דיוק בזיהוי פנים.

landmarkMode none (ברירת מחדל) | all

האם לנסות לזהות את סימני "הציונים" של הפנים – עיניים, אוזניים, אף, לחיים, פה – של כל הפנים שזוהו.

contourMode none (ברירת מחדל) | all

האם לזהות את קווי המתאר של תווי פנים. המערכת מזהה קווי מתאר רק של הפנים הבולטות ביותר בתמונה.

classificationMode none (ברירת מחדל) | all

אם לסווג פנים לקטגוריות כמו 'מחייך' ו'עיניים פקוחות'.

minFaceSize CGFloat (ברירת המחדל: 0.1)

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

isTrackingEnabled false (ברירת מחדל) | true

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

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

לדוגמה, בנו אובייקט FaceDetectorOptions כמו באחת מהדוגמאות הבאות:

Swift

// High-accuracy landmark detection and face classification
let options = FaceDetectorOptions()
options.performanceMode = .accurate
options.landmarkMode = .all
options.classificationMode = .all

// Real-time contour detection of multiple faces
// options.contourMode = .all

Objective-C

// High-accuracy landmark detection and face classification
MLKFaceDetectorOptions *options = [[MLKFaceDetectorOptions alloc] init];
options.performanceMode = MLKFaceDetectorPerformanceModeAccurate;
options.landmarkMode = MLKFaceDetectorLandmarkModeAll;
options.classificationMode = MLKFaceDetectorClassificationModeAll;

// Real-time contour detection of multiple faces
// options.contourMode = MLKFaceDetectorContourModeAll;

2. הכנת התמונה לקלט

כדי לזהות פנים בתמונה, מעבירים את התמונה כ-UIImage או כ-CMSampleBufferRef ל-FaceDetector בשיטה process(_:completion:) או results(in:):

יוצרים אובייקט 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. הורדת מופע של FaceDetector

מקבלים מופע של FaceDetector:

Swift

let faceDetector = FaceDetector.faceDetector(options: options)

Objective-C

MLKFaceDetector *faceDetector = [MLKFaceDetector faceDetectorWithOptions:options];
      

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

לאחר מכן, מעבירים את התמונה ל-method process():

Swift

weak var weakSelf = self
faceDetector.process(visionImage) { faces, error in
  guard let strongSelf = weakSelf else {
    print("Self is nil!")
    return
  }
  guard error == nil, let faces = faces, !faces.isEmpty else {
    // ...
    return
  }

  // Faces detected
  // ...
}

Objective-C

[faceDetector processImage:image
                completion:^(NSArray<MLKFace *> *faces,
                             NSError *error) {
  if (error != nil) {
    return;
  }
  if (faces.count > 0) {
    // Recognized faces
  }
}];

5. קבלת מידע על פנים שזוהו

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

Swift

for face in faces {
  let frame = face.frame
  if face.hasHeadEulerAngleX {
    let rotX = face.headEulerAngleX  // Head is rotated to the uptoward rotX degrees
  }
  if face.hasHeadEulerAngleY {
    let rotY = face.headEulerAngleY  // Head is rotated to the right rotY degrees
  }
  if face.hasHeadEulerAngleZ {
    let rotZ = face.headEulerAngleZ  // Head is tilted sideways rotZ degrees
  }

  // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
  // nose available):
  if let leftEye = face.landmark(ofType: .leftEye) {
    let leftEyePosition = leftEye.position
  }

  // If contour detection was enabled:
  if let leftEyeContour = face.contour(ofType: .leftEye) {
    let leftEyePoints = leftEyeContour.points
  }
  if let upperLipBottomContour = face.contour(ofType: .upperLipBottom) {
    let upperLipBottomPoints = upperLipBottomContour.points
  }

  // If classification was enabled:
  if face.hasSmilingProbability {
    let smileProb = face.smilingProbability
  }
  if face.hasRightEyeOpenProbability {
    let rightEyeOpenProb = face.rightEyeOpenProbability
  }

  // If face tracking was enabled:
  if face.hasTrackingID {
    let trackingId = face.trackingID
  }
}

Objective-C

for (MLKFace *face in faces) {
  // Boundaries of face in image
  CGRect frame = face.frame;
  if (face.hasHeadEulerAngleX) {
    CGFloat rotX = face.headEulerAngleX;  // Head is rotated to the upward rotX degrees
  }
  if (face.hasHeadEulerAngleY) {
    CGFloat rotY = face.headEulerAngleY;  // Head is rotated to the right rotY degrees
  }
  if (face.hasHeadEulerAngleZ) {
    CGFloat rotZ = face.headEulerAngleZ;  // Head is tilted sideways rotZ degrees
  }

  // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
  // nose available):
  MLKFaceLandmark *leftEar = [face landmarkOfType:FIRFaceLandmarkTypeLeftEar];
  if (leftEar != nil) {
    MLKVisionPoint *leftEarPosition = leftEar.position;
  }

  // If contour detection was enabled:
  MLKFaceContour *upperLipBottomContour = [face contourOfType:FIRFaceContourTypeUpperLipBottom];
  if (upperLipBottomContour != nil) {
    NSArray<MLKVisionPoint *> *upperLipBottomPoints = upperLipBottomContour.points;
    if (upperLipBottomPoints.count > 0) {
      NSLog("Detected the bottom contour of the subject's upper lip.")
    }
  }

  // If classification was enabled:
  if (face.hasSmilingProbability) {
    CGFloat smileProb = face.smilingProbability;
  }
  if (face.hasRightEyeOpenProbability) {
    CGFloat rightEyeOpenProb = face.rightEyeOpenProbability;
  }

  // If face tracking was enabled:
  if (face.hasTrackingID) {
    NSInteger trackingID = face.trackingID;
  }
}

דוגמה למתארי פנים

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

בתמונה הבאה אפשר לראות איך הנקודות האלה ממופות לפנים. אפשר ללחוץ על התמונה כדי להגדיל אותה:

דוגמה לרשת עם מתאר הפנים שזוהתה

זיהוי פנים בזמן אמת

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

  • תוכלו להגדיר את גלאי הפנים כך שישתמשו בזיהוי קווי פנים או בסיווג ובזיהוי של ציוני פנים, אבל לא בשניהם:

    זיהוי קווי מתאר
    זיהוי של ציוני דרך
    סיווג
    זיהוי וסיווג של ציוני דרך
    זיהוי קווי מתאר וזיהוי ציוני דרך
    זיהוי וסיווג קווי מתאר
    זיהוי קווי מתאר, זיהוי ציוני דרך וסיווג

  • הפעלת מצב fast (מופעל כברירת מחדל).

  • כדאי לצלם תמונות ברזולוציה נמוכה יותר. עם זאת, חשוב לזכור את הדרישות לגבי מידות התמונה של ה-API הזה.

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