التعرّف على الوجوه من خلال حزمة تعلّم الآلة على Android

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

يمكنك استخدام حزمة تعلّم الآلة لرصد الوجوه في الصور والفيديوهات.

هناك طريقتان لدمج ميزة التعرّف على الوجه: نموذج مجمّع يكون جزءًا من تطبيقك ونموذجًا غير مجمّع يعتمد على "خدمات Google Play". النموذجان متطابقان. إذا اخترت الطراز غير المجمّع، سيكون تطبيقك أصغر.

الميزةغير مجمعةمجمعة
التنفيذيتم تنزيل النموذج بشكل ديناميكي من خلال خدمات Google Play.ويتم ربط النموذج بتطبيقك بشكل ثابت في وقت الإنشاء.
حجم التطبيقزيادة حجم الصورة عن 800 كيلوبايت تقريبًازيادة حجم الملف عن 6.9 ميغابايت تقريبًا
وقت الإعدادوقد نضطر إلى الانتظار حتى يتم تنزيل النموذج قبل الاستخدام الأول.يتوفّر الطراز على الفور.

قبل البدء

  1. في ملف build.gradle على مستوى المشروع، تأكَّد من تضمين مستودع Google Mavn في كل من قسمَي buildscript وallprojects.

  2. أضِف المهام التابعة لمكتبات ML Kit لنظام التشغيل Android إلى ملف الدليل على مستوى التطبيق، والذي يكون عادةً app/build.gradle. اختَر إحدى التبعيات التالية وفقًا لاحتياجاتك:

    لوضع النموذج في تطبيقك:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:face-detection:16.1.5'
    }
    

    لاستخدام النموذج في "خدمات Google Play"، يُرجى اتّباع الخطوات التالية:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-face-detection:17.1.0'
    }
    
  3. إذا اخترت استخدام النموذج في "خدمات Google Play"، يمكنك ضبط تطبيقك لتنزيل النموذج تلقائيًا على الجهاز بعد تثبيت تطبيقك من "متجر Play". ولإجراء ذلك، أضِف البيان التالي إلى ملف التطبيق AndroidManifest.xml الخاص بتطبيقك:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="face" >
          <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    

    يمكنك أيضًا التحقّق صراحةً من مدى توفّر النموذج وطلب التنزيل من خلال خدمات Google Play UnitInstallClient API.

    في حال عدم تفعيل عمليات تنزيل نموذج وقت التثبيت أو طلب تنزيل محتوى فاضح، يتم تنزيل النموذج في المرة الأولى التي تشغّل فيها أداة الرصد. لا ينتج عن الطلبات التي تجريها قبل اكتمال التنزيل أي نتائج.

إرشادات الصور المدخلة

للتعرّف على الوجه، يجب استخدام صورة بأبعاد 480×360 بكسل على الأقل. لكي تتمكّن حزمة تعلّم الآلة من التعرّف على الوجوه بدقة، يجب أن تحتوي صور الإدخال على وجوه تمثّلها بيانات بكسل كافية. بوجه عام، يجب ألا يقل حجم كل وجه تريد رصده في الصورة عن 100×100 بكسل. وإذا أردت التعرّف على أشكال الوجوه، يتطلب تنسيق ML Kit إدخال درجة دقة أعلى: يجب أن يكون حجم كل وجه 200x200 بكسل على الأقل.

إذا اكتشفت وجوهًا في التطبيق في الوقت الفعلي، ننصحك أيضًا بمراعاة الأبعاد الإجمالية للصور التي يتم إدخالها. ويمكن معالجة الصور الأصغر حجمًا بشكل أسرع، لذا لتقليل وقت الاستجابة، التقِط صورًا بدقة أقل، ولكن احرِص على مراعاة متطلبات الدقة المذكورة أعلاه وتأكّد من أنّ الصورة التي تشغلها الصورة تشغل مساحة كبيرة من الصورة. اطّلِع أيضًا على نصائح لتحسين الأداء في الوقت الفعلي.

يمكن أن يؤثر تركيز الصورة الضعيف أيضًا على الدقة. إذا لم تحصل على نتائج مقبولة، اطلب من المستخدم التقاط الصورة.

يمكن أن يؤثر اتجاه الوجه بالنسبة إلى الكاميرا أيضًا في الميزات التي يكتشفها ML Kit في الوجه. راجِع مفاهيم اكتشاف الوجه.

1- إعداد أداة رصد الوجه

قبل تطبيق ميزة "التعرّف على الوجه" على صورة، إذا كنت تريد تغيير أيٍّ من الإعدادات التلقائية لأداة رصد الوجه، حدِّد هذه الإعدادات باستخدام عنصر FaceDetectorOptions. يمكنك تغيير الإعدادات التالية:

الإعدادات
setPerformanceMode PERFORMANCE_MODE_FAST (تلقائي) | PERFORMANCE_MODE_ACCURATE

