全国オンライン対応受付 平日 9:00-18:00
お問い合わせ

開発記録 / simpleRec制作記 / Vol.22 simpleRec制作記 Vol.1

記事 01

顔認証SaaSをゼロから作った話——AWS Rekognition・FastAPI・Google Sheets

その言葉から始まった。店舗の防犯と来店マーケティングを目的とした顔認証SaaSを、シンプルシステム株式会社の自社プロダクトとして作ることにした。

2026-06-07 公開

「要注意人物が来たら、すぐに知りたい」

その言葉から始まった。店舗の防犯と来店マーケティングを目的とした顔認証SaaSを、シンプルシステム株式会社の自社プロダクトとして作ることにした。

simpleRec(シンプルレック)と名付けた。


何ができるシステムか

カメラが人物を検知するとスナップショットをサーバーに送信する。サーバーはAWS Rekognitionで顔を認識し、登録済み人物と照合する。照合結果に応じてLINEで通知する。

人物の種別は4つだ。要注意人物(danger)・VIP顧客(vip)・スタッフ(staff)・未登録者(unknown)。dangerは即時通知、vipは来店通知、unknownは任意通知。来店した人が誰かによって、通知の優先度を変える。


マルチテナントSaaSの設計

simpleRecは複数の店舗(テナント)が使うSaaSだ。テナントAの顔認識データがテナントBから見えてはならない。

全てのデータにtenant_idを持たせて、APIの認可レイヤーで「自分のテナントのデータしか取得できない」という制限をかける。JWTトークンにtenant_idを埋め込み、全てのエンドポイントで検証する。

@app.get('/api/persons')
async def get_persons(current_user: User = Depends(get_current_user)):
    persons = get_persons_by_tenant(current_user.tenant_id)
    return persons

マルチテナントの分離はシステムの根幹だ。ここを後から追加するのは難しい。設計の最初から入れた。


なぜGoogle Sheetsをデータベースに使うか

通常のWebアプリではPostgreSQL・MySQLなどのRDBMSを使う。simpleRecではGoogleスプレッドシートを選んだ。

理由は2つある。テナント担当者が直接データを確認・編集できること。登録人物のリストを確認したい、手動でデータを修正したい、という場面でAPIを経由せずにスプレッドシートを開けばいい。

もう1つは、検証フェーズの開発速度だ。スキーマ定義やマイグレーションが不要で、シートに列を追加するだけでデータ構造を変えられる。

スプレッドシートのデータ取得はgspread(Python)を使う。get_all_records(numericise_ignore=['all'])で取得することで、数値として自動変換されるのを防ぐ。


AWS Rekognitionの使い方

Rekognitionには「コレクション」という概念がある。登録済みの顔を入れるバケツのようなものだ。テナントごとに1つのコレクションを持つ設計にした。

import boto3

rekognition = boto3.client('rekognition', region_name='ap-southeast-2')

def register_face(tenant_id: str, person_id: str, image_bytes: bytes):
    collection_id = f'simplerec-{tenant_id}'
    response = rekognition.index_faces(
        CollectionId=collection_id,
        Image={'Bytes': image_bytes},
        ExternalImageId=person_id,
        MaxFaces=1,
    )
    return response['FaceRecords']

def search_face(tenant_id: str, image_bytes: bytes):
    collection_id = f'simplerec-{tenant_id}'
    response = rekognition.search_faces_by_image(
        CollectionId=collection_id,
        Image={'Bytes': image_bytes},
        MaxFaces=1,
        FaceMatchThreshold=80,
    )
    return response.get('FaceMatches', [])

照合の閾値(FaceMatchThreshold)は80%に設定した。高すぎると本人を認識しない。低すぎると別人を認識する。80%は誤認識と見落としのバランスとして、実験で確認した値だ。


個人情報保護の法的整理

顔認証データは個人情報の中でも「要配慮個人情報」に該当する可能性がある。SaaSとして提供する場合の責任の所在を整理した。

「利用者(テナント)が個人情報取扱事業者」で「シンプルシステムは委託処理者」という位置づけにした。顔画像・来場記録の管理責任は利用者(店舗)に帰属し、シンプルシステムはシステムという器を提供する。

この整理は個人情報保護委員会に照会して確認した。テナント間のデータ共有がないこと(tenant_idによる分離)が前提条件だ。


*次回は「TP-Link VIGIカメラからWebhookでスナップショットを受け取る——ファイル名なしmultipartの罠」*

*シンプルシステム株式会社 代表 伊藤勝彦*