AWS Machine Learning Blog

Build Your Own Face Recognition Service Using Amazon Rekognition

March 2025: This post was reviewed and updated for accuracy.

Amazon Rekognition is a service that simplifies adding image analysis to your applications. It uses the same proven, highly scalable deep learning technology developed by Amazon’s computer vision scientists to analyze billions of images daily for Amazon Prime Photos. Its facial recognition feature allows you to find similar faces within large image collections.

In this post, we demonstrate how to create a face recognition service using Amazon Rekognition with Amazon DynamoDB and AWS Lambda. You can build a system to create, update, and search face databases for uses like automated image library identification, access control, and more.

Solution overview

This solution uses the face recognition capabilities of Amazon Rekognition through a serverless architecture. When you upload an image to an Amazon Simple Storage Service (Amazon S3) bucket, it triggers a Lambda function that processes the image using Amazon Rekognition. The solution stores face metadata and image references in DynamoDB tables for efficient searching and indexing.

The workflow operates in two main phases: face indexing and face recognition.

Phase 1: Face indexing

The system creates a collection of known faces by indexing images containing faces you want to recognize. When an image is uploaded to the designated S3 bucket, Lambda processes it using the Amazon Rekognition IndexFaces API, which extracts facial features and stores them as searchable vectors. The face metadata and image reference are then saved in DynamoDB.

The following diagram outlines the workflow for indexing.

The workflow consists of the following steps:

  1. The user uploads an image to an S3 bucket.
  2. The upload triggers a Lambda function.
  3. The Lambda function issues an index face request to Amazon Rekognition.
  4. Amazon Rekognition accesses the object from Amazon S3.
  5. Amazon Rekognition sends the response data to the Lambda function.
  6. HEAD object metadata is sent from Amazon S3 to the Lambda function.
  7. The function populates an index in DynamoDB.

Phase 2: Face recognition

When searching for faces, another Lambda function processes the target image using the Amazon Rekognition SearchFacesByImage API. This compares the target face against the indexed collection and returns matching results based on a configurable similarity threshold. The matches are retrieved from DynamoDB and returned to the client.

The following diagram outlines the workflow for analysis.

The workflow consists of the following steps:

  1. The client sends a SearchFaceByImage request to Amazon Rekognition.
  2. Amazon Rekognition returns face matches to the client.
  3. The user can look up the ImageId from the client in DynamoDB.

Prerequisites

To implement this solution, you should have an AWS account with the appropriate permissions.

Deploy the solution using AWS CloudFormation

If you want to get started quickly, launch the following AWS CloudFormation template:

For the manual walkthrough, make sure that you replace the resource names with your own values. A couple of resources need to be prepared to allow us to index the faces of our existing images.

Create a collection

We start by creating a collection within Amazon Rekognition. A collection is a container for persisting faces detected by the IndexFaces API. You can choose to create one container to store all faces or create multiple containers to store faces in groups.

Your use case will determine the indexing strategy for your collection as follows:

  • Face match – You want to find a match for a face within a collection of faces (as in our current example). Face matching technology applies to a wide range of use cases. For example: adding VIP groups to an approved list, identifying and denying bad actors, or supporting various logging scenarios. In such cases, you will generate a single collection containing numerous faces.
  • Face verification – For identity verification using face recognition (like access control), a separate collection is created for each person. You will store a variety of face samples per person to improve the match rate. This also lets you add samples of different appearances to the recognition model; for instance, someone who has grown a beard.
  • Social tagging – When automatically tagging friends on social networks, use one collection per user.

For more information about use cases and indexing strategies, refer to the Amazon Rekognition Developer Guide.

Amazon Rekognition doesn’t store copies of the analyzed images. Instead, it stores face feature vectors as the mathematical representation of faces within the collection. This is commonly known as a thumbprint or a faceprint. You can manage collection containers through the API. Alternatively, if you have installed and configured the AWS Command Line Interface (AWS CLI), you can use the following command:

aws rekognition create-collection --collection-id family_collection --region eu-west-1

The user or role that executes the commands must have permissions in AWS Identity and Access Management (IAM) to perform those actions. AWS provides a set of managed policies that help you get started quickly. For our example, you need to apply the following minimum managed policies to your user or role:

  • AmazonRekognitionFullAccess
  • AmazonDynamoDBFullAccess
  • AmazonS3FullAccess
  • IAMFullAccess

It is recommended that you follow IAM best practices for production implementations, which are beyond the scope of this post.

Create a DynamoDB table

Next, we create a DynamoDB table. DynamoDB is a fully managed cloud database that supports both document and key-value store models. We demonstrate using DynamoDB as a key-value store to link the Amazon Rekognition FaceId with a person’s full name.

You can use either the AWS Management Console, the API, or the AWS CLI to create the table. For the AWS CLI, use the following command, which is documented in the AWS CLI Command Reference:

