0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【顔面バイブコーディング】顔 × Aqua Voiceで完全ハンズフリーなバイブコーディングしてみた。

0
Last updated at Posted at 2026-06-06

はじめに

みなさんはバイブコーディングするとき、音声入力はお使いでしょうか?
私は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
  1. 口を開ける → 録音開始(Aqua Voice が Fn キーを検知)
  2. 話す
  3. ゆっくりまばたき → 録音停止
  4. スマイル → 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)を使って開発しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?