تشخیص چهره با ML Kit در اندروید

می توانید از کیت ML برای تشخیص چهره در تصاویر و ویدیو استفاده کنید.

قبل از اینکه شروع کنی

  1. اگر قبلاً این کار را نکرده‌اید، Firebase را به پروژه Android خود اضافه کنید .
  2. وابستگی های کتابخانه های اندروید ML Kit را به فایل 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. اختیاری اما توصیه می شود : برنامه خود را طوری پیکربندی کنید که پس از نصب برنامه از فروشگاه Play، مدل ML را به طور خودکار در دستگاه دانلود کند.

    برای انجام این کار، اعلان زیر را به فایل 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 بتواند چهره‌ها را به‌طور دقیق تشخیص دهد، تصاویر ورودی باید دارای چهره‌هایی باشند که با داده‌های پیکسلی کافی نشان داده شوند. به طور کلی، هر چهره ای که می خواهید در یک تصویر تشخیص دهید باید حداقل 100x100 پیکسل باشد. اگر می‌خواهید خطوط چهره‌ها را تشخیص دهید، کیت ML به ورودی وضوح بالاتری نیاز دارد: هر چهره باید حداقل 200x200 پیکسل باشد.

اگر چهره‌ها را در یک برنامه بلادرنگ شناسایی می‌کنید، ممکن است بخواهید ابعاد کلی تصاویر ورودی را نیز در نظر بگیرید. تصاویر کوچک‌تر را می‌توان سریع‌تر پردازش کرد، بنابراین برای کاهش تأخیر، تصاویر را با وضوح پایین‌تر (با در نظر گرفتن الزامات دقت بالا) ثبت کنید و اطمینان حاصل کنید که صورت سوژه تا حد امکان تصویر را اشغال می‌کند. همچنین به نکاتی برای بهبود عملکرد در زمان واقعی مراجعه کنید.

فوکوس ضعیف تصویر می تواند به دقت آسیب برساند. اگر نتایج قابل قبولی دریافت نکردید، از کاربر بخواهید که تصویر را دوباره بگیرد.

جهت گیری چهره نسبت به دوربین نیز می تواند بر ویژگی های صورت که کیت ML تشخیص می دهد تأثیر بگذارد. به مفاهیم تشخیص چهره مراجعه کنید.

1. آشکارساز چهره را پیکربندی کنید

قبل از اعمال تشخیص چهره بر روی یک تصویر، اگر می‌خواهید هر یک از تنظیمات پیش‌فرض آشکارساز چهره را تغییر دهید، آن تنظیمات را با یک شی FirebaseVisionFaceDetectorOptions مشخص کنید. می توانید تنظیمات زیر را تغییر دهید:

تنظیمات
حالت عملکرد FAST (پیش فرض) | ACCURATE

هنگام تشخیص چهره، سرعت یا دقت را ترجیح دهید.

شناسایی نقاط دیدنی NO_LANDMARKS (پیش‌فرض) | ALL_LANDMARKS

آیا تلاش برای شناسایی "نقاط برجسته" صورت: چشم ها، گوش ها، بینی، گونه ها، دهان، و غیره.

تشخیص خطوط NO_CONTOURS (پیش‌فرض) | ALL_CONTOURS

آیا برای تشخیص خطوط خطوط صورت. خطوط تنها برای برجسته ترین چهره در یک تصویر شناسایی می شوند.

طبقه بندی چهره ها NO_CLASSIFICATIONS (پیش‌فرض) | ALL_CLASSIFICATIONS

اینکه آیا باید چهره ها را به دسته هایی مانند "خندان" و "چشمان باز" طبقه بندی کرد یا خیر.

حداقل اندازه صورت float (پیش‌فرض: 0.1f )

حداقل اندازه، نسبت به تصویر، برای تشخیص چهره ها.

فعال کردن ردیابی چهره false (پیش فرض) | true

اینکه آیا به چهره ها یک شناسه اختصاص داده شود یا خیر، که می تواند برای ردیابی چهره ها در تصاویر استفاده شود.

توجه داشته باشید که وقتی تشخیص کانتور فعال است، فقط یک چهره شناسایی می‌شود، بنابراین ردیابی چهره نتایج مفیدی ایجاد نمی‌کند. به همین دلیل و برای بهبود سرعت تشخیص، هم تشخیص کانتور و هم ردیابی چهره را فعال نکنید.