aws dynamodb create-table --table-name family_collection \
--attribute-definitions AttributeName=RekognitionId,AttributeType=S \
--key-schema AttributeName=RekognitionId,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
--region eu-west-1

For the IndexFaces operation, you can provide the images as bytes or make them available to Amazon Rekognition inside an S3 bucket. In our example, we upload the images to an S3 bucket.

Again, you can create a bucket either from the console or from the AWS CLI. In the AWS CLI, use the following command, which is documented in the AWS CLI Command Reference:

aws s3 mb s3://bucket-name --region eu-west-1

As shown earlier in the architecture diagram, we have separated the processes of image upload and face detection into two parts. Although all the preparation steps were performed from the AWS CLI, we use a Lambda function to process the images that we uploaded to Amazon S3.

Create an IAM role

We need to create an IAM role granting our function access to S3 objects, the Amazon Rekognition IndexFaces function, and to create DynamoDB entries that map FaceId and a person’s full name.

By now, you will have noticed that we prefer AWS CLI over the use of the console. For detailed instructions for creating service roles using the AWS CLI, refer to Creating a role for a service (AWS CLI). To create the service role for Lambda, we need two JSON files that describe the trust and access policies: trust-policy.json and access-policy.json.

The following code is for trust-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

The following code is for access-policy.json:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::bucket-name/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:aws-region:account-id:table/family_collection"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "rekognition:IndexFaces"
            ],
            "Resource": "*"
        }
    ]
}

For the access policy, make sure that you replace aws-region, account-id, and the actual names of the resources (for example, bucket-name and family_collection) with the names of the resources in your environment.

Now you can use the AWS CLI again to create the service role that the indexing Lambda function will use to retrieve temporary credentials for authentication.

As described in Creating a role for a service (AWS CLI), you first need to create the role that includes the trust policy:

aws iam create-role --role-name LambdaRekognitionRole --assume-role-policy-document file://trust-policy.json

Next, attach the access policy to the role:

aws iam put-role-policy --role-name LambdaRekognitionRole --policy-name LambdaPermissions --policy-document file://access-policy.json

Create a Lambda function

The last step is creating an Amazon S3 triggered Lambda function for uploading images. Complete the following steps:

  1. On the Lambda console, choose Functions in the navigation pane.
  2. Choose Create function.
  3. Enter a name for the Lambda function and choose Python 3.13 for Runtime.
  4. For Role, choose Choose an existing role, and then choose the role you created earlier.
  5. Keep other settings as default and choose Create function.
  6. After you create the function, choose Add trigger.
  7. Choose S3 as the source, then search for the bucket you created.
  8. Configure the values for Event types and Prefix as shown in the following screenshot.

This makes sure your Lambda function is triggered only when new objects that start with a key matching the index/ pattern are created within the bucket.

  1. Choose Add to create the Lambda trigger.
  2. Return to the Lambda function you just created and enter the following Python code into the code source:
    from __future__ import print_function
    
    import boto3
    from decimal import Decimal
    import json
    import urllib
    
    print('Loading function')
    
    dynamodb = boto3.client('dynamodb')
    s3 = boto3.client('s3')
    rekognition = boto3.client('rekognition')
    
    
    # --------------- Helper Functions ------------------
    
    def index_faces(bucket, key):
    
        response = rekognition.index_faces(
            Image={"S3Object":
                {"Bucket": bucket,
                "Name": key}},
                CollectionId="family_collection")
        return response
        
    def update_index(tableName,faceId, fullName):
        response = dynamodb.put_item(
            TableName=tableName,
            Item={
                'RekognitionId': {'S': faceId},
                'FullName': {'S': fullName}
                }
            ) 
        
    # --------------- Main handler ------------------
    
    def lambda_handler(event, context):
    
        # Get the object from the event
        bucket = event['Records'][0]['s3']['bucket']['name']
        key = urllib.unquote_plus(
            event['Records'][0]['s3']['object']['key'].encode('utf8'))
    
        try:
    
            # Calls Amazon Rekognition IndexFaces API to detect faces in S3 object 
            # to index faces into specified collection
            
            response = index_faces(bucket, key)
            
            # Commit faceId and full name object metadata to DynamoDB
            
            if response['ResponseMetadata']['HTTPStatusCode'] == 200:
                faceId = response['FaceRecords'][0]['Face']['FaceId']
    
                ret = s3.head_object(Bucket=bucket,Key=key)
                personFullName = ret['Metadata']['fullname']
    
                update_index('family_collection',faceId,personFullName)
    
            # Print response to console
            print(response)
    
            return response
        except Exception as e:
            print(e)
            print("Error processing object {} from bucket {}. ".format(key, bucket))
            raise e

