Vol.10
人物マスクで前後関係を作る — Selfie Segmentation を Hands と同時に起動してはいけない
使っている場所
- 屍鬼封尽(useShikifujinSession): 人物のシルエットで死神(shinigami.glb、6.3MB)を隠し、死神が背後に立っているように見せる。
- Hand Occlusion(vision/handOcclusionMask.ts): Call Studio の螺旋丸などで、手が VFX の前に出たときに VFX が隠れるようにする。
人物合成が要らない術では、Segmentation のスクリプト自体を読み込みません。読み込むときも window.SelfieSegmentation と script[data-mediapipe-selfie-segmentation] を確認して、二重に挿入しないようにしています。
ハマった点: 同時に初期化すると WASM Module が衝突する
MediaPipe の旧ソリューション(Hands / Pose / Selfie Segmentation)を同時に初期化すると、内部の WASM Module が衝突します。屍鬼封尽は印認識の Hands、人の位置を取る Pose、遮蔽用の Segmentation の 3 つを使うので、Pose は handsReady を、Segmentation は poseReady を待ってから起動する、という 3 段の直列にしています。コード内のコメントにも「読み込み順を直列化するだけで、機能的な依存はない」と書いてあり、順番そのものに意味はなく、同時に走らせないことだけが目的です。
Hand Occlusion は、この経験を最初から設計に入れています。tick() の第 3 引数 handsSettled が true になる(Hands の読み込みが完了した、またはそもそも無効)まで、スクリプトの読み込みもモデルの初期化も始めません。
1/**2 * 重要: MediaPipe は Hands と Selfie Segmentation を同時に初期化すると内部 WASM Module が3 * 衝突する(useShikifujinSession.ts が poseReady を待ってから Segmentation を起動している4 * のと同じ理由)。そのため tick() の第3引数 handsSettled が true になるまでは5 * スクリプト読み込み/モデル初期化そのものを開始しない。6 */全身マスクを手の遮蔽に流用する
- メリット
- 指の輪郭まで正確に切れる可能性がある。
- デメリット
- モデルを探す・学習する・配信するコストが増える。Hands と Segmentation に加えて 3 つ目の推論が走る。
- メリット
- 新しい基盤を作らない。手が VFX の前に出たとき隠れて見えれば十分、という要件には足りる。
- デメリット
- 人がいる画素が全部マスクに入るので、それだけでは「手」と「体」の前後を区別できない。
採用理由: 前後関係(どこが VFX より手前か)はランドマークから作る depth proxy に任せ、マスクは「人がいる画素」を限定する 2D のゲートとしてだけ使う。役割を分ければ、全身マスクの粗さは問題にならない。
マスクの処理は品質ごとに変えています。60fps の描画ループに対して、low / standard は 4 フレームに 1 回、high / ultra は 2 フレームに 1 回だけ推論に送ります(15〜30fps 相当)。結果は 128〜256px の低解像度キャンバスに縮小して 3〜5px の blur でエッジをぼかし、前の結果に globalAlpha(0.5〜0.35)で重ねて時間方向にも平滑化します。ピクセルの読み戻し(getImageData)は一切しないので、OBS に届くまでの遅延も増えません。
実装から得た知見
- MediaPipe の旧ソリューションは 1 つずつ初期化する。同時に起動すると内部の WASM Module が衝突する。新しい組み合わせを足すたびに同じ問題が出るので、設計段階で「前のものの準備完了を待つ」引数を用意しておく。
- マスクに前後関係まで背負わせない。奥行きはランドマーク、マスクは「人がいるか」の 2D ゲート、と役割を分けると全身マスクで足りた。
- OBS など外部に映像を渡す用途では、getImageData のようなピクセルの読み戻しを避ける。マスクの平滑化もキャンバスの合成だけで済ませられる。