やってみたいけどまだやっていない

プロジェクト棚上げ|飲食店向け顔認証会員システム

Project: OmoteSan (仮) / 開始トリガー:いつでも / 文書バージョン: 1.0 / 2026-04-26

プロジェクトの目的(一行)

VISION 個店でも、高級ホテルのコンシェルジュレベルの「私のこと覚えてくれてる感」を、月¥30,000で実現する。

背景

  • ドンマンジョーネ案件で「どんまーパス」サブスクを設計中(2026年4月)
  • みーきゅんが「顔認証で前回注文を覚えて『いつものハイボール濃いめでいいですか?』と聞ける店」発想を提示
  • 「人間の店員が言うから価値がある」「AIアバターは却下」「顔認証は会員カード代わり+ポイント連動」と方針確定
  • ドンマンジョーネ実装が成功すれば、そのまま個店向けSaaS事業として展開可能と判断
  • 本ドキュメントは、後日プロジェクトを着手する際の起動ファイルとして保管

3つの選択肢(着手時に決定)

選択肢規模初期コスト収益見込リスク
A. 単店専用
ドンマンジョーネのみ運用
1店舗 ¥10,000程度 店から¥600,000+月¥30,000 最小
B. 地域SaaS
千種区・名古屋市内10-50店
10-50店舗 ¥500,000程度 月¥300,000-¥1,500,000
C. 全国SaaS化
OmoteSan独立事業化
100-1,000店舗 ¥3,000,000-¥10,000,000 年¥3.5億〜
推奨開始順序 A → ドンマンジョーネで実証 → 千種区他店から問い合わせ来たらB → さらに伸びたらC。
本ドキュメントは「Aの実装手順」を中心にまとめる。後でB/Cに昇格する設計を意識。

技術スタック(決定済み)

レイヤ採用技術理由
顔検出MediaPipe Face DetectionGoogle製、軽量、リアルタイム可
顔認証face_recognition (dlib + FaceNet)OSS定番、精度98-99%、Python1行で使える
ベクトルDBFaiss or pgvector100名なら全件比較でも数ms、Faissで余裕
APIFastAPI (既存)katuocloud基盤に追加、認証も既存活用
会員DBMariaDB (既存) + 専用テーブル既存のmikyun_cloudスキーマを拡張
リアルタイム通知WebSocket + LINE Bot (既存)店員iPadへ即時表示・お客様LINE通知
店員UIWeb (HTML + Tailwind)iPadのSafariで開くだけ、アプリ不要
カメラLogitech C920(USBカメラ)¥8,000、Linuxドライバ標準対応
サーバーR640 (既存) + 店舗用Raspberry Pi 5店舗ローカル処理+クラウド同期
注意 顔認証エンジンは 店舗ローカルのRaspberry Pi 5で動かす のが正解(プライバシー+低レイテンシ)。
クラウド(R640)には 顔特徴量ベクトル(数値)と会員IDのみ同期。顔写真自体は店舗外に出さない。

アーキテクチャ図

┌─────────────────────── 店舗 ───────────────────────┐ [ Logitech C920 ] USB [ Raspberry Pi 5 ] エッジ処理 ├ MediaPipe (顔検出) ├ face_recognition (特徴量抽出) ├ Faiss (ローカル会員DBで比較) └ WebSocket送信 Wi-Fi (店内LAN) [ 店員iPad ] Web画面 「3番テーブル:山田様、前回ラグー、 ハイボール濃いめ。本日のオススメ: アクアパッツァ ¥1,680」 └─────────────────────────────────────────────────────┘ WireGuard VPN ┌──── R640 (katuocloud / クラウド) ────┐ [ FastAPI / mikyun-portal ] ├ /api/face/register ├ /api/face/match ├ /api/points/award └ /api/order/upsell-suggest [ MariaDB: mikyun_cloud ] ├ members (既存) ├ face_embeddings (新規) ├ visit_log (新規) ├ order_history (新規) └ points_ledger (新規) [ LINE Messaging API ] お客様への自動通知 └───────────────────────────────────────┘

