זיהוי פנים עם ערכת ML באנדרואיד

ניתן להשתמש בערכת ML לאיתור פנים בתמונות ובווידיאו.

לפני שאתה מתחיל

  1. אם לא עשית זאת עדיין, להוסיף Firebase לפרויקט Android שלך .
  2. מוסיפים את התלות עבור ספריות אנדרואיד קיט ML למודול שלך (ברמת האפליקציה) קובץ Gradle (בדרך כלל app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      // If you want to detect face contours (landmark detection and classification
      // don't require this additional model):
      implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
    }
    
  3. אופציונלי אך מומלץ: להגדיר את האפליקציה שלך באופן אוטומטי להוריד את מודל ML למכשיר אחרי האפליקציה שלך מותקנת מחנות Play.

    לשם כך, להוסיף את ההצהרה הבאה של האפליקציה שלך AndroidManifest.xml קובץ:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="face" />
      <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    
    אם לא תפעיל להתקין במשרת הורדות מודל, המודל הורד בפעם הראשונה שתפעיל את הגלאי. בקשות שתשלח לפני השלמת ההורדה לא יניבו תוצאות.

הקלט הנחיות לתמונות

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

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

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

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

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

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

הגדרות
מצב הופעה FAST (ברירת מחדל) | ACCURATE

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

זיהוי ציוני דרך NO_LANDMARKS (ברירת מחדל) | ALL_LANDMARKS

האם לנסות לזהות "ציוני דרך" בפנים: עיניים, אוזניים, אף, לחיים, פה וכו '.

לזהות קווי מתאר NO_CONTOURS (ברירת מחדל) | ALL_CONTOURS

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

לסווג פנים NO_CLASSIFICATIONS (ברירת המחדל) | ALL_CLASSIFICATIONS

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

גודל פנים מינימלי float (ברירת מחדל: 0.1f )

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

אפשר מעקב פנים false (ברירת מחדל) | true

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

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

לדוגמה:

ג'אווה

// High-accuracy landmark detection and face classification
FirebaseVisionFaceDetectorOptions highAccuracyOpts =
        new FirebaseVisionFaceDetectorOptions.Builder()
                .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
                .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
                .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
                .build();

// Real-time contour detection of multiple faces
FirebaseVisionFaceDetectorOptions realTimeOpts =
        new FirebaseVisionFaceDetectorOptions.Builder()
                .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
                .build();

קוטלין+KTX

// High-accuracy landmark detection and face classification
val highAccuracyOpts = FirebaseVisionFaceDetectorOptions.Builder()
        .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
        .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
        .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
        .build()

// Real-time contour detection of multiple faces
val realTimeOpts = FirebaseVisionFaceDetectorOptions.Builder()
        .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
        .build()

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

כדי לזהות פרצופים בתמונה, ליצור FirebaseVisionImage אובייקט מתוך אחת מעמד Bitmap , media.Image , ByteBuffer מערך, בתים, או קובץ בהתקן. ואז, להעביר את FirebaseVisionImage האובייקט אל FirebaseVisionFaceDetector של detectInImage השיטה.

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

  1. צור FirebaseVisionImage אובייקט מתוך התמונה שלך.

    • כדי ליצור FirebaseVisionImage האובייקט מנקודת media.Image אובייקט, כגון בעת לכידת תמונה מתוך המצלמה של המכשיר, להעביר את media.Image האובייקט ואת הסיבוב של התמונה FirebaseVisionImage.fromMediaImage() .

      אם אתה משתמש CameraX הספרייה, OnImageCapturedListener ו ImageAnalysis.Analyzer הכיתות לחשב את ערך הסיבוב בשבילך, כך שאתה רק צריך להמיר את הסיבוב לאחד של ML קיט ROTATION_ קבוע לפני פניית FirebaseVisionImage.fromMediaImage() :

      ג'אווה

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }
      

      קוטלין+KTX

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }
      

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

      ג'אווה

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      private int getRotationCompensation(String cameraId, Activity activity, Context context)
              throws CameraAccessException {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
          int rotationCompensation = ORIENTATIONS.get(deviceRotation);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      קוטלין+KTX

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      @Throws(CameraAccessException::class)
      private fun getRotationCompensation(cameraId: String, activity: Activity, context: Context): Int {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          val deviceRotation = activity.windowManager.defaultDisplay.rotation
          var rotationCompensation = ORIENTATIONS.get(deviceRotation)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      ואז, להעביר את media.Image אובייקט וערך סיבוב כדי FirebaseVisionImage.fromMediaImage() :

      ג'אווה

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      קוטלין+KTX

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • כדי ליצור FirebaseVisionImage אובייקט מקובץ URI, להעביר את הקשר אפליקצית קובץ אורי FirebaseVisionImage.fromFilePath() . תכונה זו שימושית כאשר אתה משתמש ACTION_GET_CONTENT כוונה מהמשתמש לבחור תמונה מאפליקציית הגלריה שלהם.

      ג'אווה

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      קוטלין+KTX

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • כדי ליצור FirebaseVisionImage אובייקט מנקודת ByteBuffer או למערך בתים, ראשון לחשב את סיבוב התמונה כמתואר לעיל media.Image קלט.

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

      ג'אווה

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      קוטלין+KTX

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      השתמש חיץ או מערך, לבין האובייקט מטה, כדי ליצור FirebaseVisionImage אובייקט:

      ג'אווה

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
      // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      קוטלין+KTX

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • כדי ליצור FirebaseVisionImage האובייקט מנקודת Bitmap האובייקט:

      ג'אווה

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      קוטלין+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      הדימוי המיוצג על ידי Bitmap האובייקט חייב להיות זקוף, ללא רוטציה נוספת הנדרשת.
  2. קבל מופע של FirebaseVisionFaceDetector :

    ג'אווה

    FirebaseVisionFaceDetector detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options);

    קוטלין+KTX

    val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)
  3. לבסוף, עוברים את התמונה detectInImage השיטה:

    ג'אווה

    Task<List<FirebaseVisionFace>> result =
            detector.detectInImage(image)
                    .addOnSuccessListener(
                            new OnSuccessListener<List<FirebaseVisionFace>>() {
                                @Override
                                public void onSuccess(List<FirebaseVisionFace> faces) {
                                    // Task completed successfully
                                    // ...
                                }
                            })
                    .addOnFailureListener(
                            new OnFailureListener() {
                                @Override
                                public void onFailure(@NonNull Exception e) {
                                    // Task failed with an exception
                                    // ...
                                }
                            });

    קוטלין+KTX

    val result = detector.detectInImage(image)
            .addOnSuccessListener { faces ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }

