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

يمكنك استخدام هذه الأدوات لرصد الوجوه في الصور والفيديوهات.

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

التجربة الآن

قبل البدء

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

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

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

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

    لاستخدام النموذج في "خدمات 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>
    

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

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

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

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

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

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

يمكن أن يؤثر اتجاه الوجه بالنسبة إلى الكاميرا أيضًا في الميزات التي ترصدها أدوات تعلُّم الآلة. يمكنك الاطّلاع على مفاهيم التعرّف على الوجوه.

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

لتحديد ما إذا كان سيتم تصنيف الوجوه إلى فئات مثل "الابتسام" أو "العيون المفتوحة".

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.

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

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

استخدام media.Image

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

إذا كنت تستخدم مكتبة CameraX، ستحتسب الفئتان 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- الحصول على مثيل لميزة "التعرّف على الوجه"

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 في نموذج التطبيق السريع للبدء للحصول على مثال.
  • إذا كنت تستخدم واجهة برمجة تطبيقات Camera2، التقِط الصور بتنسيق ImageFormat.YUV_420_888. إذا كنت تستخدم واجهة برمجة تطبيقات الكاميرا القديمة، التقِط الصور بتنسيق ImageFormat.NV21.