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

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

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

  1. אם עדיין לא הוספתם את Firebase לאפליקציה, צריך לבצע את הפעולות הבאות במדריך לתחילת העבודה.
  2. כוללים את ספריות ML Kit ב-Podfile:
    pod 'Firebase/MLVision', '6.25.0'
    # If you want to detect face contours (landmark detection and classification
    # don't require this additional model):
    pod 'Firebase/MLVisionFaceModel', '6.25.0'
    
    אחרי שמתקינים או מעדכנים את קבוצות ה-Pod של הפרויקט, חשוב לפתוח את ה-Xcode באמצעות ה-.xcworkspace שלו.
  3. מייבאים את Firebase לאפליקציה:

    Swift

    import Firebase

    Objective-C

    @import Firebase;

הנחיות להוספת תמונה

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

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

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

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

1. הגדרת גלאי הפנים

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

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

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

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

האם לנסות לזהות את 'סימני הדרך' של הפנים - עיניים, אוזניים, אף, לחיים, פה – מכל הפנים שזוהו.

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

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

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

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

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

הגודל המינימלי של הפנים לזיהוי, ביחס לתמונה.

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

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

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

לדוגמה, אפשר ליצור אובייקט VisionFaceDetectorOptions לפי אחת מהדוגמאות הבאות:

Swift

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

// Real-time contour detection of multiple faces
let options = VisionFaceDetectorOptions()
options.contourMode = .all

Objective-C

// High-accuracy landmark detection and face classification
FIRVisionFaceDetectorOptions *options = [[FIRVisionFaceDetectorOptions alloc] init];
options.performanceMode = FIRVisionFaceDetectorPerformanceModeAccurate;
options.landmarkMode = FIRVisionFaceDetectorLandmarkModeAll;
options.classificationMode = FIRVisionFaceDetectorClassificationModeAll;

// Real-time contour detection of multiple faces
FIRVisionFaceDetectorOptions *options = [[FIRVisionFaceDetectorOptions alloc] init];
options.contourMode = FIRVisionFaceDetectorContourModeAll;

2. הפעלת גלאי הפנים

