Real-Time Face Search with Raspberry Pi, Kinesis Video Streams, and Amazon Rekognition
Stream video from a Raspberry Pi to Kinesis Video Streams and match detected faces against an Amazon Rekognition collection.
This example streams video from a Raspberry Pi USB camera to Kinesis Video Streams. An Amazon Rekognition Video stream processor detects faces and searches for matches in a pre-indexed face collection.
Requirements
Hardware Requirements
- Raspberry Pi 4B with 4GB RAM
- Running Ubuntu 23.10 (installed via Raspberry Pi Imager)
- USB Camera
Software Requirements
- GStreamer
- Amazon Kinesis Video Streams CPP Producer, GStreamer Plugin and JNI
- AWS SAM CLI
- Python 3.11
Building the AWS Resources
AWS SAM Template
AWSTemplateFormatVersion: 2010-09-09Transform: AWS::Serverless-2016-10-31Description: face-detector-using-kinesis-video-streams
Resources: Function: Type: AWS::Serverless::Function Properties: FunctionName: face-detector-function CodeUri: src/ Handler: app.lambda_handler Runtime: python3.11 Architectures: - arm64 Timeout: 3 MemorySize: 128 Role: !GetAtt FunctionIAMRole.Arn Events: KinesisEvent: Type: Kinesis Properties: Stream: !GetAtt KinesisStream.Arn MaximumBatchingWindowInSeconds: 10 MaximumRetryAttempts: 3 StartingPosition: LATEST
FunctionIAMRole: Type: AWS::IAM::Role Properties: RoleName: face-detector-function-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole Policies: - PolicyName: policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - kinesisvideo:GetHLSStreamingSessionURL - kinesisvideo:GetDataEndpoint Resource: !GetAtt KinesisVideoStream.Arn
KinesisVideoStream: Type: AWS::KinesisVideo::Stream Properties: Name: face-detector-kinesis-video-stream DataRetentionInHours: 24
RekognitionCollection: Type: AWS::Rekognition::Collection Properties: CollectionId: FaceCollection
RekognitionStreamProcessor: Type: AWS::Rekognition::StreamProcessor Properties: Name: face-detector-rekognition-stream-processor KinesisVideoStream: Arn: !GetAtt KinesisVideoStream.Arn KinesisDataStream: Arn: !GetAtt KinesisStream.Arn RoleArn: !GetAtt RekognitionStreamProcessorIAMRole.Arn FaceSearchSettings: CollectionId: !Ref RekognitionCollection FaceMatchThreshold: 80 DataSharingPreference: OptIn: false
KinesisStream: Type: AWS::Kinesis::Stream Properties: Name: face-detector-kinesis-stream StreamModeDetails: StreamMode: ON_DEMAND
RekognitionStreamProcessorIAMRole: Type: AWS::IAM::Role Properties: RoleName: face-detector-rekognition-stream-processor-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: rekognition.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonRekognitionServiceRole Policies: - PolicyName: policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - kinesis:PutRecord - kinesis:PutRecords Resource: - !GetAtt KinesisStream.ArnLambda Function
- The Lambda event source mapping delivers each Kinesis Data Streams record as a Base64-encoded string, which the function decodes on line 17.
- When a face search result is present, the Lambda function generates an HLS playback URL for the corresponding time range (lines 54–66).
import base64import jsonimport loggingfrom datetime import datetime, timedelta, timezonefrom functools import cache
import boto3
JST = timezone(timedelta(hours=9))kvs_client = boto3.client('kinesisvideo')logger = logging.getLogger(__name__)logger.setLevel(logging.INFO)
def lambda_handler(event: dict, context: dict) -> dict: for record in event['Records']: base64_data = record['kinesis']['data'] stream_processor_event = json.loads(base64.b64decode(base64_data).decode()) # Refer to https://docs.aws.amazon.com/rekognition/latest/dg/streaming-video-kinesis-output.html for details on the structure.
if not stream_processor_event['FaceSearchResponse']: continue
logger.info(stream_processor_event) url = get_hls_streaming_session_url(stream_processor_event) logger.info(url)
return { 'statusCode': 200, }
@cachedef get_kvs_am_client(api_name: str, stream_arn: str): # Retrieves the data endpoint for the stream. # See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kinesisvideo/client/get_data_endpoint.html endpoint = kvs_client.get_data_endpoint( APIName=api_name.upper(), StreamARN=stream_arn )['DataEndpoint'] return boto3.client('kinesis-video-archived-media', endpoint_url=endpoint)
def get_hls_streaming_session_url(stream_processor_event: dict) -> str: # Generates an HLS streaming URL for the video stream. # See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kinesis-video-archived-media/client/get_hls_streaming_session_url.html
kinesis_video = stream_processor_event['InputInformation']['KinesisVideo'] stream_arn = kinesis_video['StreamArn'] kvs_am_client = get_kvs_am_client('get_hls_streaming_session_url', stream_arn) start_timestamp = datetime.fromtimestamp(kinesis_video['ServerTimestamp'], JST) end_timestamp = datetime.fromtimestamp(kinesis_video['ServerTimestamp'], JST) + timedelta(minutes=1)
return kvs_am_client.get_hls_streaming_session_url( StreamARN=stream_arn, PlaybackMode='ON_DEMAND', HLSFragmentSelector={ 'FragmentSelectorType': 'SERVER_TIMESTAMP', 'TimestampRange': { 'StartTimestamp': start_timestamp, 'EndTimestamp': end_timestamp, }, }, ContainerFormat='FRAGMENTED_MP4', Expires=300, )['HLSStreamingSessionURL']Deploying the Stack
Build and deploy the SAM application:
sam buildsam deployIndexing Faces
To search for known faces in the camera stream, first register reference faces in a Rekognition collection with the IndexFaces API.
Replace the following with the actual values:
<YOUR_BUCKET><YOUR_OBJECT><PERSON_ID>
aws rekognition index-faces \ --image '{"S3Object": {"Bucket": "<YOUR_BUCKET>", "Name": "<YOUR_OBJECT>"}}' \ --collection-id FaceCollection \ --external-image-id <PERSON_ID>Rekognition does not store the source image bytes in the face collection. It stores extracted facial feature vectors and associated metadata.
https://docs.aws.amazon.com/rekognition/latest/dg/add-faces-to-collection-procedure.html
For each face detected, Amazon Rekognition extracts facial features and stores the feature information in a database. In addition, the command stores metadata for each face that’s detected in the specified face collection. Amazon Rekognition doesn’t store the actual image bytes.
Setting Up the Video Producer
This example uses the Raspberry Pi 4B with 4GB RAM running Ubuntu 23.10 as the video producer.

