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

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

רוצה לנסות?

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

  1. כוללים ב-Podfile את רצפי ה-ML Kit הבאים:
    pod 'GoogleMLKit/FaceDetection', '15.5.0'
    
  2. אחרי שמתקינים או מעדכנים את קבוצות ה-Pod של הפרויקט, פותחים את פרויקט 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:) של הגלאי. שיחת טלפון שיטה זו מ AVCaptureVideoDataOutputSampleBufferDelegate פונקציה captureOutput(_, didOutput:from:) לקבלת תוצאות בסרטון הנתון באופן סינכרוני מסגרת. שמור את של AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames בתור true כדי לווסת שיחות למזהה. אם תג חדש פריים הווידאו יהפוך לזמין כשהגלאי פועל, הוא יוסר.
  • אם משתמשים בפלט של הגלאי כדי להציג גרפיקה בשכבת-על מקבלים קודם את התוצאה מ-ML Kit ואז מעבדים את התמונה וליצור שכבת-על בשלב אחד. כך תוכלו להציג את משטח המסך רק פעם אחת לכל מסגרת קלט שעברה עיבוד. אפשר לעיין בתצוגה updatePreviewOverlayViewWithLastFrame בדוגמת המדריך למתחילים ל-ML Kit.