3. קבל מידע על פרצופים שזוהו

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

ג'אווה

for (FirebaseVisionFace face : faces) {
    Rect bounds = face.getBoundingBox();
    float rotY = face.getHeadEulerAngleY();  // Head is rotated to the right rotY degrees
    float rotZ = face.getHeadEulerAngleZ();  // Head is tilted sideways rotZ degrees

    // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
    // nose available):
    FirebaseVisionFaceLandmark leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR);
    if (leftEar != null) {
        FirebaseVisionPoint leftEarPos = leftEar.getPosition();
    }

    // If contour detection was enabled:
    List<FirebaseVisionPoint> leftEyeContour =
            face.getContour(FirebaseVisionFaceContour.LEFT_EYE).getPoints();
    List<FirebaseVisionPoint> upperLipBottomContour =
            face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).getPoints();

    // If classification was enabled:
    if (face.getSmilingProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        float smileProb = face.getSmilingProbability();
    }
    if (face.getRightEyeOpenProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        float rightEyeOpenProb = face.getRightEyeOpenProbability();
    }

    // If face tracking was enabled:
    if (face.getTrackingId() != FirebaseVisionFace.INVALID_ID) {
        int id = face.getTrackingId();
    }
}

קוטלין+KTX

for (face in faces) {
    val bounds = face.boundingBox
    val rotY = face.headEulerAngleY // Head is rotated to the right rotY degrees
    val rotZ = face.headEulerAngleZ // Head is tilted sideways rotZ degrees

    // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
    // nose available):
    val leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR)
    leftEar?.let {
        val leftEarPos = leftEar.position
    }

    // If contour detection was enabled:
    val leftEyeContour = face.getContour(FirebaseVisionFaceContour.LEFT_EYE).points
    val upperLipBottomContour = face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).points

    // If classification was enabled:
    if (face.smilingProbability != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        val smileProb = face.smilingProbability
    }
    if (face.rightEyeOpenProbability != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        val rightEyeOpenProb = face.rightEyeOpenProbability
    }

    // If face tracking was enabled:
    if (face.trackingId != FirebaseVisionFace.INVALID_ID) {
        val id = face.trackingId
    }
}

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

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

התמונה הבאה ממחישה כיצד נקודות אלה ממפות לפנים (לחץ על התמונה להגדלה):

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

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

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

    זיהוי קווי מתאר
    איתור ציון דרך
    מִיוּן
    איתור וסיווג ציוני דרך
    איתור קווי מתאר וזיהוי ציון דרך
    איתור וסיווג קווי המתאר
    איתור קווי מתאר, איתור ציון דרך וסיווג

  • אפשר FAST מצב (מופעל כברירת מחדל).

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

  • מצערת מצערת לגלאי. אם מסגרת וידאו חדשה הופכת לזמינה בזמן שהגלאי פועל, השמט את המסגרת.
  • אם אתה משתמש בפלט הגלאי כדי לכסות גרפיקה על תמונת הקלט, תחילה קבל את התוצאה מ- ML Kit, ולאחר מכן עיבד את התמונה ואת שכבת העל בשלב אחד. על ידי כך, אתה מעבד למשטח התצוגה רק פעם אחת עבור כל מסגרת קלט.
  • אם אתה משתמש ב- API Camera2, ללכוד תמונות ב ImageFormat.YUV_420_888 פורמט.

    אם אתה משתמש ב- API המצלמה המבוגרת, ללכוד תמונות ב ImageFormat.NV21 פורמט.