Building the GStreamer Plugin
AWS provides the Amazon Kinesis Video Streams C++ Producer SDK, GStreamer plugin, and JNI. The kvssink plugin sends the Raspberry Pi camera stream to Kinesis Video Streams.
While AWS offers a Docker image for the GStreamer plugin, the image may not work on Raspberry Pi due to architecture limitations.
Run the following commands. Depending on your system’s specifications, the build may take 20 minutes or more.
sudo apt updatesudo apt upgradesudo apt install \ make \ cmake \ build-essential \ m4 \ autoconf \ default-jdksudo apt install \ libssl-dev \ libcurl4-openssl-dev \ liblog4cplus-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ gstreamer1.0-plugins-base-apps \ gstreamer1.0-plugins-bad \ gstreamer1.0-plugins-good \ gstreamer1.0-plugins-ugly \ gstreamer1.0-tools
git clone https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp.gitmkdir -p amazon-kinesis-video-streams-producer-sdk-cpp/buildcd amazon-kinesis-video-streams-producer-sdk-cpp/build
sudo cmake .. -DBUILD_GSTREAMER_PLUGIN=ON -DBUILD_JNI=TRUEsudo makeAfter the build completes, verify that GStreamer can load kvssink:
cd ~/amazon-kinesis-video-streams-producer-sdk-cppexport GST_PLUGIN_PATH=`pwd`/buildexport LD_LIBRARY_PATH=`pwd`/open-source/local/libgst-inspect-1.0 kvssinkThe output includes plugin details like the following:
Factory Details: Rank primary + 10 (266) Long-name KVS Sink Klass Sink/Video/Network Description GStreamer AWS KVS plugin Author AWS KVS <[email protected]>...Add the exports to ~/.profile so they are set in future login sessions:
echo "" >> ~/.profileecho "# GStreamer" >> ~/.profileecho "export GST_PLUGIN_PATH=$GST_PLUGIN_PATH" >> ~/.profileecho "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> ~/.profileRunning GStreamer
Connect the USB camera to the Raspberry Pi and run the following pipeline to send H.264 video to Kinesis Video Streams.
Be sure to replace the following with the actual values:
<KINESIS_VIDEO_STREAM_NAME><YOUR_ACCESS_KEY><YOUR_SECRET_KEY><YOUR_AWS_REGION>
Higher resolution, frame rate, and bitrate increase the volume of video ingested and stored by Kinesis Video Streams. Rekognition streaming video charges are primarily based on processing duration.
gst-launch-1.0 -v v4l2src device=/dev/video0 \ ! videoconvert \ ! video/x-raw,format=I420,width=320,height=240,framerate=5/1 \ ! x264enc bframes=0 key-int-max=45 bitrate=500 tune=zerolatency \ ! video/x-h264,stream-format=avc,alignment=au \ ! kvssink stream-name=<KINESIS_VIDEO_STREAM_NAME> storage-size=128 access-key="<YOUR_ACCESS_KEY>" secret-key="<YOUR_SECRET_KEY>" aws-region="<YOUR_AWS_REGION>"Verify the live feed in the Kinesis Video Streams console.