تفضيل السرعة أو الدقة عند التعرّف على الوجوه

setLandmarkMode LANDMARK_MODE_NONE (تلقائي) | LANDMARK_MODE_ALL

لتحديد ما إذا كنت تريد محاولة التعرّف على الوجه أو علامات "الوجه" أو "الوجه" أو "الآذان أو الأنف أو الخدّان أو الفم أو غير ذلك".

setContourMode CONTOUR_MODE_NONE (تلقائي) | CONTOUR_MODE_ALL

لتحديد ما إذا كانت ملامح الوجه مفعّلة أم لا. يتم التعرّف على الاكتشادات للوجه الأكثر بروزًا في الصورة.

setClassificationMode CLASSIFICATION_MODE_NONE (تلقائي) | CLASSIFICATION_MODE_ALL

لتحديد ما إذا كنت تريد تصنيف الوجوه إلى فئات مثل &&;;;;; فسيل&م فكاهر؛ و&&;عينات مفتوحة&quot:

setMinFaceSize float (القيمة التلقائية: 0.1f)

لضبط أصغر حجم وجه مطلوب، ويتم التعبير عنه كنسبة عرض الرأس إلى عرض الصورة.

enableTracking false (تلقائي) | true

لتحديد ما إذا كنت تريد تخصيص وجه للمعرّف أم لا، والذي يمكن استخدامه لتتبّع الوجوه في الصور

يُرجى العِلم أنه عند تفعيل ميزة "التعرّف على الحدود"، يتم رصد وجه واحد فقط، لذا لا ينتج عن ميزة "التعرّف على الوجه" نتائج مفيدة. لهذا السبب، ولتحسين سرعة الرصد، لا تفعِّل ميزتَي "الرصد" و"تتبّع الوجه".

مثلاً:

Kotlin

// High-accuracy landmark detection and face classification
val highAccuracyOpts = FaceDetectorOptions.Builder()
        .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
        .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
        .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
        .build()

// Real-time contour detection
val realTimeOpts = FaceDetectorOptions.Builder()
        .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
        .build()

Java

// High-accuracy landmark detection and face classification
FaceDetectorOptions highAccuracyOpts =
        new FaceDetectorOptions.Builder()
                .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
                .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
                .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
                .build();

// Real-time contour detection
FaceDetectorOptions realTimeOpts =
        new FaceDetectorOptions.Builder()
                .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
                .build();

2. إعداد صورة الإدخال

لاكتشاف الوجوه في الصورة، يمكنك إنشاء عنصر InputImage إما من Bitmap أو media.Image أو ByteBuffer أو مصفوفة بايت أو ملف على الجهاز. بعد ذلك، يمكنك تمرير الكائن InputImage إلى طريقة process FaceDetector's.

بالنسبة إلى ميزة التعرّف على الوجوه، يجب استخدام صورة لا يقل حجمها عن 480x360 بكسل. إذا كنت ترصد الوجوه في الوقت الفعلي، يمكن أن يساعدك التقاط الإطارات على الحد الأدنى لدرجة الدقة في تقليل وقت الاستجابة.

يمكنك إنشاء كائن InputImage من مصادر مختلفة، وسيتم توضيح كل منها في ما يلي.

باستخدام media.Image

لإنشاء عنصر InputImage من كائن media.Image، مثلاً عند التقاط صورة من كاميرا الجهاز، مرِّر الكائن media.Image وتدوير الصورة إلى InputImage.fromMediaImage().

إذا كنت تستخدم مكتبة XX، تحسب الفئتان OnImageCapturedListener وImageAnalysis.Analyzer قيمة التدوير نيابةً عنك.

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

إذا لم تكن تستخدم مكتبة كاميرا تمنحك درجة تدوير الصورة، يمكنك احتسابها من درجة تدوير الجهاز واتجاه جهاز استشعار الكاميرا في الجهاز:

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * 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, isFrontFacing: Boolean): 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)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * 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, boolean isFrontFacing)
        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);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

بعد ذلك، اضبط العنصر media.Image وقيمة درجة التدوير على InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

استخدام معرف موارد منتظم (URI) للملف

لإنشاء عنصر InputImage من معرّف موارد منتظم (URI)، يجب تمرير سياق التطبيق ومعرّف الموارد المنتظم (URI) إلى InputImage.fromFilePath(). ويكون هذا الإجراء مفيدًا عندما تستخدم نيّة ACTION_GET_CONTENT لحثّ المستخدم على اختيار صورة من تطبيق معرض الصور.

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

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

باستخدام ByteBuffer أو ByteArray

لإنشاء عنصر InputImage من ByteBuffer أو ByteArray، يجب أولاً حساب درجة عرض الصور بالتناوب كما هو موضّح سابقًا في إدخال media.Image. بعد ذلك، يمكنك إنشاء العنصر InputImage باستخدام المخزن المؤقت أو المصفوفة، مع طول الصورة وعرضها وتنسيق ترميز الألوان ودرجة الدوران:

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

