Raspberry Pi、Kinesis Video Streams、Amazon Rekognition によるリアルタイム顔照合
Raspberry Pi から Kinesis Video Streams へ動画を送信し、検出した顔を Amazon Rekognition のコレクションと照合します。
Raspberry Pi の USB カメラ映像を Kinesis Video Streams へ送信します。Amazon Rekognition Video のストリームプロセッサが顔を検出し、事前に登録した顔コレクションから一致する顔を検索します。
必要要件
ハードウェア要件
- Raspberry Pi 4B(RAM 4 GB)
- Ubuntu 23.10 を実行(Raspberry Pi Imager でインストール)
- USB カメラ
ソフトウェア要件
- GStreamer
- Amazon Kinesis Video Streams CPP Producer, GStreamer Plugin and JNI
- AWS SAM CLI
- Python 3.11
AWS リソースの構築
AWS SAM テンプレート
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 関数
- Lambda のイベントソースマッピングは、Kinesis Data Streams の各レコードを Base64 エンコードされた文字列として渡します。関数は 17 行目でデコードします。
- 顔の検索結果がある場合、Lambda 関数は該当する時間範囲の HLS 再生 URL を生成します(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']スタックのデプロイ
SAM アプリケーションをビルドしてデプロイします。
sam buildsam deploy顔のインデックス登録
カメラ映像から既知の顔を検索するため、IndexFaces API で参照画像の顔を Rekognition コレクションへ登録します。
以下を実際の値に置き換えてください。
<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 は顔コレクションに元の画像データを保存しません。抽出した顔特徴ベクトルと関連メタデータを保存します。
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.
ビデオプロデューサーのセットアップ
この例では、Ubuntu 23.10 を実行する Raspberry Pi 4B(RAM 4 GB)をビデオプロデューサーとして使用します。

GStreamer プラグインのビルド
AWS は Amazon Kinesis Video Streams C++ Producer SDK、GStreamer プラグイン、JNI を提供しています。kvssink プラグインを使って、Raspberry Pi のカメラ映像を Kinesis Video Streams へ送信します。
AWS は GStreamer プラグイン用の Docker イメージ を提供していますが、アーキテクチャの制約により Raspberry Pi では動作しない場合があります。
以下のコマンドを実行します。システムの性能によっては、ビルドに 20 分以上かかります。
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 makeビルドが完了したら、GStreamer が 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 kvssink以下のような出力が表示されるはずです。
Factory Details: Rank primary + 10 (266) Long-name KVS Sink Klass Sink/Video/Network Description GStreamer AWS KVS plugin Author AWS KVS <[email protected]>...今後のログインセッションでも環境変数を設定するため、~/.profile に以下の export 文を追加します。
echo "" >> ~/.profileecho "# GStreamer" >> ~/.profileecho "export GST_PLUGIN_PATH=$GST_PLUGIN_PATH" >> ~/.profileecho "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> ~/.profileGStreamer の実行
USB カメラを Raspberry Pi に接続し、以下のパイプラインで H.264 動画を Kinesis Video Streams へ送信します。
以下を実際の値に置き換えてください。
<KINESIS_VIDEO_STREAM_NAME><YOUR_ACCESS_KEY><YOUR_SECRET_KEY><YOUR_AWS_REGION>
解像度、フレームレート、ビットレートを上げると、Kinesis Video Streams が取り込み、保存するデータ量が増えます。Rekognition のストリーミング動画料金は、主に処理時間に基づきます。
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>"Kinesis Video Streams コンソールでライブ映像を確認します。

テスト
Rekognition Video ストリームプロセッサの起動
Rekognition Video ストリームプロセッサ を起動します。Kinesis の動画ストリームを読み込み、検出した顔をコレクションと照合して、結果を Kinesis Data Streams へ出力します。
ストリームプロセッサを起動します。
aws rekognition start-stream-processor \ --name face-detector-rekognition-stream-processorストリームプロセッサが実行中であることを確認するため、ステータスを確認します。
aws rekognition describe-stream-processor \ --name face-detector-rekognition-stream-processor | grep "Status""Status": "RUNNING" と表示されれば正常です。
顔の検出と照合
USB カメラから動画が送信されると、Rekognition Video ストリームプロセッサが顔を検出し、顔コレクションから一致する候補を返します。
結果を確認するには、以下のコマンドで Lambda 関数のログを表示します。
sam logs -n Function \ --stack-name face-detector-using-kinesis-video-streams \ --tailログレコードには、以下の例のようにストリームプロセッサイベントの詳細情報が含まれます。
{ "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
顔の検索結果を含むイベントでは、該当する再生時間の HLS URL もログに出力されます。
https://x-xxxxxxxx.kinesisvideo.<AWS_REGION>.amazonaws.com/hls/v1/getHLSMasterPlaylist.m3u8?SessionToken=xxxxxxxxxxSafari または HLS 対応プレーヤーで URL を開きます。
Chrome は HLS 再生をネイティブにサポートしていません。Native HLS Playback などのサードパーティ拡張機能を利用できます。

クリーンアップ
作業が終わったら、ストリームプロセッサを停止しスタックを削除します。
aws rekognition stop-stream-processor \ --name face-detector-rekognition-stream-processorsam deleteまとめ
Kinesis Video Streams が Raspberry Pi のカメラ映像を配信し、Rekognition Video ストリームプロセッサが検出した顔を登録済みのコレクションと照合します。Lambda 関数は検索結果をログへ出力し、該当する再生時間の HLS URL を生成します。
顔の検出と照合は Rekognition Video が行うため、Lambda 関数は結果レコードのデコードと再生 URL の取得だけを担当し、動画フレームを処理しません。
kvssink に渡す解像度、フレームレート、ビットレートは、Kinesis Video Streams の取り込み・保存データ量に影響します。まずはこの記事の低い設定から始め、安定した照合に画質が足りない場合だけ引き上げてください。
Related posts
Cognito User Pools と OIDC で Slack サインインを実装する
Cognito user pool を OIDC 経由で Slack と連携させ、"Sign in with Slack" を Amplify で Next.js アプリケーションに組み込みます。
Lambda Web Adapter で FastAPI を AWS Lambda にデプロイする
FastAPI で書いた API バックエンドをコンテナ化し、Lambda Web Adapter と AWS CDK を使って単一の Lambda 関数へデプロイします。
API Gateway WebSocket:モック統合の実装
バックエンドの Lambda を使わず、モック統合のみで API Gateway WebSocket API を構築し、あらかじめ用意したレスポンスを返します。
CloudFront 署名付き URL 経由で S3 にアップロードする
CloudFront の署名付き URL を使い、独自ドメイン経由で S3 にアップロードする方法を紹介します。S3 の署名付き URL を直接使えない場合に有用です。
AWS EventBridge Scheduler:スケジュールに沿って EC2 を起動・停止する
Lambda を介さず、EventBridge Scheduler から EC2 API を直接呼び出し、cron スケジュールに従って EC2 インスタンスを起動・停止します。