مثلا:

Java

// 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();

Kotlin+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 را به متد detectInImage FirebaseVisionFaceDetector ارسال کنید.

برای تشخیص چهره، باید از تصویری با ابعاد حداقل 480x360 پیکسل استفاده کنید. اگر چهره‌ها را در زمان واقعی تشخیص می‌دهید، گرفتن فریم‌ها با این حداقل وضوح می‌تواند به کاهش تأخیر کمک کند.

  1. یک شی FirebaseVisionImage از تصویر خود ایجاد کنید.

    • برای ایجاد یک شی FirebaseVisionImage از یک شی media.Image ، مانند هنگام گرفتن تصویر از دوربین دستگاه، شی media.Image و چرخش تصویر را به FirebaseVisionImage.fromMediaImage() منتقل کنید.

      اگر از کتابخانه CameraX ، کلاس‌های OnImageCapturedListener و ImageAnalysis.Analyzer استفاده می‌کنید، مقدار چرخش را برای شما محاسبه می‌کنند، بنابراین فقط باید قبل از فراخوانی FirebaseVisionImage.fromMediaImage() چرخش را به یکی از ثابت‌های ROTATION_ ML Kit تبدیل کنید:

      Java

      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
              // ...
          }
      }
      

      Kotlin+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
                  // ...
              }
          }
      }
      

      اگر از کتابخانه دوربینی که چرخش تصویر را به شما می دهد استفاده نمی کنید، می توانید آن را از روی چرخش دستگاه و جهت سنسور دوربین در دستگاه محاسبه کنید:

      Java

      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;
      }

      Kotlin+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() ارسال کنید:

      Java

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

      Kotlin+KTX

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • برای ایجاد یک شی FirebaseVisionImage از URI فایل، زمینه برنامه و فایل URI را به FirebaseVisionImage.fromFilePath() ارسال کنید. این زمانی مفید است که از یک هدف ACTION_GET_CONTENT استفاده می کنید تا از کاربر بخواهید تصویری را از برنامه گالری خود انتخاب کند.

      Java

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

      Kotlin+KTX

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • برای ایجاد یک شی FirebaseVisionImage از یک ByteBuffer یا یک آرایه بایت، ابتدا چرخش تصویر را همانطور که در بالا برای ورودی media.Image توضیح داده شد محاسبه کنید.

      سپس، یک شی FirebaseVisionImageMetadata ایجاد کنید که شامل ارتفاع، عرض، فرمت کدگذاری رنگ و چرخش تصویر باشد:

      Java

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

      Kotlin+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 از بافر یا آرایه و شیء فراداده استفاده کنید:

      Java

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

      Kotlin+KTX

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • برای ایجاد یک شی FirebaseVisionImage از یک شی Bitmap :

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      تصویر نمایش داده شده توسط شی Bitmap باید عمودی باشد، بدون نیاز به چرخش اضافی.
  2. نمونه ای از FirebaseVisionFaceDetector را دریافت کنید:

    Java

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

    Kotlin+KTX

    val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)
  3. در نهایت تصویر را به متد detectInImage منتقل کنید:

    Java

    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
                                    // ...
                                }
                            });

    Kotlin+KTX

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

3. اطلاعاتی در مورد چهره های شناسایی شده دریافت کنید

اگر عملیات تشخیص چهره با موفقیت انجام شود، لیستی از اشیاء FirebaseVisionFace به شنونده موفقیت آمیز ارسال می شود. هر شی FirebaseVisionFace نمایانگر چهره ای است که در تصویر شناسایی شده است. برای هر چهره، می‌توانید مختصات مرزی آن را در تصویر ورودی و همچنین اطلاعات دیگری را که آشکارساز چهره پیکربندی کرده‌اید، دریافت کنید. مثلا:

Java

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();
    }
}

Kotlin+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 دریافت کنید، سپس تصویر را رندر کنید و در یک مرحله همپوشانی کنید. با انجام این کار، برای هر فریم ورودی فقط یک بار به سطح نمایشگر رندر می دهید.
  • اگر از Camera2 API استفاده می کنید، تصاویر را با فرمت ImageFormat.YUV_420_888 بگیرید.

    اگر از دوربین قدیمی‌تر API استفاده می‌کنید، تصاویر را با فرمت ImageFormat.NV21 بگیرید.