DBスキーマ設計(追加分のみ)

-- 既存 members テーブルへの拡張 ALTER TABLE members ADD COLUMN face_consent_at DATETIME NULL, -- 顔認証同意日時 ADD COLUMN face_consent_revoked_at DATETIME NULL, -- 撤回日時 ADD COLUMN total_points INT NOT NULL DEFAULT 0, -- 累計ポイント ADD COLUMN available_points INT NOT NULL DEFAULT 0;-- 利用可能ポイント -- 顔特徴量ベクトル(128次元 × float32 = 512バイト) CREATE TABLE face_embeddings ( id BIGINT AUTO_INCREMENT PRIMARY KEY, member_id BIGINT NOT NULL, embedding BLOB NOT NULL, -- 128次元float32 reference_image_hash CHAR(64), -- 元画像のSHA256(トレース用) enrolled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, is_active TINYINT(1) NOT NULL DEFAULT 1, FOREIGN KEY (member_id) REFERENCES members(id) ON DELETE CASCADE, INDEX idx_member (member_id, is_active) ); -- 来店ログ CREATE TABLE visit_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, member_id BIGINT NOT NULL, store_id VARCHAR(32) NOT NULL, -- マルチ店舗対応 visited_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, recognition_method ENUM('face','qr','manual') DEFAULT 'face', recognition_confidence FLOAT, FOREIGN KEY (member_id) REFERENCES members(id), INDEX idx_member_time (member_id, visited_at) ); -- 注文履歴 CREATE TABLE order_history ( id BIGINT AUTO_INCREMENT PRIMARY KEY, visit_id BIGINT NOT NULL, member_id BIGINT NOT NULL, item_name VARCHAR(128) NOT NULL, item_category VARCHAR(32), -- pasta/risotto/drink/dessert等 price_yen INT NOT NULL, notes TEXT, -- 「ハイボール濃いめ」等の好み ordered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (visit_id) REFERENCES visit_log(id) ON DELETE CASCADE, INDEX idx_member (member_id, ordered_at) ); -- ポイント台帳(増減すべて記録) CREATE TABLE points_ledger ( id BIGINT AUTO_INCREMENT PRIMARY KEY, member_id BIGINT NOT NULL, points_delta INT NOT NULL, -- 正なら付与、負なら使用 reason ENUM('visit','redeem','expire','adjust'), related_visit_id BIGINT NULL, description VARCHAR(255), occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (member_id) REFERENCES members(id), INDEX idx_member_time (member_id, occurred_at) );

FastAPI エンドポイント設計

メソッドパス用途認証
POST/api/face/enroll会員の顔を新規登録(複数枚OK)HTTP Basic(管理者)
POST/api/face/match顔ベクトルを送信、最も近い会員を返す店舗API Key
GET/api/members/{id}/profile来店時の会員情報+過去注文+好み店舗API Key
POST/api/visits来店記録 + ポイント自動付与店舗API Key
POST/api/orders注文記録(ポイント付与の根拠)店舗API Key
POST/api/points/redeemポイント引換店舗API Key
GET/api/upsell/{member_id}過去注文ベースのアップセル提案店舗API Key
DELETE/api/face/forget/{member_id}顔データの完全削除(退会・撤回)HTTP Basic + 本人確認
認証設計 店舗側Raspberry PiからR640へのアクセスは WireGuard VPN内でのAPI Key認証
管理画面への外部アクセスは既存の /ui_admin のHTTP Basic認証を流用。

コードスケルトン(Python)

1. 顔登録エンドポイント