In a nutshell, the script performs two major activities:

  1. It uses the Amazon Rekognition IndexFaces API to detect the face in the input image and add it to the specified collection.
  2. If successful, it retrieves the full name of the person from the metadata of the object in Amazon S3. Then it stores this as a key-value tuple with the FaceId in the DynamoDB table for later reference.

Indexing

We seed the face collection by uploading our images to Amazon S3. We again use a short Python snippet for this example. This code steps through a list where each entry shows a file path and the person in the image.

Look closely at line 16 in the following code example. Here, we add additional metadata to the objects in Amazon S3. The Lambda function uses this metadata to extract the full name of the person within the image. To suit your needs, you can adjust this process by incorporating extra metadata or importing the metadata definition from a database or file.

import boto3

s3 = boto3.resource('s3')

# Get list of objects for indexing
images=[('image01.jpeg','Albert Einstein'),
      ('image02.jpeg','Albert Einstein'),
      ('image03.jpeg','Albert Einstein'),
      ('image04.jpeg','Niels Bohr'),
      ('image05.jpeg','Niels Bohr'),
      ('image06.jpeg','Niels Bohr')
      ]

# Iterate through list to upload objects to S3   
for image in images:
    file = open(image[0],'rb')
    object = s3.Object('rekognition-pictures','index/'+ image[0])
    ret = object.put(Body=file,
                    Metadata={'FullName':image[1]}
                    )

To maximize the matching rate for individuals, we add several reference images per person to the image database. It also provides additional matching logic to further enhance the results.

Analysis

After the collection is populated, we can query it by passing in other images that contain faces. Using the SearchFacesByImage API, you need to provide at least two parameters: the name of the collection to query and the reference to the image to analyze. You can either provide a reference to the S3 bucket name and object key of the image or provide the image itself as a byte stream.

In the following example, we demonstrate how to submit the image as a byte stream. In response, Amazon Rekognition returns a JSON object containing the FaceId values of the matches. This object includes a confidence score and the coordinates of the face within the image, among other metadata, as documented in the API reference.

import boto3
import io
from PIL import Image
from botocore.exceptions import ClientError

# Please configure the following Constants
REGION = 'eu-west-1'
COLLECTION_ID = 'recognition-xxxx'
TABLE_NAME = 'recognition-xxxx'
IMAGE_PATH = "1.jpg"

class FaceRecognitionService:
def __init__(self):
self.rekognition = boto3.client('rekognition', region_name=REGION)
self.dynamodb = boto3.client('dynamodb', region_name=REGION)
self.results = []

def process_image(self):
"""Process image and return binary"""
try:
with Image.open(IMAGE_PATH) as image:
stream = io.BytesIO()
image.save(stream, format="JPEG")
return stream.getvalue()
except Exception as e:
raise Exception(f"Error processing image: {e}")

def table_exists(self):
"""Check if DynamoDB table exists"""
try:
self.dynamodb.describe_table(TableName=TABLE_NAME)
return True
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
return False
raise

def get_person_details(self, face_id):
"""Get person details from DynamoDB"""
if not self.table_exists():
self.results.append(f"Table {TABLE_NAME} does not exist")
return None

try:
response = self.dynamodb.get_item(
TableName=TABLE_NAME,
Key={'RekognitionId': {'S': face_id}}
)
return response.get('Item')
except ClientError as e:
self.results.append(f"DynamoDB error: {e}")
return None

def search_faces(self, image_binary):
"""Search for faces in the image"""
try:
return self.rekognition.search_faces_by_image(
CollectionId=COLLECTION_ID,
Image={'Bytes': image_binary}
)
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
self.results.append(f"The collection '{COLLECTION_ID}' was not found")
elif e.response['Error']['Code'] == 'InvalidParameterException':
self.results.append("No faces detected in the image")
else:
self.results.append(f"Rekognition error: {e}")
raise

def process_matches(self, matches):
"""Process and store face matches"""
if not matches:
self.results.append("No faces matched in the collection")
return

for match in matches:
face_id = match['Face']['FaceId']
confidence = match['Face']['Confidence']
self.results.append(f"\nFace Match Details:")
self.results.append(f"- Face ID: {face_id}")
self.results.append(f"- Confidence: {confidence:.2f}%")

person = self.get_person_details(face_id)
if person and 'FullName' in person:
self.results.append(f"- Matched Person: {person['FullName']['S']}")
else:
self.results.append("- No person details found")

def run(self):
"""Execute the face recognition process"""
try:
image_binary = self.process_image()
response = self.search_faces(image_binary)
self.process_matches(response['FaceMatches'])

# Print all results at once
print("\n".join(self.results))

except Exception as e:
print(f"Error during execution: {e}")

def main():
service = FaceRecognitionService()
service.run()

if __name__ == "__main__":
main() 

