はじめに
みなさんはバイブコーディングするとき、音声入力はお使いでしょうか?
私はAqua Voiceを使って音声入力してます(月額1600円かかるけどもう手放せなくなってしまった)
Aqua Voiceはショートカットキーを押して起動するのですが、正直これすらめんどい。
せっかく音声入力で手を動かす必要がなくなったのに、音声入力するために手を使うっていう矛盾。
私はいつもMacBookを使っているんですが、MacBookに限らず、ノートPCにはウェブカメラが付いてますよね。ビデオ会議とかなければこのウェブカメラってあんまり使う機会がないので個人的にずっともったいないなと思っていて、せっかくかなり優秀なセンサーなのでなにかしらに使いたいな、なんて思う日々(1日)。
閃いた
カメラを使って自分の顔で音声入力のトリガーを制御すれば、完全にハンズフリーでできるじゃん。
ということでつくったのが本システムです(Aqua VoiceにちなんでAqua Faceと名付けました)。
- 口を開ける → Aqua Voice の録音開始(Fn 押下)
- ゆっくりまばたき → 録音停止(Fn 解放)
- スマイル → Enter 送信(文字起こし結果を確定 & 承認も兼ねる)
これで音声入力からEnter押下の承認までを顔面だけで完結させることができます。
セットアップ
git clone https://github.com/tacosl4ver/aqua-face.git
cd aqua-face
bash install.sh
Python 3.10 以上、macOS が必要。初回起動時にカメラのアクセス許可が求められる。
使い方
# チューニング用(スライダー UI あり)
python3 face_trigger.py
# 普段使い(バックグラウンド)
python3 face_trigger.py --headless
- 口を開ける → 録音開始(Aqua Voice が Fn キーを検知)
- 話す
- ゆっくりまばたき → 録音停止
- スマイル → Enter 送信(Aqua Voice の文字起こしを確定)
技術スタック
| 役割 | 使用技術 |
|---|---|
| 顔ランドマーク検出 | MediaPipe FaceLandmarker(blendshapes) |
| カメラ映像取得 | OpenCV |
| Fn キー送信 | PyObjC Quartz(CGEvent) |
| Enter キー送信 | pynput |
| CPU 使用率表示 | psutil |
macOS 専用。M1 以降推奨。
仕組みの解説
1. 表情の取得:MediaPipe blendshapes
MediaPipe の FaceLandmarker には blendshapes という機能があり、顔の 52 種類の「変形スコア」を 0〜1 のフロートで返してくれる。今回使うのはこの 3 つ。
| blendshape 名 | 意味 |
|---|---|
jawOpen |
口の開き具合 |
eyeBlinkLeft / eyeBlinkRight
|
左右それぞれの目の閉じ具合 |
mouthSmileLeft / mouthSmileRight
|
左右それぞれの口角の上がり |
def get_blendshape(blendshapes, name: str) -> float:
for b in blendshapes:
if b.category_name == name:
return b.score
return 0.0
jaw = get_blendshape(bs, "jawOpen")
blink = (get_blendshape(bs, "eyeBlinkLeft") +
get_blendshape(bs, "eyeBlinkRight")) / 2
smile = (get_blendshape(bs, "mouthSmileLeft") +
get_blendshape(bs, "mouthSmileRight")) / 2
単純な閾値比較で判定できるので、モデルの精度が高ければロジック自体はシンプルに書ける。
2. Fn キーの送信がひとクセある
一番ハマったのがここ。Fn キーは通常の keydown / keyup ではなく、FlagsChanged イベント として macOS に送る必要がある。pynput では送れないため、Quartz の CGEvent を直接使った。
_FN_KEYCODE = 63
_FN_FLAG = Quartz.kCGEventFlagMaskSecondaryFn # 0x800000
def _fn_event(pressed: bool):
src = Quartz.CGEventSourceCreate(Quartz.kCGEventSourceStateHIDSystemState)
event = Quartz.CGEventCreateKeyboardEvent(src, _FN_KEYCODE, pressed)
Quartz.CGEventSetType(event, Quartz.kCGEventFlagsChanged)
Quartz.CGEventSetFlags(event, _FN_FLAG if pressed else 0)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
kCGHIDEventTap に投稿することで、OS レベルで Fn キーの押下として認識される。これによって Aqua Voice が正しくトリガーされる。
3. ステートマシンで誤検知を防ぐ
「口を開けたまままばたきしてしまった」「録音前にスマイルが検出された」といった誤作動を防ぐため、4 つの状態を持つステートマシンで管理している。
IDLE → (口を開ける) → RECORD → (ゆっくりまばたき) → COOLDOWN → READY → (スマイル) → IDLE
| 状態 | 意味 |
|---|---|
IDLE |
待機中。Enter トリガーは無効 |
RECORD |
録音中(Fn キー押下中) |
COOLDOWN |
Aqua Voice が文字起こし処理中。Enter トリガーは無効 |
READY |
次の発話 or Enter 待ち。スマイルで Enter を送信できる |
COOLDOWN を挟むことで、録音直後にスマイル判定が暴発するのを防いでいる。
4. まばたきのアダプティブ閾値
固定の閾値だと、部屋の明るさや顔の向きによって精度が変わってしまう。そこで、目が開いているときのスコアを動的にベースラインとして更新し、「ベースライン + delta 以上 = 目閉じ」と判定する方式にした。
# 目が開いている時だけベースラインを更新
if blink < ear_baseline + blink_delta * 0.5:
ear_baseline = (1 - EAR_BASELINE_ALPHA) * ear_baseline + EAR_BASELINE_ALPHA * blink
ear_baseline = min(ear_baseline, 0.35)
指数移動平均(EMA)でゆっくり更新することで、急な動きには反応せず、長期的な環境変化にだけ追従する。
また、口を開けた直後はまばたき判定を一時的に無効にする(JAW_BLINK_GUARD = 0.25s)ことで、発話開始時の誤検知も防いでいる。
5. リアルタイムでチューニングできるスライダー UI
プレビューモードで起動すると、カメラ映像の下にスライダーパネルが表示される。
python3 face_trigger.py
| パラメータ | デフォルト | 説明 |
|---|---|---|
| JAW | 0.22 | 口の開き閾値 |
| BLINK_DELTA | 0.28 | まばたき感度 |
| BLINK_HOLD | 0.30s | スローブリンク判定秒数 |
| SMILE | 0.12 | スマイル閾値 |
ドラッグするだけでリアルタイムに反映され、終了時に settings.json へ自動保存される。次回起動時に自動で読み込まれる。
6. 安全策:Enter を送信するアプリを限定する
スマイルで Enter が送信されるので、ブラウザやメールなど意図しないアプリで送信されると困る。そこで 許可アプリリスト(allowed_apps)を設けた。
{
"allowed_apps": ["Cursor", "Terminal", "iTerm2", "Code", "Visual Studio Code"]
}
フロントに来ているアプリが許可リスト外なら Enter を送信せずスキップする。Quartz の CGWindowListCopyWindowInfo でフロントウィンドウのアプリ名を取得している。
7. CPU 負荷の最適化
MediaPipe の推論はそれなりに重い。以下の工夫で常用レベルに落とした。
-
フレームスキップ(
FRAME_SKIP = 2): 1 フレームおきにしか推論しない(実質 15fps 相当) - 解像度を落とす(320×240): 精度への影響はほぼなし
-
ヘッドレスモード(
--headless): OpenCV の描画コストをゼロにする
バックグラウンドで常駐させるなら --headless が推奨。
感想
作り始める前は実用レベルまでは無理だろうなって思ってたんですけど、実際に使ってみると意外と実用的だったのでびっくり。
ちなみに、自分は一行もコードを書いてなくて、Claude Codeに全部書かせました。
依存ライブラリ
| ライブラリ | ライセンス | 用途 |
|---|---|---|
| MediaPipe | Apache 2.0 | 顔ランドマーク検出 |
| OpenCV | Apache 2.0 | カメラ映像処理 |
| pynput | LGPL v3 | Enter キー送信 |
| PyObjC Quartz | MIT | Fn キー送信(CGEvent) |
| psutil | BSD 3-Clause | CPU 使用率表示 |
このプロジェクトは Claude Code(Anthropic)を使って開発しました。