# app/routers/face.py from fastapi import APIRouter, UploadFile, Depends, HTTPException import face_recognition import numpy as np from .deps import get_db, require_admin router = APIRouter(prefix="/api/face", tags=["face"]) @router.post("/enroll") async def enroll_face( member_id: int, image: UploadFile, db = Depends(get_db), _admin = Depends(require_admin), ): # 1. 同意確認 member = db.execute( "SELECT face_consent_at FROM members WHERE id = %s", (member_id,) ).fetchone() if not member or not member['face_consent_at']: raise HTTPException(403, "face_consent_required") # 2. 画像から特徴量抽出 img = face_recognition.load_image_file(image.file) encodings = face_recognition.face_encodings(img) if len(encodings) != 1: raise HTTPException(400, "face_must_be_exactly_one") embedding = encodings[0].astype(np.float32).tobytes() # 3. DB保存 db.execute( "INSERT INTO face_embeddings (member_id, embedding) VALUES (%s, %s)", (member_id, embedding) ) db.commit() return {"status": "enrolled", "member_id": member_id}

2. 顔認識エンドポイント(店舗からの問い合わせ)

# app/routers/face.py 続き @router.post("/match") async def match_face( embedding_b64: str, # 店舗側で抽出済みの128次元ベクトル db = Depends(get_db), _api_key = Depends(require_store_key), ): # 1. デコード target = np.frombuffer(base64.b64decode(embedding_b64), dtype=np.float32) # 2. 全アクティブ会員と比較 rows = db.execute(""" SELECT fe.member_id, fe.embedding, m.name, m.available_points FROM face_embeddings fe JOIN members m ON m.id = fe.member_id WHERE fe.is_active = 1 AND m.face_consent_revoked_at IS NULL """).fetchall() best_match = None best_distance = 1.0 for row in rows: emb = np.frombuffer(row['embedding'], dtype=np.float32) distance = np.linalg.norm(target - emb) if distance < best_distance: best_distance = distance best_match = row # 3. 閾値判定(0.5未満を一致とする) if not best_match or best_distance >= 0.5: return {"matched": False} return { "matched": True, "member_id": best_match['member_id'], "name": best_match['name'], "confidence": float(1 - best_distance), "available_points": best_match['available_points'], }

3. 来店記録+自動ポイント付与

# app/routers/visits.py POINT_RULES = { 980: 10, 1280: 15, 1480: 20, 1680: 25, } @router.post("/api/visits") async def log_visit( member_id: int, store_id: str, confidence: float, method: str = "face", db = Depends(get_db), ): # 1. 来店ログ cursor = db.execute(""" INSERT INTO visit_log (member_id, store_id, recognition_method, recognition_confidence) VALUES (%s, %s, %s, %s) """, (member_id, store_id, method, confidence)) visit_id = cursor.lastrowid # 2. LINE通知(裏で) asyncio.create_task(notify_line(member_id, "おかえりなさい!本日もお疲れさまです🍝")) return {"visit_id": visit_id} @router.post("/api/orders") async def log_order( visit_id: int, item_name: str, price_yen: int, notes: str = None, db = Depends(get_db), ): # 1. 注文記録 member_id = db.execute("SELECT member_id FROM visit_log WHERE id = %s", (visit_id,)).fetchone()['member_id'] db.execute(""" INSERT INTO order_history (visit_id, member_id, item_name, price_yen, notes) VALUES (%s, %s, %s, %s, %s) """, (visit_id, member_id, item_name, price_yen, notes)) # 2. ポイント自動付与 points = POINT_RULES.get(price_yen, price_yen // 100) db.execute(""" INSERT INTO points_ledger (member_id, points_delta, reason, related_visit_id, description) VALUES (%s, %s, 'visit', %s, %s) """, (member_id, points, visit_id, f"order: {item_name}")) db.execute(""" UPDATE members SET total_points = total_points + %s, available_points = available_points + %s WHERE id = %s """, (points, points, member_id)) db.commit() return {"points_awarded": points}

4. アップセル提案(店員iPad向け)

