FLASH
FLASH(Front-camera Live Auto Shutter by palm Hand)は、セミナー受付における出席確認を自動化するスマートフォン向けWebアプリケーションである。運営側が用意したAndroidスマートフォンをキオスク端末として設置し、参加者がフロントカメラに手のひらをかざすだけで、カウントダウン撮影が自動実行される。撮影された顔写真はサーバーへ自動アップロードされ、次の参加者の撮影待機に即座に移行する。
手のひら検出にはGoogleのMediaPipe Handsを採用しており、すべてのML推論はブラウザ内で完結する。映像フレームが外部サーバーに送信されることはなく、送信されるのは撮影確定後のJPEG画像のみである。ネイティブアプリのインストールは不要で、URLアクセスだけで利用を開始できる。参加者はカメラの前で手のひらをかざすだけでよく、画面に一切触れる必要がない。
概要
FLASHは、セミナー等の受付における出席確認用の顔写真撮影を自動化するWebアプリケーションである。運営側が用意したAndroidスマートフォンのブラウザ上で動作し、URLにアクセスするだけで利用を開始できる。参加者がフロントカメラの前で手のひらをかざすと、MediaPipe Handsが手のひらの開いた状態を検出し、カウントダウン撮影を自動実行する。撮影画像は設定されたサーバーへ自動アップロードされ、次の参加者の撮影に即座に移行する。受付スタッフと参加者双方の操作負荷を最小化し、スムーズな出席確認フローを実現することを目的としている。
背景と課題
- 顔写真記録の要求 — セミナー受付での出席確認に顔写真記録が求められるケースがあるが、参加者ごとにスタッフがカメラ操作を行うのは非効率である
- インストール不要の要求 — ネイティブアプリのインストールなしに、URLアクセスのみで利用可能なソリューションが求められている
- 非音声操作の要求 — 音声による操作が使えない受付環境への代替手段が必要であり、手のひら検出による非接触トリガーが最適解として選定された
特徴
- 完全非接触 — 参加者は画面に一切触れる必要がない。手のひらをかざすだけで撮影が完了する
- ブラウザ動作 — ネイティブアプリのインストール不要。Android ChromeのURLアクセスだけで即座に利用開始できる
- オンデバイスML推論 — MediaPipe Handsによる手のひら検出はすべて端末内で完結し、映像フレームが外部に送信されることはない
- 自動ループ運用 — 撮影→アップロード→次の待機が全自動で繰り返され、スタッフの介入なく連続運用できる
- カウントダウン確定実行 — 一度開始されたカウントダウンは必ず最後まで進行し、中断やキャンセルは発生しない
- PWA対応 — ホーム画面に追加してアプリライクに起動・利用できる。Service Workerによるキャッシュで2回目以降の起動が高速化される
- PIN保護の設定画面 — 設定変更は4桁PINコードで保護され、参加者による意図しない設定変更を防止する
- ネットワーク障害への耐性 — ローカル保存モードに切り替えることで、オフライン環境でも撮影・保存が継続可能
システム構成
FLASHはクライアントサイドで撮影・手のひら検出を完結し、サーバー側は画像受信・保存のみを担当する構成をとる。Webアプリ本体(HTML/CSS/JS/MediaPipe Handsモデル等の静的ファイル)およびPHP受信スクリプトは同一のXサーバー上に設置される。
クライアントサイド
カメラ映像取得にはMediaDevices API(getUserMedia)を使用し、facingMode: "user" の指定によりフロントカメラを選択する。手のひら検出にはMediaPipe Handsを使用し、ブラウザ上で21個のランドマーク検出を行って手のひらが開いた状態を判定する。撮影処理は <video> 要素のフレームを <canvas> に描画し、canvas.toBlob() によりJPEG画像を生成する。解像度は1920×1080程度の高品質である。画面のスリープ防止にはWake Lock APIを使用し、設定値の永続化にはlocalStorageを使用する。画面の向きはCSS・JavaScriptにより縦向き固定とされる。
サーバーサイド
PHPスクリプトがPOSTリクエストで画像データとAPIキーを受信する。APIキーの照合を行い、不一致の場合はリクエストを拒否する(HTTP 403)。受信日に応じた日付フォルダ(例:20260214)をサーバー上に自動作成し、クライアントから送信されたファイル名で画像ファイルを保存する。接続テスト用のリクエストにも応答する。
利用フロー
運営側の準備
- Android端末のブラウザ(Chrome推奨)でFLASHのURLにアクセスする
- 初回アクセス時はPINコード設定画面が自動表示されるので、4桁の数字PINを設定する
- PIN設定完了後、設定画面に遷移する。アップロード先URL、APIキー、セミナー名、ガイドメッセージ等を入力する
- 「接続テスト」ボタンでサーバーとの疎通を確認する
- 設定完了後、撮影待機画面に遷移する
- 端末を受付に縦置きで設置する
参加者の利用
- 受付に設置された端末の前に立つ
- 画面にはカメラプレビュー、セミナー名、ガイドメッセージ(例:「手のひらをかざしてください」)が表示されている
- フロントカメラに向かって手のひらをかざす
- カウントダウンが開始され、画面中央に大きな数字が表示される。画面の縁が点滅する
- カウントダウン終了時にフラッシュ演出とともに撮影が実行される
- 撮影後、設定されたプレビュー秒数の間プレビューが表示される
- 自動的に撮影待機画面に戻り、次の参加者の撮影が可能となる
撮影待機画面
撮影待機画面は、参加者が最初に目にする画面であり、アプリの主画面である。カメラプレビューが全画面に近いレイアウトで表示され、手のひら検出が有効な場合はリアルタイムに検出処理が実行されている。
画面構成要素
| 要素 | 位置 | 説明 |
|---|---|---|
| カメラプレビュー | 全画面 | フロントカメラの映像をリアルタイムで表示 |
| セミナー名 | 上部 | 設定画面で入力したセミナー名を表示(設定されている場合のみ) |
| ガイドメッセージ | プレビュー上 | 「手のひらをかざしてください」等のガイド文言を表示 |
| 歯車アイコン | 画面隅 | 設定画面へのアクセス用。タップするとPINコード入力画面を表示 |
| 撮影カウント | 歯車アイコンの下 | 現在のセッションにおける撮影枚数を数字のみで表示 |
| 手のひらマーク | 画面内 | 手のひらタイマーが有効な場合に、検出可能であることを示すアイコン |
| 警告メッセージ | 画面上 | アップロード先URL未設定時に「アップロード先が未設定です」と表示 |
手のひら検出とトリガー
検出の仕組み
FLASHではMediaPipe Handsを使用してリアルタイムに手のひらを検出する。MediaPipe HandsはGoogleが開発したブラウザ上で動作する手検出用機械学習モデルであり、手のひらの21個のランドマーク(関節点)をリアルタイムで検出できる。検出処理はすべてブラウザ内で完結し、映像フレームが外部サーバーへ送信されることはない。
モデルおよび必要なアセットの読み込みが完了するまではローディング表示が行われ、読み込み完了後に検出が開始される。Service Workerによるキャッシュにより、2回目以降のアクセスではモデルの読み込みが高速化される。
| 項目 | 仕様 |
|---|---|
| 検出モデル | MediaPipe Hands |
| ランドマーク数 | 21個(手の関節点) |
| 判定条件 | 21個のランドマークの位置関係から「手のひらが開いた状態」を判定 |
| 信頼度閾値 | 0.8以上(palm_conf_threshold) |
| 推論実行間隔 | 100ms目安(inference_interval_ms、動的調整あり) |
| 推論実行場所 | すべてブラウザ内(端末内完結) |
トリガー条件
手のひら検出が信頼度閾値以上で成立しても、単発の検出ではトリガーは成立しない。連続フレームでの検出成立(デフォルト5フレーム連続)が要求され、単発の誤検出を低減する。トリガー成立後、即座にカウントダウンが開始される。
| パラメータ | 値 | 説明 |
|---|---|---|
palm_conf_threshold |
0.8 | 手のひら検出の信頼度閾値 |
consecutive_frames_required |
5 | トリガー成立に必要な連続検出フレーム数 |
カウントダウンと撮影
カウントダウン演出
トリガー成立後、画面中央に大きな数字のオーバーレイが表示されカウントダウンが開始される。カウントダウン中は画面の縁を点滅させる演出が行われ、参加者にカウントダウン進行中であることを視覚的に伝える。
| 項目 | 仕様 |
|---|---|
| タイマー秒数 | 3秒 / 5秒 / 10秒(設定画面で選択) |
| カウントダウン表示 | 画面中央に大きな数字のオーバーレイ |
| 縁点滅演出 | カウントダウンに同期して画面の縁を点滅 |
| 中断・キャンセル | 不可。一度開始されたカウントダウンは必ず最後まで進行する |
撮影実行
カウントダウン終了時に画面フラッシュ演出が行われ、<canvas> キャプチャにより撮影が実行される。canvas.toBlob() によりJPEG画像データが生成される。ファイル名は {yyyyMMddHHmmss}_{ランダム英数字6文字}.jpg の形式でクライアントサイドで生成される。
| 項目 | 仕様 |
|---|---|
| 画像フォーマット | JPEG |
| 解像度 | 1920×1080程度 |
| 品質パラメータ | 0.92(image_quality) |
| ファイル名形式 | 20260214120000_a1b2c3.jpg |
プレビュー表示
撮影完了後、撮影画像がプレビュー表示される。プレビュー表示秒数は設定画面で変更可能であり、1秒/3秒/5秒/10秒から選択できる(デフォルト3秒)。プレビュー表示中にバックグラウンドでサーバーへの画像アップロードが開始される。
画像アップロード
アップロード処理
撮影した画像は、設定画面で指定されたアップロード先URLにHTTP POSTで送信される。POSTリクエストにはAPIキーが含まれ、サーバー側での認証に使用される。プレビュー表示中にアップロードが完了した場合、プレビュー表示終了後に撮影待機画面に自動遷移する。
| 項目 | 仕様 |
|---|---|
| 送信方式 | HTTP POST |
| 送信内容 | JPEG画像データ、APIキー、ファイル名 |
| タイムアウト | 10秒(upload_timeout_ms: 10000) |
| 最大リトライ回数 | 3回(upload_max_retries) |
リトライとエラー処理
アップロード失敗時は自動的にリトライが実行され、最大3回まで試行される。プレビュー表示中にアップロードが完了しなかった場合、プレビュー終了後にローディング表示に切り替わり、アップロード完了まで待機する。3回リトライしてもすべて失敗した場合は、エラーメッセージが表示され「スキップ」ボタンが提供される。スキップボタン押下で撮影待機画面に戻る。スキップした場合、撮影カウントはカウントアップされない。
+ アップロード開始
カウント+1
リトライ自動実行
+ スキップボタン
ローカル保存モード
設定画面で保存モードを「ローカル保存」に切り替えることができる。ローカル保存モードでは、撮影画像をダウンロードリンク(<a download>)により端末にダウンロード保存する。サーバーへのアップロードは行われない。ネットワーク障害時の代替手段として利用できる。
設定画面
設定画面は、撮影待機画面の隅に常時表示されている歯車アイコンからアクセスする。歯車アイコンをタップするとPINコード入力画面が表示され、正しいPINコードを入力した場合のみ設定画面に遷移する。
設定項目一覧
| 設定項目 | 説明 | 選択肢・形式 | デフォルト |
|---|---|---|---|
| 撮影設定 | |||
| 手のひらタイマー ON/OFF | 手のひら検出によるタイマー撮影機能の有効/無効 | ON / OFF | ON |
| タイマー秒数 | カウントダウンの秒数 | 3秒 / 5秒 / 10秒 | 3秒 |
| プレビュー秒数 | 撮影後のプレビュー表示時間 | 1秒 / 3秒 / 5秒 / 10秒 | 3秒 |
| 保存・アップロード設定 | |||
| 保存モード | サーバーアップロード / ローカル保存の切り替え | サーバーアップロード / ローカル保存 | サーバーアップロード |
| アップロード先URL | PHP受信スクリプトのURL | テキスト入力 | 空欄 |
| APIキー | サーバー認証用のAPIキー | テキスト入力 | 空欄 |
| 接続テストボタン | サーバーとの疎通確認 | ボタン | — |
| 表示設定 | |||
| セミナー名 | 撮影待機画面上部に表示するセミナー名 | テキスト入力 | 空欄(非表示) |
| ガイドメッセージ | 撮影待機画面に表示するガイド文言 | テキスト入力 | 手のひらをかざしてください |
| 管理 | |||
| PINコード変更 | 管理用PINコードの変更 | 数字4桁入力 | 初回設定時に指定 |
| カウントリセット | 撮影カウントをゼロに戻す | ボタン | — |
接続テスト
設定画面の「接続テスト」ボタンを押すと、設定されたアップロード先URLとAPIキーを使ってサーバーとの疎通確認が行われる。サーバー側でAPIキーの照合が成功すればテスト成功、認証失敗の場合はエラーが表示される。本番の撮影運用を開始する前に、必ず接続テストでサーバーとの通信が正常であることを確認することが推奨される。
PINコード管理
設定と変更
PINコードは数字4桁で構成される。初回アクセス時にはPINコード設定画面が自動表示され、PIN設定完了までは撮影画面に進むことができない。PINコードはlocalStorageに保存され、設定画面内で変更することも可能である。
ロックアウト
PINコード入力を10回連続で間違えた場合、10分間PINコード入力がロックアウトされる。ロックアウト中はロックアウト状態であることと残り時間が画面に表示される。
| 項目 | 仕様 |
|---|---|
| PIN桁数 | 数字4桁 |
| ロックアウト条件 | 10回連続失敗 |
| ロックアウト時間 | 10分間 |
| ロックアウト中の表示 | ロックアウト状態 + 残り時間 |
| 保存場所 | localStorage |
PINコードの回復
状態遷移
FLASHの画面状態は以下のように遷移する。主要な正常フローは S0 → S1 → S2 → S3 → S4 → S5 → S6 → S7 → S8 → S9 → S4(ループ)である。
| 状態 | 名称 | 説明 | 遷移先 |
|---|---|---|---|
| S0 | 初回セットアップ | 初回アクセス時。PINコード設定を行う | S1 |
| S1 | 設定画面 | 各種設定の変更が可能 | S2 |
| S2 | カメラ権限要求 | カメラアクセス権限を要求 | 許可 → S3 / 拒否 → S-E |
| S3 | モデル読み込み中 | MediaPipe Handsのモデルを読み込み。ローディング表示 | 完了 → S4 / 失敗 → S-E2 |
| S4 | 撮影待機(検出待機) | フレームごとに手のひら検出を実行 | 検出成立 → S5 / 歯車タップ → PIN入力 |
| S5 | トリガー成立 | 連続Nフレームで手のひら検出が成立 | S6 |
| S6 | カウントダウン中 | 視覚的カウントダウンと縁点滅。中断不可 | S7 |
| S7 | 撮影実行 | Canvasキャプチャにより画像生成。フラッシュ演出 | S8 |
| S8 | プレビュー表示・アップロード中 | プレビュー表示 + バックグラウンドアップロード | 完了 → S9 / 未完了 → S8-L |
| S8-L | アップロード待機 | ローディング表示。リトライ自動実行 | 完了 → S9 / 3回失敗 → S8-E |
| S8-E | アップロード失敗 | エラー表示 + スキップボタン | スキップ → S9(カウントアップなし) |
| S9 | クールダウン | 再トリガーを2秒間抑止 | S4 |
| S-E | カメラエラー | カメラ権限拒否。権限許可の案内を表示 | — |
| S-E2 | モデル読み込み失敗 | リトライUI。手動シャッターのみで利用可能 | S4(機能制限付き) |
エラー処理
| コード | エラー | 対処 |
|---|---|---|
| E-01 | カメラ権限拒否 | 機能が利用できない旨を表示し、ブラウザ設定から権限を許可する方法を案内 |
| E-02 | getUserMedia非対応ブラウザ | 非対応である旨を表示し、推奨ブラウザ(Android Chrome)への案内 |
| E-03 | MediaPipe Handsモデル読み込み失敗 | リトライUIを提供。手のひらタイマーは無効化し、手動シャッターのみで撮影可能 |
| E-04 | WebGL/WebAssembly非対応 | 手のひらタイマーは無効化し、手動シャッターのみで撮影可能 |
| E-05 | アップロード失敗(3回リトライ後) | エラーメッセージを表示し、スキップボタンを提供 |
| E-06 | カメラストリーム中断 | フォアグラウンド復帰時にストリームの再取得を試行 |
パラメータ一覧
以下はFLASHで調整可能なパラメータの一覧である。「設定画面」列が「可」のものは管理者が設定画面から変更できる。「不可」のものはコード内で定義されており、変更にはソースコードの編集が必要である。
| パラメータ名 | 説明 | 型 | 既定値 | 設定画面 |
|---|---|---|---|---|
| 検出・トリガー | ||||
palm_conf_threshold |
手のひら検出信頼度閾値 | float | 0.8 | 不可 |
consecutive_frames_required |
トリガー成立に必要な連続検出フレーム数 | int | 5 | 不可 |
palm_timer_enabled |
手のひらタイマー ON/OFF | bool | ON | 可 |
inference_interval_ms |
ML推論の実行間隔(ミリ秒) | int | 100 | 不可(動的調整) |
| 撮影 | ||||
timer_seconds |
タイマー秒数 | int | 3 | 可(3/5/10) |
cooldown_seconds |
クールダウン秒数 | float | 2.0 | 不可 |
image_quality |
JPEG画像品質(0.0〜1.0) | float | 0.92 | 不可 |
preview_seconds |
プレビュー表示秒数 | int | 3 | 可(1/3/5/10) |
| アップロード | ||||
save_mode |
保存モード | enum | SERVER | 可 |
upload_url |
アップロード先URL | string | 空欄 | 可 |
api_key |
サーバー認証用APIキー | string | 空欄 | 可 |
upload_timeout_ms |
アップロード1回あたりのタイムアウト | int | 10000 | 不可 |
upload_max_retries |
アップロード最大リトライ回数 | int | 3 | 不可 |
| 表示・管理 | ||||
seminar_name |
セミナー名表示 | string | 空欄 | 可 |
guide_message |
ガイドメッセージ | string | 手のひらをかざしてください | 可 |
pin_code |
管理用PINコード | string(4桁) | 初回設定時に指定 | 可 |
サーバーサイド仕様
サーバーサイドはXサーバー上に設置されるPHPスクリプトで構成される。画像アップロード用と接続テスト用の2つの機能を、いずれもPOSTリクエストで提供する。
画像アップロード処理
| 項目 | 仕様 |
|---|---|
| リクエスト方式 | HTTP POST |
| 受信データ | APIキー、JPEG画像データ、ファイル名 |
| 認証 | APIキー照合。不一致の場合はHTTP 403 |
| 保存先 | 受信日の日付フォルダ(例:20260214)に自動作成・保存 |
| ファイル名 | クライアントから送信されたファイル名をそのまま使用 |
| 成功レスポンス | HTTP 200 |
接続テスト処理
| 項目 | 仕様 |
|---|---|
| リクエスト方式 | HTTP POST |
| 受信データ | APIキー |
| 認証成功 | HTTP 200(テスト成功レスポンス) |
| 認証失敗 | HTTP 403(認証エラーレスポンス) |
フォルダ構造の例
├── 20260214/
│ ├── 20260214091523_a3f8k2.jpg
│ ├── 20260214091601_m9x2p7.jpg
│ └── 20260214091648_q1w4r8.jpg
└── 20260215/
├── 20260215100012_b7c3d9.jpg
└── ...
PWA対応
FLASHはPWA(Progressive Web App)として動作する。Web App Manifestを提供し、ホーム画面への追加時にアプリ名・アイコン・テーマカラーが適切に表示される。Service Workerを導入し、アプリシェル(HTML/CSS/JS/MediaPipe Handsモデル)をキャッシュすることで、2回目以降の起動が高速化される。
| 項目 | 仕様 |
|---|---|
| マニフェスト | アプリ名、アイコン、テーマカラー、表示モードを定義 |
| Service Worker | HTML/CSS/JS/MediaPipe Handsモデルをキャッシュ |
| オフライン時 | ローカル保存モードでの撮影は可能。サーバーアップロードモードではエラー表示 |
| ホーム画面追加 | アプリライクに起動。ブラウザUIなしで全画面表示可能 |
トラブルシューティング
カメラが起動しない
- ブラウザのカメラ権限が許可されているか確認する。Android Chromeの場合は「設定 → サイトの設定 → カメラ」から確認できる
- 他のアプリやブラウザタブがカメラを使用中でないか確認する
- 端末を再起動して再試行する
手のひらが検出されない
- 手のひらをカメラに向けて大きく開き、しっかりとフロントカメラに映るようにする
- 照明が十分であることを確認する。暗所や強い逆光の環境では検出精度が低下する場合がある
- 設定画面で「手のひらタイマー ON/OFF」がONになっているか確認する
- MediaPipe Handsモデルのローディングが完了しているか確認する(ローディング表示が消えているか)
誤検出が頻発する
- 背景に手のひらに似た模様(ポスター、絵画等)がある場合は、背景を変更するか端末の設置角度を調整する
- 連続フレーム検出(デフォルト5フレーム)により単発の誤検出は低減されるが、環境によっては改善が必要な場合がある
アップロードに失敗する
- 端末がインターネットに接続されているか確認する
- 設定画面の「接続テスト」ボタンでサーバーとの疎通を確認する
- アップロード先URLが正しく入力されているか確認する
- APIキーがサーバー側の設定と一致しているか確認する
- アップロードは自動的に3回までリトライされる。3回失敗後は「スキップ」で次の撮影に進める
- ネットワーク障害が続く場合は、設定画面で「ローカル保存」モードに切り替えて運用を継続する
画面がスリープする
- Wake Lock APIが動作しているか確認する。一部のブラウザバージョンではサポートされていない場合がある
- タブを切り替えたりバックグラウンドに移行するとWake Lockが解除される。フォアグラウンドに戻れば自動再取得される
- 端末のバッテリーセーバーモードが有効な場合、Wake Lockが制限される可能性がある
設定画面に入れない
- PINコードが正しいか確認する
- 10回連続でPINコードを間違えると10分間ロックアウトされる。ロックアウト中は残り時間が表示されるので、解除を待つ
- PINコードを忘れた場合は、ブラウザのサイトデータをクリアすることで全設定が初期化され、初回セットアップに戻る
モデルの読み込みが遅い・失敗する
- 初回アクセス時はMediaPipe Handsのモデルダウンロードが必要なため、安定したネットワーク環境で行う
- 2回目以降はService Workerのキャッシュにより高速に読み込まれる
- 読み込みに失敗した場合はリトライUIが提供される。手のひらタイマーは無効化されるが、手動シャッターで撮影は継続可能
「アップロード先が未設定です」の警告が表示される
- 保存モードが「サーバーアップロード」の場合に、アップロード先URLが設定されていないと表示される
- 歯車アイコンから設定画面に入り、アップロード先URLとAPIキーを設定する
- サーバーを使用しない場合は、保存モードを「ローカル保存」に変更すれば警告は消える
注意事項
- 対象ブラウザはモバイル版Chrome(Android)である。iOS Safari、デスクトップブラウザは対象外
- カメラはフロントカメラのみ対応。背面カメラでの撮影は非対応
- 端末は必ず縦向きに設置すること。横向きにするとレイアウトが崩れる
- カウントダウンは一度開始されると必ず最後まで進行する。中断・キャンセルの手段はない
- 手のひら検出を含むすべての映像処理はブラウザ内で完結し、映像フレームが外部サーバーに送信されることはない。送信されるのは撮影確定後のJPEG画像のみ
- localStorageには設定値およびPINコードのみが保存され、撮影画像データは永続保存されない
- 低性能なAndroid端末ではML推論によりプレビューのフレームレートが低下する場合がある。推論頻度は動的に調整されるが、著しく低性能な端末では手のひらタイマーをOFFにし手動シャッターでの運用を検討すること
- 長時間の連続運用(100名以上の撮影等)では、端末の発熱やバッテリー消耗に注意すること。充電ケーブルを接続した状態での運用を推奨
- PINコードを忘れた場合の回復手段はブラウザのサイトデータクリア(全設定初期化)のみ。PINコードは安全な場所に控えておくこと
- アプリが使用するサーバーへのアップロード先は管理者が設定画面で明示的に指定したURLに限定される。意図しない送信先へのアップロードは行われない
- 本アプリは顔認証処理を行わない。撮影された画像の照合・認証は別システムで行う必要がある