An additional matching process could further enhance this. For example, if we received three matches for Person A and a confidence score of over 90% each, and one match for Person B with a confidence of 85% or below, we can reasonably assume that the person was indeed Person A. Based on your business requirements, you could also return fuzzy matches like this to a human process step for validation.

Enhancements

For the analysis phase, Amazon Rekognition only matches the most visible face in each image. If your image contains multiple people, you first need to use the DetectFaces API to retrieve the individual bounding boxes of the faces within the image. You can then use the retrieved x,y coordinates to cut out the faces from the image and submit them individually to the SearchFacesByImage API.

We extend the face box boundaries by 10% in each direction to simplify cropping. Although ideally the box should be repositioned and reoriented to match head tilts, expanding the crop area serves as an effective workaround. This works reliably because Amazon Rekognition only detects the largest face in an image, and even a 50% larger crop won’t capture a face bigger than the one already identified.

from __future__ import print_function

import boto3
import io
from PIL import Image
from pprint import pprint

rekognition = boto3.client('rekognition', region_name='eu-west-1')
dynamodb = boto3.client('dynamodb', region_name='eu-west-1')

image = Image.open("eg.jpg")
stream = io.BytesIO()
image.save(stream,format="JPEG")
image_binary = stream.getvalue()

response = rekognition.detect_faces(
Image={'Bytes':image_binary} 
)

all_faces=response['FaceDetails']

# Initialize list object
boxes = []

# Get image diameters
image_width = image.size[0]
image_height = image.size[1]

# Crop face from image
for face in all_faces:
box=face['BoundingBox']
x1 = int(box['Left'] * image_width) * 0.9
y1 = int(box['Top'] * image_height) * 0.9
x2 = int(box['Left'] * image_width + box['Width'] * image_width) * 1.10
y2 = int(box['Top'] * image_height + box['Height'] * image_height) * 1.10
image_crop = image.crop((x1,y1,x2,y2))

stream = io.BytesIO()
image_crop.save(stream,format="JPEG")
image_crop_binary = stream.getvalue()

# Submit individually cropped image to Amazon Rekognition
response = rekognition.search_faces_by_image(
CollectionId='recognition-xxxx',
Image={'Bytes':image_crop_binary} 
)

if len(response['FaceMatches']) > 0:
# Return results
print ('Coordinates ', box)
for match in response['FaceMatches']:

face = dynamodb.get_item(
TableName='recognition-xxxx', 
Key={'RekognitionId': {'S': match['Face']['FaceId']}}
)

if 'Item' in face:
person = face['Item']['FullName']['S']
else:
person = 'no match found'

print (match['Face']['FaceId'],match['Face']['Confidence'],person) 

To supplement the manual process previously described, consider creating a Lambda function that includes your face detection code. To improve the process, store the names of detected individuals in a database (such as DynamoDB) instead of simply displaying them on the console. This allows you to detect faces within an extensive collection of images at scale.

Conclusion

In this post, we provided a guide to designing and building your own face recognition service. We shared insights on integrating Amazon Rekognition with other AWS services, including Lambda, Amazon S3, DynamoDB, and IAM. With these services at your disposal, you’re well-equipped to create a personalized solution that can detect, index, and recognize faces across various scenarios—from organizing family photos and managing vast image libraries to implementing simple access control systems.

The possibilities are endless for using face recognition technology. We can’t wait to see what innovative solutions you will create!

To extend your knowledge even further, learn how to find distinct people in a video with Amazon Rekognition.


About the Authors

Christian Petters is a Solutions Architect for Amazon Web Service in Germany. He has a background in the design, implementation, and operation of large-scale web and groupware applications. At AWS, he helps customers assemble the right building blocks to address their business challenges.

Shashank Chinchli is a Solutions Architect at AWS. With over 13 years of industry experience in cloud architecture, systems engineering, and application development, he specializes in helping businesses build efficient and scalable solutions on AWS. Notably, he has earned 13 AWS certifications and a Golden Jacket, reflecting his exceptional expertise. Outside of work, Shashank enjoys staying active with HIIT workouts and exploring his passion for music.

Achintya Veer Singh is a Solutions Architect at AWS based in Bangalore. He works with AWS customers to address their business challenges by designing secure, performant, and scalable solutions using the latest cloud technologies. He is passionate about technology and enjoys building and experimenting with AI/ML and generative AI. Outside of work, he enjoys cooking, reading non-fiction books, and spending time with his family.

Aashi Agrawal is a Solutions Architect at AWS, where she specializes in the analytics domain. She guides customers through the transformative process of migration and modernization. With a blend of visionary architecture and robust security, she crafts resilient systems and seamlessly integrates cutting-edge AI/ML services, including the marvels of generative AI, into their technological tapestry. Outside of work, she loves to explore new things and discover new music.


Audit History

Last reviewed and updated in March 2025 by Shashank Chinchli, Achintya Veer Singh and Aashi Agrawal | Solutions Architect