# app/routers/upsell.py @router.get("/api/upsell/{member_id}") async def upsell_suggest(member_id: int, db = Depends(get_db)): # 1. 過去30日の注文を取得 orders = db.execute(""" SELECT item_name, item_category, price_yen, notes, COUNT(*) as freq FROM order_history WHERE member_id = %s AND ordered_at >= NOW() - INTERVAL 30 DAY GROUP BY item_name, item_category, price_yen, notes ORDER BY freq DESC """, (member_id,)).fetchall() # 2. 「いつもの」を抽出 favorite_drink = next((o for o in orders if o['item_category'] == 'drink'), None) favorite_pasta = next((o for o in orders if o['item_category'] == 'pasta'), None) # 3. 提案文生成(店員が読み上げる用) suggestions = [] if favorite_drink: notes = f" ({favorite_drink['notes']})" if favorite_drink['notes'] else "" suggestions.append(f"いつもの{favorite_drink['item_name']}{notes}でいいですか?") if favorite_pasta: suggestions.append(f"前回ご好評の{favorite_pasta['item_name']}、本日もお作りできます") # 4. 価格帯アップセル(高単価ほどポイント率高い) avg_price = sum(o['price_yen'] for o in orders) // max(len(orders), 1) if avg_price < 1480: suggestions.append("今夜はちょっと豪華に¥1,480のコースもおすすめです(20pt付与)") return {"suggestions": suggestions, "avg_spending": avg_price}

店舗側 Raspberry Pi コード(要旨)

# store_client/main.py - 店舗側で常駐するプロセス import cv2 import face_recognition import requests import websocket import base64 API_BASE = "http://r640.internal:9000" STORE_ID = "donmangione-ikeshita" API_KEY = "" def main(): cap = cv2.VideoCapture(0) last_seen = {} # member_id -> timestamp(重複検知防止) while True: ret, frame = cap.read() rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) encodings = face_recognition.face_encodings(rgb) for emb in encodings: # R640に問い合わせ r = requests.post( f"{API_BASE}/api/face/match", json={"embedding_b64": base64.b64encode(emb.tobytes()).decode()}, headers={"X-API-Key": API_KEY}, ).json() if r.get("matched"): mid = r["member_id"] # 5分以内の重複は無視 if mid in last_seen and time.time() - last_seen[mid] < 300: continue last_seen[mid] = time.time() # 来店記録 requests.post(f"{API_BASE}/api/visits", json={ "member_id": mid, "store_id": STORE_ID, "confidence": r["confidence"], "method": "face", }, headers={"X-API-Key": API_KEY}) # 店員iPadへWebSocket通知 send_to_ipad({ "event": "member_arrived", "name": r["name"], "member_id": mid, "available_points": r["available_points"], }) time.sleep(0.5) # 0.5秒に1回チェック if __name__ == "__main__": main()

店員iPad UI(HTML雛形)

<!-- iPad向けリアルタイム会員到着画面 --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ドンマンジョーネ 来店アシスト</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-900 text-white p-8"> <h1 class="text-3xl mb-6">来店ピックアップ</h1> <div id="arrivals" class="space-y-4"></div> <script> const ws = new WebSocket('wss://r640.internal/ws/store/donmangione-ikeshita'); const list = document.getElementById('arrivals'); ws.onmessage = async (ev) => { const msg = JSON.parse(ev.data); if (msg.event === 'member_arrived') { // アップセル提案を取得 const r = await fetch(`/api/upsell/${msg.member_id}`).then(r => r.json()); const card = document.createElement('div'); card.className = 'bg-white text-gray-900 p-6 rounded-lg shadow-xl'; card.innerHTML = ` <div class="text-2xl font-bold mb-2">${msg.name} 様 ご来店</div> <div class="text-sm text-gray-600 mb-4"> 会員ポイント: ${msg.available_points}pt </div> <div class="border-l-4 border-red-500 pl-3"> <div class="font-bold text-red-700 mb-1">接客スクリプト</div> ${r.suggestions.map(s => `<div>・${s}</div>`).join('')} </div> `; list.prepend(card); // 5分後に消す setTimeout(() => card.remove(), 300000); } }; </script> </body> </html>