باستخدام Bitmap

لإنشاء كائن InputImage من كائن Bitmap، عليك اتخاذ الإجراء التالي:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

ويتم تمثيل الصورة من خلال عنصر Bitmap جنبًا إلى جنب مع درجات التدوير.

3- الحصول على مثال لتطبيق FaceDetector

Kotlin

val detector = FaceDetection.getClient(options)
// Or, to use the default option:
// val detector = FaceDetection.getClient();

Java

FaceDetector detector = FaceDetection.getClient(options);
// Or use the default options:
// FaceDetector detector = FaceDetection.getClient();

4. معالجة الصورة

تمرير الصورة إلى الطريقة process:

Kotlin

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

Java

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

5. الحصول على معلومات عن الوجوه التي تم رصدها

في حال نجاح عملية التعرّف على الوجوه، يتم تمرير قائمة Face بالعناصر إلى مستمع الاستماع. ويمثّل كل كائن Face وجهًا تم اكتشافه في الصورة. بالنسبة إلى كل وجه، يمكنك الحصول على إحداثيات الحدود في صورة الإدخال، بالإضافة إلى أي معلومات أخرى تم ضبط أداة رصد الوجه للعثور عليها. مثلاً:

Kotlin

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(FaceLandmark.LEFT_EAR)
    leftEar?.let {
        val leftEarPos = leftEar.position
    }

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

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

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

Java

for (Face 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):
    FaceLandmark leftEar = face.getLandmark(FaceLandmark.LEFT_EAR);
    if (leftEar != null) {
        PointF leftEarPos = leftEar.getPosition();
    }

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

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

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

مثال على ملامح الوجه

عندما تكون ميزة "التعرّف على الوجه" مفعّلة، ستتلقّى قائمة بالنقاط لكل ميزة وجه تم رصدها. تمثّل هذه النقاط شكل الميزة. يمكنك الاطّلاع على مفاهيم اكتشاف الوجه لمعرفة تفاصيل عن كيفية تمثيل التفاصيل.

توضّح الصورة التالية كيفية ربط هذه النقاط بوجه، انقر على الصورة لتكبيرها:

مثال على عملية التعرّف على الوجه

التعرّف على الوجوه في الوقت الفعلي

إذا أردت استخدام ميزة "التعرّف على الوجوه" في تطبيق في الوقت الفعلي، اتّبِع هذه الإرشادات لتحقيق أفضل عدد من اللقطات:

  • يمكنك ضبط أداة رصد الوجوه لاستخدام ميزتَي "التعرّف على الوجه" أو "التصنيف حسب الوجوه" و"التعرّف على المَعالم" ولكن ليس كليهما:

    الاكتشاف المحيطي

  • تفعيل وضع FAST (مفعّل تلقائيًا)

  • ننصحك بالتقاط الصور بدقة أقل. ومع ذلك، ضَع في اعتبارك أيضًا متطلّبات أبعاد الصورة في واجهة برمجة التطبيقات هذه.

  • إذا كنت تستخدم Camera أو واجهة برمجة التطبيقات camera2، يمكنك تقييد الاتصالات إلى أداة الرصد. وفي حال توفّر إطار فيديو جديد أثناء تشغيل أداة الرصد، أفلِت الإطار. يمكنك الاطّلاع على السمة VisionProcessorBase في نموذج البدء السريع للحصول على مثال.
  • إذا كنت تستخدم واجهة برمجة تطبيقات CameraX، تأكد من ضبط استراتيجية الضغط على القيمة التلقائية ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. وهذا يضمن تسليم صورة واحدة فقط للتحليل في كل مرة. وإذا تم إنشاء المزيد من الصور عندما تكون أداة التحليل مشغولة، سيتم إدراجها تلقائيًا ولن يتم وضعها في قائمة الانتظار للتسليم. بعد أن يتم إغلاق الصورة التي يتم تحليلها من خلال استدعاء ImageProxy.close()، سيتم تسليم الصورة التالية التالية.
  • إذا كنت تستخدم ناتج أداة الكشف لتراكب الرسومات على الصورة المُدخلة، عليك أولاً الحصول على النتيجة من حزمة تعلّم الآلة، ثم عرض الصورة والتراكب على خطوة واحدة. ويتم العرض على مساحة العرض مرة واحدة فقط لكل إطار إدخال. يمكنك الاطّلاع على السمتَين CameraSourcePreview و GraphicOverlay في نموذج التطبيق السريع للبدء كمثال.
  • إذا كنت تستخدم واجهة برمجة التطبيقات للكاميرا 2، التقِط صورًا بتنسيق ImageFormat.YUV_420_888. إذا كنت تستخدم واجهة برمجة تطبيقات الكاميرا القديمة، التقِط صورًا بتنسيق ImageFormat.NV21.