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

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

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

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

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

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

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

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

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

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

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

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

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

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

בין אם לנסות לזהות את הפנים && המירכאות, האוזניים, האוזניים, האף, הלחיים והפה, בכל הפנים שזוהו.

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

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

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

האם לסווג את הקטגוריות לפי קטגוריות כמו "smiling" ו-"eyes open".

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. עיבוד התמונה

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