買い物リスト(着手日にAmazonでポチる)

  • 必須 Logitech C920 HD Pro Webcam(¥10,000程度)
  • 必須 Raspberry Pi 5 8GB(¥18,000程度)
  • 必須 Pi 5用 Active Cooler(¥1,500)
  • 必須 Pi 5用 microSDカード 64GB(¥1,500)
  • 必須 Pi 5用電源 27W USB-C PD(¥2,000)
  • あると便利 カメラ用三脚 or マウントアーム(¥1,500)
  • あると便利 LEDリングライト(暗い時間帯用、¥2,500)
  • 店員側 iPad(既存転用OK、なければ中古¥30,000〜)
合計 最小構成 約¥35,000、フル構成 約¥70,000。
ドンマンジョーネに導入する場合は、Phase 4 のオプション工事費に組み込み可。

プライバシー・法令対応チェックリスト

  • 入会時の同意フォーム作成(顔認証使用について明示)
  • 同意取消フローの実装(DELETE /api/face/forget/{id})
  • 店内に「顔認証システム稼働中」掲示
  • プライバシーポリシーに顔データ取扱を明記
  • 顔特徴量の暗号化(DB保存時にAES-256)
  • 顔写真原本は保存しない(特徴量抽出後即削除)
  • クラウド外送信なし(店舗ローカル処理)
  • 退会時の即削除+ログ残す
  • 個人情報保護委員会への届け出要否確認(規模次第)
  • 顧問弁護士に利用規約レビュー依頼

マイルストーン(着手後)

Weekマイルストーン確認方法
Day 1-2hello_face.py で自分の顔が認識されるWebカメラに映って名前表示
Week 1FastAPI に face エンドポイント追加Postmanで /api/face/match が動く
Week 2Raspberry Pi にカメラ常駐プロセス本番店舗想定でカメラ→Pi→R640フロー成功
Week 3店員iPad UI完成WebSocket通知が iPad に表示される
Week 4ポイント連動完成注文記録→ポイント加算→LINE通知
Week 5ベータテスト(自分+家族)10名規模で精度98%以上
Week 6ドンマンジョーネ実装開始店舗オーナー合意あり

事業化を考え始める分岐ポイント(KPI)

継続判断(A→B昇格)
  • ドンマンジョーネで6ヶ月安定運用
  • 会員ポイント実使用率50%以上
  • 追加注文発生率20%以上
  • 他店から問い合わせ3件以上
事業化判断(B→C昇格)
  • 地域10店舗で運用実績
  • 月次解約率5%以下
  • 1店舗あたり月¥30,000の継続課金が成立
  • LINE通知のクリック率20%以上

アイデアメモ(実装時に検討)

  • マスク対応:マスク有り画像も学習させる(精度85%目標)
  • 双子・親子の誤認:閾値調整+複数枚学習で対応
  • 季節提案:体調別メニューと連動「冬は温かい料理を多めに」
  • 誕生日サプライズ:誕生日来店検知 → シェフへ即通知
  • 「お久しぶりLINE」自動化:14日来店なし → 自動でLINE
  • 予約システム連動:予約時刻+顔認証で「予約のお客様、ご到着です」
  • 同伴者識別:会員+家族2名識別 → 家族割引適用
  • 客層分析ダッシュボード:年齢・性別の傾向(個人特定なし統計)
  • SaaS化前提のマルチテナント設計:store_id をUUIDで切る

関連リンク(着手時に開く)

用途リンク
face_recognition (Python)github.com/ageitgey/face_recognition
MediaPipedevelopers.google.com/mediapipe
Faiss (Facebook AI)github.com/facebookresearch/faiss
Logitech C920www.logitech.com/ja-jp/products/webcams/c920-pro-hd-webcam.html
Raspberry Pi 5www.raspberrypi.com/products/raspberry-pi-5/
個人情報保護委員会ガイドラインwww.ppc.go.jp/personalinfo/

再開時のチェックポイント

