How to Use Amazon Rekognition to Detect Objects in an Image from Lambda
You've got an S3 bucket filling up with user-uploaded photos, and someone asks: 'Can we auto-tag these with what's actually in them?' That's exactly the problem Amazon Rekognition's DetectLabels API solves — and wiring it to a Lambda function triggered by S3 uploads is the most common production pattern for this use case.
TL;DR: Amazon Rekognition DetectLabels via Lambda
| Step | What Happens |
|---|---|
| 1. Image uploaded to S3 | S3 event notification triggers Lambda |
| 2. Lambda invokes DetectLabels | Passes S3 bucket + object key to Rekognition |
| 3. Rekognition returns labels | JSON array with label names and confidence scores |
| 4. Lambda stores results | Write tags to DynamoDB, S3 metadata, or downstream system |
How Amazon Rekognition DetectLabels Works
DetectLabels analyzes an image and returns a list of labels — objects, scenes, concepts, and activities detected in the image — each with a confidence score between 0 and 100. You set a minimum confidence threshold and a maximum label count. Rekognition handles the ML inference entirely; you only supply the image source and parameters.
For images already in S3, you pass an S3Object reference directly in the API call. Rekognition reads the image from S3 on your behalf using its own service role — your Lambda does not need to download the image bytes and re-upload them. This is the critical detail most tutorials skip: the image transfer happens between Rekognition and S3 directly, not through your function's memory.
Think of it like handing Rekognition a library card and a shelf location. It fetches the book itself — your Lambda just makes the request and reads the summary.
The S3 bucket and the Lambda function must be in the same AWS region as the Rekognition API endpoint you call. Cross-region S3 references are not supported by the Rekognition S3Object input.
S3 PutObject"] --> B["S3 Event
ObjectCreated"] B --> C["Lambda Function
photo-label-detector"] C --> D["Rekognition
DetectLabels API"] D --> E["S3 Bucket
Image Fetch"] E --> D D --> F["Labels JSON
name + confidence"] F --> C C --> G["DynamoDB
PhotoLabels Table"]
- S3 Upload: A user uploads a photo; S3 fires a
s3:ObjectCreatedevent. - Lambda Trigger: The event invokes the Lambda function with the bucket name and object key.
- DetectLabels Call: Lambda calls Rekognition, passing the S3 reference — no byte transfer through Lambda.
- Label Response: Rekognition returns structured JSON with label names, confidence scores, and parent categories.
- Persistence: Lambda writes the labels to your storage layer (DynamoDB shown here).
Prerequisites and IAM Permissions
Before writing a single line of Lambda code, the IAM execution role attached to your function needs explicit permissions for three things: reading from the source S3 bucket, calling Rekognition's DetectLabels action, and writing to wherever you store results. Missing any one of these produces a silent failure or a cryptic access-denied error that doesn't point to the right layer.
Here's the minimal IAM policy for the Lambda execution role:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3Read",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::your-photo-bucket/*"
},
{
"Sid": "AllowRekognitionDetect",
"Effect": "Allow",
"Action": [
"rekognition:DetectLabels"
],
"Resource": "*"
},
{
"Sid": "AllowDynamoDBWrite",
"Effect": "Allow",
"Action": [
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/PhotoLabels"
}
]
}
Note that rekognition:DetectLabels does not support resource-level restrictions — "Resource": "*" is required for this action, as documented in the Rekognition Service Authorization Reference. Attempting to scope it to a specific ARN will cause the policy to fail validation or silently deny the call depending on how it's applied.
Also attach the AWS managed policy AWSLambdaBasicExecutionRole so the function can write logs to CloudWatch Logs.
Setting Up the S3 Event Trigger
Configure the S3 bucket to notify Lambda on object creation. You can do this via the console or CLI. Using the CLI, first grant S3 permission to invoke your function:
aws lambda add-permission \
--function-name photo-label-detector \
--statement-id s3-invoke-permission \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::your-photo-bucket \
--source-account 123456789012 \
--region us-east-1
Then configure the bucket notification. Create a file named notification.json:
{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:photo-label-detector",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{
"Name": "suffix",
"Value": ".jpg"
}
]
}
}
}
]
}
aws s3api put-bucket-notification-configuration \
--bucket your-photo-bucket \
--notification-configuration file://notification.json \
--region us-east-1
The suffix filter limits triggers to JPEG files. Add additional configurations for .png or .webp if needed. Without a filter, every object creation — including non-image files — will invoke your function.
Lambda Function: Calling DetectLabels
The function extracts the bucket and key from the S3 event, calls DetectLabels with an S3Object reference, and persists the results. The key implementation detail is URL-decoding the object key — S3 event notifications encode special characters (spaces become +, for example), and passing the raw key to Rekognition will produce a InvalidS3ObjectException for any filename with spaces or special characters.
🔽 Click to expand — Full Lambda function (Python 3.12)
import json
import boto3
import urllib.parse
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
rekognition = boto3.client('rekognition', region_name='us-east-1')
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('PhotoLabels')
MIN_CONFIDENCE = 75.0
MAX_LABELS = 20
def lambda_handler(event, context):
for record in event['Records']:
bucket = record['s3']['bucket']['name']
# URL-decode the key — S3 event encodes special characters
key = urllib.parse.unquote_plus(
record['s3']['object']['key']
)
logger.info(f'Processing s3://{bucket}/{key}')
try:
response = rekognition.detect_labels(
Image={
'S3Object': {
'Bucket': bucket,
'Name': key
}
},
MaxLabels=MAX_LABELS,
MinConfidence=MIN_CONFIDENCE
)
except rekognition.exceptions.InvalidS3ObjectException as e:
logger.error(f'Rekognition could not access object: {e}')
raise
except rekognition.exceptions.InvalidImageException as e:
logger.error(f'Image format not supported: {e}')
# Don't retry unsupported formats
return
labels = [
{
'Name': label['Name'],
'Confidence': str(round(label['Confidence'], 2))
}
for label in response['Labels']
]
logger.info(f'Detected {len(labels)} labels for {key}')
table.put_item(
Item={
'ImageKey': key,
'Bucket': bucket,
'Labels': labels
}
)
return {'statusCode': 200, 'body': 'Labels stored.'}
A few deliberate choices in this implementation worth noting:
- DynamoDB Decimal constraint: DynamoDB does not accept Python
floattypes directly. Confidence scores are stored as strings here to avoid aTypeErrorat write time. Alternatively, use thedecimal.Decimaltype. - InvalidImageException handling: Returning without raising prevents Lambda from retrying on a permanently unsupported file format, which would otherwise loop until the event expires from the queue.
- Single-region clients: Both the Rekognition client and the S3 bucket must be in the same region. Hardcoding the region in the client constructor avoids relying on environment variable resolution order.
Deploying the Lambda Function via CLI
Package and deploy the function. Assuming your code is in lambda_function.py and you've already created the IAM role:
zip function.zip lambda_function.py
aws lambda create-function \
--function-name photo-label-detector \
--runtime python3.12 \
--role arn:aws:iam::123456789012:role/PhotoLabelLambdaRole \
--handler lambda_function.lambda_handler \
--zip-file fileb://function.zip \
--timeout 30 \
--memory-size 256 \
--region us-east-1
For updates after the initial deploy:
aws lambda update-function-code \
--function-name photo-label-detector \
--zip-file fileb://function.zip \
--region us-east-1
Testing the DetectLabels Integration Manually
Before relying on the S3 trigger, verify the Rekognition call works in isolation. Upload a test image and call DetectLabels directly from the CLI to confirm permissions and region alignment are correct — this isolates Rekognition issues from Lambda trigger issues.
aws s3 cp ./test-photo.jpg s3://your-photo-bucket/test-photo.jpg \
--region us-east-1
aws rekognition detect-labels \
--image '{"S3Object":{"Bucket":"your-photo-bucket","Name":"test-photo.jpg"}}' \
--max-labels 10 \
--min-confidence 75 \
--region us-east-1
A successful response looks like this (truncated):
{
"Labels": [
{
"Name": "Cat",
"Confidence": 98.72,
"Parents": [
{ "Name": "Animal" },
{ "Name": "Pet" }
]
},
{
"Name": "Furniture",
"Confidence": 87.14,
"Parents": []
}
],
"LabelModelVersion": "3.0"
}
If this CLI call fails with an access denied error, the problem is in the IAM policy — not the Lambda code. Fix the policy before touching the function.
Diagnosing the Most Common Failure: Silent Label Mismatch
Here's a failure pattern that's easy to miss in testing. You upload an image, the Lambda runs without error, DynamoDB gets a record — but the Labels array is empty. The instinct is to blame Rekognition. The actual cause is almost always a MinConfidence threshold set too high for the image content.
A blurry or low-resolution photo might return labels with confidence scores in the 55-65 range. If your threshold is 75, the response contains labels but your filter discards all of them before writing. The function logs 'Detected 0 labels' and moves on. No exception, no retry, no alert.
The fix is to log the raw Rekognition response before filtering, at least during initial deployment:
raw_labels = response['Labels']
logger.info(f'Raw label count before filter: {len(raw_labels)}')
for label in raw_labels:
logger.info(f"{label['Name']}: {label['Confidence']:.2f}")
Once you see the actual confidence distribution across your real image corpus, set the threshold accordingly. A threshold of 75 is reasonable for clear product photos. For user-generated content with variable quality, 55-60 is more practical.
All Labels Returned"] --> B{"Apply MinConfidence
Threshold Filter"} B -->|"All labels below threshold"| C["Filtered List: Empty
No error raised"] B -->|"Labels meet threshold"| D["Filtered Labels
Written to DynamoDB"] C --> E["Log Raw Scores
to Diagnose Threshold"] E --> F["Adjust MinConfidence
Based on Corpus"]
- Raw Labels Returned: Rekognition returns all detected labels above its internal floor — your
MinConfidenceparameter filters this list further. - Threshold Too High: All labels fall below your threshold; the filtered list is empty. No error is raised.
- Threshold Appropriate: Labels above threshold pass through to storage.
- Diagnosis: Log raw label count and scores before filtering to identify threshold misconfiguration.
Wrap-Up and Next Steps for Amazon Rekognition Label Detection
The S3-to-Lambda-to-Rekognition pattern for Amazon Rekognition object detection is straightforward once the IAM permissions are correctly layered and the S3 key URL-decoding is handled. The two most common production issues — empty label arrays from threshold misconfiguration and InvalidS3ObjectException from unescaped keys — are both preventable with the patterns shown above.
From here, consider these extensions:
- Moderate content: Add a
DetectModerationLabelscall in the same function to flag unsafe images before they reach end users. - Structured taxonomy: Use the
Parentsfield in each label to build a hierarchical tag structure (e.g., 'Cat' → 'Animal' → 'Pet') for richer search filtering. - Dead-letter queue: Attach an SQS DLQ to the Lambda function to capture events that fail after all retries, so no upload is silently dropped.
- Cost visibility: Rekognition pricing is per image analyzed. Enable AWS Cost Explorer with service-level granularity to monitor usage as upload volume grows. Pricing and limits vary — always check the official AWS Rekognition pricing page.
Official references: DetectLabels API Reference | Detecting Labels in an Image
Glossary
| Term | Definition |
|---|---|
| DetectLabels | Rekognition API that returns a list of labels identifying objects, scenes, and concepts in an image, each with a confidence score. |
| S3Object | An input structure in Rekognition API calls that references an image by its S3 bucket name and object key, allowing Rekognition to fetch the image directly. |
| MinConfidence | A threshold parameter (0–100) passed to DetectLabels; only labels with confidence at or above this value are returned in the response. |
| Lambda Execution Role | The IAM role assumed by a Lambda function at runtime, defining what AWS APIs and resources the function is permitted to access. |
| S3 Event Notification | A bucket-level configuration that sends a structured JSON event to a target (Lambda, SQS, SNS) when objects are created, deleted, or modified. |
Comments
Post a Comment