Testing
Starting the Rekognition Video Stream Processor
Start the Rekognition Video stream processor. It reads the Kinesis video stream, searches detected faces against the collection, and writes the results to Kinesis Data Streams.
Start the stream processor:
aws rekognition start-stream-processor \ --name face-detector-rekognition-stream-processorVerify that the stream processor is running:
aws rekognition describe-stream-processor \ --name face-detector-rekognition-stream-processor | grep "Status"The expected output should show "Status": "RUNNING".
Capturing and Matching Faces
As the USB camera streams video, the Rekognition Video stream processor detects faces and returns any matches from the face collection.
To check the results, view the Lambda function logs with the following command:
sam logs -n Function \ --stack-name face-detector-using-kinesis-video-streams \ --tailThe logs contain stream processor events like the following:
{ "InputInformation": { "KinesisVideo": { "StreamArn": "arn:aws:kinesisvideo:<AWS_REGION>:<AWS_ACCOUNT_ID>:stream/face-detector-kinesis-video-stream/xxxxxxxxxxxxx", "FragmentNumber": "91343852333181501717324262640137742175000164731", "ServerTimestamp": 1702208586.022, "ProducerTimestamp": 1702208585.699, "FrameOffsetInSeconds": 0.0, } }, "StreamProcessorInformation": {"Status": "RUNNING"}, "FaceSearchResponse": [ { "DetectedFace": { "BoundingBox": { "Height": 0.4744676, "Width": 0.29107505, "Left": 0.33036956, "Top": 0.19599175, }, "Confidence": 99.99677, "Landmarks": [ {"X": 0.41322955, "Y": 0.33761832, "Type": "eyeLeft"}, {"X": 0.54405355, "Y": 0.34024307, "Type": "eyeRight"}, {"X": 0.424819, "Y": 0.5417343, "Type": "mouthLeft"}, {"X": 0.5342691, "Y": 0.54362005, "Type": "mouthRight"}, {"X": 0.48934412, "Y": 0.43806323, "Type": "nose"}, ], "Pose": {"Pitch": 5.547308, "Roll": 0.85795176, "Yaw": 4.76913}, "Quality": {"Brightness": 57.938313, "Sharpness": 46.0298}, }, "MatchedFaces": [ { "Similarity": 99.986176, "Face": { "BoundingBox": { "Height": 0.417963, "Width": 0.406223, "Left": 0.28826, "Top": 0.242463, }, "FaceId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "Confidence": 99.996605, "ImageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "ExternalImageId": "iwasa", }, } ], } ],}HLS URL for Video Playback
For events containing a face search result, the logs also include an HLS URL for the corresponding playback window:
https://x-xxxxxxxx.kinesisvideo.<AWS_REGION>.amazonaws.com/hls/v1/getHLSMasterPlaylist.m3u8?SessionToken=xxxxxxxxxxOpen the HLS URL in Safari or another HLS-compatible player.
Chrome does not natively support HLS playback. You can use a third-party extension, such as Native HLS Playback.

Cleaning Up
Stop the stream processor and remove the stack once finished:
aws rekognition stop-stream-processor \ --name face-detector-rekognition-stream-processorsam deleteConclusion
Kinesis Video Streams carries the Raspberry Pi camera feed, while the Rekognition Video stream processor searches detected faces against a pre-indexed collection. A Lambda function logs each match and creates an HLS URL for the associated playback window.
Because Rekognition Video performs face detection and matching, the Lambda function only needs to decode result records and request a playback URL; it does not process video frames.
The resolution, frame rate, and bitrate passed to kvssink affect Kinesis Video Streams ingestion and storage volume. Start with the low settings used here and increase them only when image quality is insufficient for reliable matching.
Related posts
Sign in with Slack Using Cognito User Pools and OIDC
Federating Cognito user pools with Slack over OIDC and wiring "Sign in with Slack" into a Next.js app with Amplify.
Deploying FastAPI on AWS Lambda with Lambda Web Adapter
Containerizing a FastAPI backend and deploying it to a single Lambda function with Lambda Web Adapter and AWS CDK.
API Gateway WebSocket: Implementing a Mock Integration
Building an API Gateway WebSocket API entirely with mock integrations, returning canned responses with no backend Lambda involved.
Uploading to S3 Through CloudFront Signed URLs
CloudFront signed URLs let you upload to S3 through a custom domain—useful when direct S3 pre-signed URLs are not an option.
AWS EventBridge Scheduler: Starting and Stopping EC2 on a Schedule
Starting and stopping EC2 instances on a cron schedule with EventBridge Scheduler calling the EC2 API directly, no Lambda involved.