כדי לזהות פנים בתמונה, יש להעביר את התמונה כ-UIImage או כ- CMSampleBufferRef ל-detect(in:) של VisionFaceDetector method:

  1. מקבלים מופע של VisionFaceDetector:

    Swift

    lazy var vision = Vision.vision()
    
    let faceDetector = vision.faceDetector(options: options)

    Objective-C

    FIRVision *vision = [FIRVision vision];
    FIRVisionFaceDetector *faceDetector = [vision faceDetector];
    // Or, to change the default settings:
    // FIRVisionFaceDetector *faceDetector =
    //     [vision faceDetectorWithOptions:options];
  2. יצירת אובייקט VisionImage באמצעות UIImage או CMSampleBufferRef

    כדי להשתמש ב-UIImage:

    1. במקרה הצורך, מסובבים את התמונה כך ש-imageOrientation הוא .up.
    2. יצירת אובייקט VisionImage באמצעות סיבוב נכון UIImage. אל תציינו מטא-נתונים של סבב, ברירת המחדל יש להשתמש בערך .topLeft.

      Swift

      let image = VisionImage(image: uiImage)

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

    כדי להשתמש ב-CMSampleBufferRef:

    1. יוצרים אובייקט VisionImageMetadata שמציין את הכיוון של נתוני התמונה שמכיל מאגר CMSampleBufferRef.

      כדי לקבל את כיוון התמונה:

      Swift

      func imageOrientation(
          deviceOrientation: UIDeviceOrientation,
          cameraPosition: AVCaptureDevice.Position
          ) -> VisionDetectorImageOrientation {
          switch deviceOrientation {
          case .portrait:
              return cameraPosition == .front ? .leftTop : .rightTop
          case .landscapeLeft:
              return cameraPosition == .front ? .bottomLeft : .topLeft
          case .portraitUpsideDown:
              return cameraPosition == .front ? .rightBottom : .leftBottom
          case .landscapeRight:
              return cameraPosition == .front ? .topRight : .bottomRight
          case .faceDown, .faceUp, .unknown:
              return .leftTop
          }
      }

      Objective-C

      - (FIRVisionDetectorImageOrientation)
          imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                                 cameraPosition:(AVCaptureDevicePosition)cameraPosition {
        switch (deviceOrientation) {
          case UIDeviceOrientationPortrait:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationLeftTop;
            } else {
              return FIRVisionDetectorImageOrientationRightTop;
            }
          case UIDeviceOrientationLandscapeLeft:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationBottomLeft;
            } else {
              return FIRVisionDetectorImageOrientationTopLeft;
            }
          case UIDeviceOrientationPortraitUpsideDown:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationRightBottom;
            } else {
              return FIRVisionDetectorImageOrientationLeftBottom;
            }
          case UIDeviceOrientationLandscapeRight:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationTopRight;
            } else {
              return FIRVisionDetectorImageOrientationBottomRight;
            }
          default:
            return FIRVisionDetectorImageOrientationTopLeft;
        }
      }

      לאחר מכן, יוצרים את אובייקט המטא-נתונים:

      Swift

      let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
      let metadata = VisionImageMetadata()
      metadata.orientation = imageOrientation(
          deviceOrientation: UIDevice.current.orientation,
          cameraPosition: cameraPosition
      )

      Objective-C

      FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
      AVCaptureDevicePosition cameraPosition =
          AVCaptureDevicePositionBack;  // Set to the capture device you used.
      metadata.orientation =
          [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                       cameraPosition:cameraPosition];
    2. יוצרים אובייקט VisionImage באמצעות אובייקט CMSampleBufferRef והמטא-נתונים של הסבב:

      Swift

      let image = VisionImage(buffer: sampleBuffer)
      image.metadata = metadata

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  3. לאחר מכן, מעבירים את התמונה לשיטה detect(in:):

    Swift

    faceDetector.process(visionImage) { faces, error in
      guard error == nil, let faces = faces, !faces.isEmpty else {
        // ...
        return
      }
    
      // Faces detected
      // ...
    }

    Objective-C

    [faceDetector detectInImage:image
                     completion:^(NSArray<FIRVisionFace *> *faces,
                                  NSError *error) {
      if (error != nil) {
        return;
      } else if (faces != nil) {
        // Recognized faces
      }
    }];

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

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

Swift

for face in faces {
  let frame = face.frame
  if face.hasHeadEulerAngleY {
    let rotY = face.headEulerAngleY  // Head is rotated to the right rotY degrees
  }
  if face.hasHeadEulerAngleZ {
    let rotZ = face.headEulerAngleZ  // Head is rotated upward 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 (FIRVisionFace *face in faces) {
  // Boundaries of face in image
  CGRect frame = face.frame;

  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):
  FIRVisionFaceLandmark *leftEar = [face landmarkOfType:FIRFaceLandmarkTypeLeftEar];
  if (leftEar != nil) {
    FIRVisionPoint *leftEarPosition = leftEar.position;
  }

  // If contour detection was enabled:
  FIRVisionFaceContour *upperLipBottomContour = [face contourOfType:FIRFaceContourTypeUpperLipBottom];
  if (upperLipBottomContour != nil) {
    NSArray<FIRVisionPoint *> *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 הזה.

  • ויסות נתונים (throttle) קריאות לגלאי. אם פריים חדש בסרטון הופך בזמן שהגלאי פועל, משחררים את הפריים.
  • אם אתם משתמשים בפלט של הגלאי כדי להוסיף שכבת-על של גרפיקה לתמונה הקלט, קודם צריך לקבל את התוצאה מ-ML Kit, ואז לבצע עיבוד (רנדור) של התמונה ולהוסיף את שכבת-העל בשלב אחד. כך תוכלו להציג את משטח המסך פעם אחת בלבד לכל מסגרת קלט. לדוגמה, תוכלו לעיין בכיתות previewOverlayView ו-FIRDetectionOverlayView באפליקציית הדוגמה הזו.