再開時、まずこれを読む 1. このドキュメントを冒頭から読み直す(30分)
2. ドンマンジョーネ提案書(donmangione_proposal_v2.pptx)の P24-26 を見直す
3. /home/claude/face-project/ ディレクトリのコード雛形を確認
4. R640 の現状(mikyun-portal)を確認、新エンドポイント追加箇所を決定
5. Webカメラを購入していなければ買う
6. hello_face.py から開始
再開条件の目安 - ドンマンジョーネ提案がオーナーに通った時
- 千種区他店から「うちもやりたい」と連絡来た時
- katuocloud 本体が安定運用に入った時
- 単純に「やる気が湧いた」時(これが一番大事)

本ドキュメント最終更新: 2026-04-26 / Mikyun Web Lab. 内部資料 / 棚上げ承認: みーきゅん

OmoteSan — 飲食店向け顔認証会員システム

個店でも、高級ホテルのコンシェルジュレベルの「私のこと覚えてくれてる感」を、月¥30,000で実現する。

ステータス: 棚上げ中(2026-04-26)
着手予定: ドンマンジョーネ提案承認後 / または やる気が湧いたとき
設計者: みーきゅん × カツオ


このプロジェクトは何か

飲食店向けの顔認証ベース会員管理システム。
入店時に顔を認識して、会員カード代わりにポイント付与+過去注文を店員iPadに表示する。
AIが裏方、人間が表方で、「いつものハイボール濃いめでいいですか?」を成立させる。

中核機能

  1. 顔認証 = 会員カード
    入店時にカメラがピッと認識、財布からカードを出す必要なし
  2. 来店ポイント自動付与
    ¥980→10pt / ¥1,280→15pt / ¥1,480→20pt / ¥1,680→25pt
  3. 店員iPadへの「神接客カンペ」
    過去注文・好み・体調記録を即時表示。店員(人間)が自然に接客

なぜ作るか

  • ドンマンジョーネで実証可能(既存提案案件)
  • みーきゅんの既存資産(katuocloud / FastAPI / LINE Bot / Stripe)が95%流用可能
  • 個店向け統合サービスの市場空白
  • 「人間性 × AI」コンセプトの時代適合性

詳細ドキュメント

完全な設計・コード雛形・買い物リスト・マイルストーンは:

📄 face_recognition_project_handover.html(WordPress貼り付け可)

クイックスタート(着手日にやること)

# 1. 環境構築
cd ~/projects
mkdir omotesan && cd omotesan
python -m venv .venv && source .venv/bin/activate
pip install face_recognition opencv-python fastapi uvicorn

# 2. Hello Face を試す
python hello_face.py
# → Webカメラに自分の顔が映って名前が表示されたら成功

# 3. R640 へのデプロイ準備
# /root/mikyun-cloud/mikyun-portal/app/routers/face.py に
# 本ドキュメントのスケルトンを配置

アーキテクチャ概要

[ Logitech C920 ] → USB → [ Raspberry Pi 5 ] → WireGuard → [ R640 / FastAPI ]
                                                              │
                                                              ├ MariaDB
                                                              ├ LINE Bot
                                                              └ WebSocket
                                                                    ↓
                                                              [ 店員iPad ]
  • 店舗側 Pi 5: 顔検出・特徴量抽出(プライバシー保護)
  • R640: 会員DB照合・ポイント管理・LINE通知
  • iPad: WebSocket でリアルタイム表示

拡張プラン

Phase規模月収益見込
A. 単店専用ドンマンジョーネのみ¥30,000
B. 地域SaaS千種区10-50店¥300,000-1,500,000
C. 全国SaaS100-1,000店¥3,000,000-30,000,000

ライセンス・データ取扱

  • 顔写真は保存しない(特徴量のみ)
  • 全データ店舗ローカル処理
  • 退会時即削除
  • 個人情報保護法準拠

関連プロジェクト


「面白そうだからやってみたい」が、すべての始まり。