{ "cells": [ { "cell_type": "markdown", "id": "0872dfc5", "metadata": {}, "source": [ "# Best Practices to capture faces" ] }, { "cell_type": "markdown", "id": "2496dd5b", "metadata": {}, "source": [ "The models used for face comparison operations are designed to work for a wide variety of poses, facial expressions, age ranges, rotations, lighting conditions, and sizes. We recommend that you use the following guidelines when choosing reference photos for CompareFaces or for adding faces to a collection using IndexFaces.\n", "\n", "- Use an image with a face that is within the recommended range of angles. The pitch should be less than 30 degrees face down and less than 45 degrees face up. The yaw should be less than 45 degrees in either direction. There is no restriction on the roll.\n", "\n", "- Use an image of a face with both eyes open and visible.\n", "\n", "- When creating a collection using IndexFaces, use multiple face images of an individual with different pitches and yaws (within the recommended range of angles). We recommend that at least five images of the person are indexed—straight on, face turned left with a yaw of 45 degrees or less, face turned right with a yaw of 45 degrees or less, face tilted down with a pitch of 30 degrees or less, and face tilted up with a pitch of 45 degrees or less. If you want to track that these face instances belong to the same individual, consider using the external image ID attribute if there is only one face in the image being indexed. For example, five images of John Doe can be tracked in the collection with external image IDs as John_Doe_1.jpg, … John_Doe_5.jpg.\n", "\n", "- Use an image of a face that is not obscured or tightly cropped. The image should contain the full head and shoulders of the person. It should not be cropped to the face bounding box.\n", "\n", "- Avoid items that block the face, such as sunglasses and masks.\n", "\n", "- Ensure face size is greater than 50x50 pixels\n", "\n", "- Use color images.\n", "\n", "- Use images with flat lighting on the face, as opposed to varied lighting such as shadows.\n", "\n", "- Use images that have sufficient contrast with the background. A high-contrast monochrome background works well.\n", "\n", "- Use images of faces with neutral facial expressions with mouth closed and little to no smile for applications that require high precision.\n", "\n", "- Use images that are bright and sharp. Avoid using images that may be blurry due to subject and camera motion as much as possible. DetectFaces can be used to determine the brightness and sharpness of a face." ] }, { "cell_type": "code", "execution_count": null, "id": "ee9e5e4a", "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import json\n", "import io\n", "import cv2\n", "\n", "import numpy as np\n", "from IPython.display import Image as IImage\n", "from skimage.exposure import is_low_contrast\n", "\n", "from PIL import Image, ImageDraw, ExifTags, ImageColor\n", "from matplotlib.pyplot import imshow" ] }, { "cell_type": "code", "execution_count": null, "id": "e334f667", "metadata": {}, "outputs": [], "source": [ "client=boto3.client('rekognition')" ] }, { "cell_type": "code", "execution_count": null, "id": "0d681ed0", "metadata": {}, "outputs": [], "source": [ "def image_check (photo):\n", " \n", " with open(photo, 'rb') as image:\n", " response = client.detect_faces(Image={'Bytes': image.read()},Attributes=['ALL'])\n", " \n", " \n", " #perform image check\n", " image = Image.open(photo)\n", " imgWidth, imgHeight = image.size \n", " faceDetail = response['FaceDetails'][0]\n", " pitch = faceDetail['Pose']['Pitch']\n", " yaw = faceDetail['Pose']['Yaw']\n", " sunglasses = faceDetail['Sunglasses']['Value']\n", " box = faceDetail['BoundingBox']\n", " width = imgWidth * box['Width']\n", " height = imgHeight * box['Height']\n", " eyesOpen = faceDetail['EyesOpen']['Value']\n", " emotion = faceDetail['Emotions'][0]['Type']\n", " emotionConfidence = faceDetail['Emotions'][0]['Confidence']\n", " mouthOpen = faceDetail['MouthOpen']['Value']\n", " image_results = \"\"\n", " \n", " if (width < 50) or (height < 50):\n", " image_results += f'Face dimensions should be > 50x50 pixels. They are {width:.1f} x {height:.1f}\\n'\n", " \n", " if (abs(yaw) > 45):\n", " image_results += f'Yaw (side face rotation) should be less than 45 degrees. It is {abs(yaw)} \\n'\n", " \n", " if ((pitch > 45)):\n", " image_results += f'Pitch (looking up) should be less than 45 degrees. It is {pitch} \\n'\n", " \n", " if ((pitch < -30)):\n", " image_results += f'Pitch (looking down) should be less than 30 degrees. It is {pitch} \\n'\n", " \n", " if not eyesOpen:\n", " image_results += f'Eyes are not open \\n'\n", " \n", " if sunglasses:\n", " image_results += f'remove sunglasses \\n'\n", " \n", " if (mouthOpen):\n", " image_results += f'Mouth should be closed \\n'\n", "\n", " # if no entry in image results, then image is compliant\n", " if (not image_results):\n", " image_results = \"Image is compliant\"\n", " \n", " \n", " return image_results\n", " " ] }, { "cell_type": "code", "execution_count": null, "id": "60c72423", "metadata": {}, "outputs": [], "source": [ "def low_contrast_check (photo):\n", " image = cv2.imread(photo)\n", " gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n", " return is_low_contrast(gray, fraction_threshold=0.35)" ] }, { "cell_type": "code", "execution_count": null, "id": "b6be946f", "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "def show_faces (photo):\n", " \n", "\n", " # Load image\n", " print(f'photo = {photo}')\n", " \n", " with open(photo, 'rb') as image:\n", " \n", "\n", " #Call DetectFaces \n", " response = client.detect_faces(Image={'Bytes': image.read()},Attributes=['ALL'])\n", "\n", " image = Image.open(photo)\n", " imgWidth, imgHeight = image.size \n", " draw = ImageDraw.Draw(image) \n", "\n", "\n", " # calculate and display bounding boxes for each detected face \n", " print('Detected faces for ' + photo) \n", " for faceDetail in response['FaceDetails']:\n", " print('The detected face is between ' + str(faceDetail['AgeRange']['Low']) \n", " + ' and ' + str(faceDetail['AgeRange']['High']) + ' years old')\n", "\n", " box = faceDetail['BoundingBox']\n", " left = imgWidth * box['Left']\n", " top = imgHeight * box['Top']\n", " width = imgWidth * box['Width']\n", " height = imgHeight * box['Height']\n", "\n", "\n", " #print('Left: ' + '{0:.0f}'.format(left))\n", " #print('Top: ' + '{0:.0f}'.format(top))\n", " #print('Face Width: ' + \"{0:.0f}\".format(width))\n", " #print('Face Height: ' + \"{0:.0f}\".format(height))\n", "\n", " points = (\n", " (left,top),\n", " (left + width, top),\n", " (left + width, top + height),\n", " (left , top + height),\n", " (left, top)\n", "\n", " )\n", " draw.line(points, fill='#00d400', width=10)\n", "\n", " # Alternatively can draw rectangle. However you can't set line width.\n", " #draw.rectangle([left,top, left + width, top + height], outline='#00d400') \n", "\n", " imshow(np.asarray(image))\n", "\n", " return len(response['FaceDetails'])" ] }, { "cell_type": "code", "execution_count": null, "id": "dcd6d6f3", "metadata": {}, "outputs": [], "source": [ "path = \"./media/\"" ] }, { "cell_type": "markdown", "id": "a22f87e4", "metadata": {}, "source": [ "## Examples of acceptable face profile" ] }, { "cell_type": "markdown", "id": "0cf80464", "metadata": {}, "source": [ "### Front view with head and shoulder - Capture 5 images at different face angles" ] }, { "cell_type": "code", "execution_count": null, "id": "ace9200e", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo1.jpeg'\n", "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "a698d821", "metadata": {}, "outputs": [], "source": [ "show_faces (photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "34f53120", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')" ] }, { "cell_type": "markdown", "id": "1f3b19c4", "metadata": {}, "source": [ "### Face turned right with head and shoulder" ] }, { "cell_type": "code", "execution_count": null, "id": "59f02ca4", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo3.jpeg'\n", "IImage(filename=photo) \n" ] }, { "cell_type": "code", "execution_count": null, "id": "ad2fbd79", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')" ] }, { "cell_type": "markdown", "id": "6a719513", "metadata": {}, "source": [ "### Face turned left with head and shoulder" ] }, { "cell_type": "code", "execution_count": null, "id": "365ac1a9", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo2.jpeg'\n", "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "be2f9b47", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')" ] }, { "cell_type": "markdown", "id": "19144a17", "metadata": {}, "source": [ "### Face turned up slightly with head and shoulder" ] }, { "cell_type": "code", "execution_count": null, "id": "ea1144cd", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo4.jpeg'\n", "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "38bf553e", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')" ] }, { "cell_type": "markdown", "id": "a8683095", "metadata": {}, "source": [ "### Face turned down slightly with head and shoulder" ] }, { "cell_type": "code", "execution_count": null, "id": "47bfdf80", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo5.jpeg'\n", "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "03f5db82", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')" ] }, { "cell_type": "markdown", "id": "0d03a90c", "metadata": {}, "source": [ "## Following are examples of unacceptable face profile" ] }, { "cell_type": "markdown", "id": "b1af9b3d", "metadata": {}, "source": [ "### Examples with high yaw and pitch" ] }, { "cell_type": "code", "execution_count": null, "id": "aaf1d348", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo6.jpeg'" ] }, { "cell_type": "code", "execution_count": null, "id": "29cbfacc", "metadata": { "scrolled": true }, "outputs": [], "source": [ "IImage(filename=photo) " ] }, { "cell_type": "code", "execution_count": null, "id": "57b44991", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "code", "execution_count": null, "id": "aae89e90", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo7.jpeg'" ] }, { "cell_type": "code", "execution_count": null, "id": "f61bba04", "metadata": {}, "outputs": [], "source": [ "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "fd755657", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "code", "execution_count": null, "id": "2924e09b", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo8.jpeg'\n", "IImage(filename=photo) " ] }, { "cell_type": "code", "execution_count": null, "id": "461439a2", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6ec162c0", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo9.jpeg'\n", "IImage(filename=photo) " ] }, { "cell_type": "code", "execution_count": null, "id": "4c6cda65", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "markdown", "id": "9bbc80b8", "metadata": {}, "source": [ "### Example of less than 50x50 pixels covering the face " ] }, { "cell_type": "code", "execution_count": null, "id": "e37b925d", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo13.jpeg'\n", "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "aff04f71", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')" ] }, { "cell_type": "markdown", "id": "2816abc6", "metadata": {}, "source": [ "### Example of occlusions - Avoid sunglasses, masks, headbands, hat" ] }, { "cell_type": "code", "execution_count": null, "id": "6c86331c", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo10.jpeg'\n", "IImage(filename=photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "eda856d1", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b1fe6ece", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo11.jpeg'\n", "IImage(filename=photo) " ] }, { "cell_type": "code", "execution_count": null, "id": "62b4f56b", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "markdown", "id": "c78a7816", "metadata": {}, "source": [ "### Example of low contrast image" ] }, { "cell_type": "code", "execution_count": null, "id": "d1cdc32d", "metadata": {}, "outputs": [], "source": [ "photo = path + 'photo12.jpeg'\n", "IImage(photo)" ] }, { "cell_type": "code", "execution_count": null, "id": "78a9fe25", "metadata": {}, "outputs": [], "source": [ "print(image_check(photo))\n", "result = 'Good contrast' if not low_contrast_check (photo) else 'Low contrast'\n", "print(f'Image has {result}')\n" ] }, { "cell_type": "markdown", "id": "2b4df54a", "metadata": {}, "source": [ "## Resources" ] }, { "cell_type": "markdown", "id": "164c4e13", "metadata": {}, "source": [ "- https://docs.aws.amazon.com/rekognition/latest/dg/recommendations-facial-input-images.html\n", "- https://docs.aws.amazon.com/rekognition/latest/dg/best-practices.html\n", " " ] }, { "cell_type": "code", "execution_count": null, "id": "b17d3f0c", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.13" } }, "nbformat": 4, "nbformat_minor": 5 }