Slide 1

Slide 1 text

MediaPipe Face Landmarkerの 精度限界にどう立ち向かうか ランドマーク幾何学による補正の実践 RIO · @rioX432 · AnotherBall / Avvy

Slide 2

Slide 2 text

Self Introduction RIO (Ryosuke Shimizu) @rioX432 AnotherBall・Avvy Android・iOS・Kotlin Multiplatform 2

Slide 3

Slide 3 text

誰でもスマホ一つで2Dアバターを作成でき 顔出しなし・機材なしでライブ配信を始められる VTuberアバター作成・配信アプリ 3

Slide 4

Slide 4 text

Android/iOSクライアントは、KMPとUnityを共用する KMP=AndroidとiOSでKotlinのコードを共有する仕組み。 Unity=アバターを描くゲームエンジン 4

Slide 5

Slide 5 text

Unityの描画に、ネイティブUIを重ねる 5

Slide 6

Slide 6 text

Avvyには、頬をふくらませる表情がある ふつう 頬をふくらませたとき 6

Slide 7

Slide 7 text

頬をふくらませる「ぷく顔」は、イラストやVTuberで 定番の表情 ピクシブ百科事典「ぷくおこ(ぷくおこ)」 7

Slide 8

Slide 8 text

頬をふくらませても、Androidでは動かない Android iOS 8

Slide 9

Slide 9 text

ユーザーからは、ぷく顔が求められている サポートへ届いた問い合わせ 「Androidでは 難しい理由が あるのでしょうか」 iOSとAndroidで 差があると続く サポートへ届いた問い合わせ 「ほっぺを膨らませる ことができないです」 9

Slide 10

Slide 10 text

今日は、この課題を原因からアプローチまで説明 01 02 03 04 05 Avvyのフェイストラッキングのしくみと工夫 MediaPipeとは なぜ、Androidでは頬がふくらまないのか 数値がだめなら、顔の点の位置から作れないか 4つのOSのフェイストラッキングを、一つの共通SDKへ 10

Slide 11

Slide 11 text

01 Avvyのフェイストラッキング しくみと工夫 01 しくみ 02 MediaPipe 03 なぜ 04 点から作る 05 端末差 06 共通SDK 11

Slide 12

Slide 12 text

表情を推定するところが、AndroidとiOSで違う 表情を推定 カメラ 前面カメラ Android → MediaPipe iOS → KMP 共通の型へ Unity → アバターへ渡す ARKit MediaPipeはGoogle、ARKitはAppleの、 顔の動きを数値にする技術。MediaPipeは章02で詳しく見る 12

Slide 13

Slide 13 text

MediaPipeもARKitも、表情を blendshape で返す 名前 顔のどこが、どう動いたか eyeBlinkLeft 左目を閉じた eyeBlinkRight 右目を閉じた jawOpen あごを下げて口を開けた mouthPucker 口をすぼめた cheekPuff 頬をふくらませた 強さはどれも 0.0(動かしていない)〜 1.0(いちばん大きく動かした)。 以後、この値を「係数」と呼ぶ。 MediaPipe ソース / Apple 公式ドキュメント 13

Slide 14

Slide 14 text

cheekPuffは、頬のふくらみの強さを表す係数 14

Slide 15

Slide 15 text

フェイストラッキングは、5段階の処理でできている 15

Slide 16

Slide 16 text

処理が追いつかないときは、最新の1枚だけ使う // Android — 遅れたフレームを溜めない ImageAnalysis.Builder() .setBackpressureStrategy( ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST ) .build() 処理が追いつかなければ古いフレームを捨てる 16

Slide 17

Slide 17 text

iOSは最も低消費電力の形式を選び、顔は1つだけ追う configuration.isLightEstimationEnabled = false configuration.maximumNumberOfTrackedFaces = 1 if let lowestPowerVideoFormat = ARFaceTrackingConfiguration .supportedVideoFormats.last { configuration.videoFormat = lowestPowerVideoFormat } 使わない機能は切り、 対応形式の中でいちばん省電力のものを選ぶ 17

Slide 18

Slide 18 text

Androidは向きを戻し、左右反転してから推論にかける 18

Slide 19

Slide 19 text

回転と左右反転は、Matrixの2行で指定する transformMatrix.apply { // カメラの向きを戻す postRotate(rotationDegrees.toFloat()) if (isFrontCamera) { postScale(-1f, 1f) // 鏡と同じ向きへ } } 自前のMatrix。反転は前面カメラの経路だけ 19

Slide 20

Slide 20 text

Unityとの通信は、2系統に分けている 20

Slide 21

Slide 21 text

顔を動かす43個の値は、共有メモリに続けて置く // Make sure to have same value in native side // Android: .../AvatarTrackingDataInteropBridge.kt // iOS: .../AvatarTrackingDataInteropBridge.swift // AVATAR DATA public const int FACE_TRACKING_DATA_SIZE = 43 * sizeof(float); public const int CAMERA_DATA_SIZE = 3 * sizeof(float); public const int STRIDE = FACE_TRACKING_DATA_SIZE + CAMERA_DATA_SIZE; 表情37・頭の向き3・頭の位置3。 各実装の並びは、コメントを手掛かりに人がそろえている 21

Slide 22

Slide 22 text

Unity側は、届いた値を受信間隔で補間してから整える 22

Slide 23

Slide 23 text

アバターの頬は、2種類の係数を1本の式で動かす var val = Mathf.Clamp01( // 左の頬 (r.CheekSquintLeft * 係数) + r.CheekPuff); ... var val = Mathf.Clamp01( // 右の頬 (r.CheekSquintRight * 係数) + r.CheekPuff); cheekSquint : 目の周りと下の頬が上へ動く度合い) cheekPuff : 両頬が外へ動く度合い cheekPuffは左右の式に同じ値が入り、 上がれば両頬が同時に動く 23

Slide 24

Slide 24 text

実機計測では、75秒間の送信を毎秒30回で維持した 推論 共有メモリ書き込み 送信 1枚あたり 21〜22ms 1回あたり 約30µs 毎秒30枚・ 取りこぼし0 中央値 中央値 顔を映した75秒×3本 Galaxy S25 Ultraでの実測。 熱でGPUクロックが絞られても、この30fpsは落ちなかった 24

Slide 25

Slide 25 text

反映周期は、iOSは固定、Androidは動的に変える iOSの60はループへ渡す設定値。 Androidは熱やスペックに応じて動的に切り替えている。 推定の回数ではない 25

Slide 26

Slide 26 text

周期を決めるのは、端末区分と熱状態の表 DeviceTier.HIGH to mapOf( ThermalState.SAFE to 30, ThermalState.WARNING to 24, ThermalState.STRESS to 15, ThermalState.CRITICAL to 10, ) HIGH区分の抜粋。熱を4段階に分け、端末区分とFPSの組み合わせて選ぶ android.os.PowerManagerを使い、端末の熱状態を判定。 OSバージョンで処理分け。 26

Slide 27

Slide 27 text

端末の格付けは、OSの申告を先に見る if (activityManager.isLowRamDevice) return DeviceTier.LOW // Android 12+ — OSが申告する性能クラスを先に見る val mpc = Build.VERSION.MEDIA_PERFORMANCE_CLASS if (mpc >= 33) return DeviceTier.HIGH if (mpc in 31..32) return DeviceTier.MEDIUM // 申告が無い端末だけ、コア数とRAMで決める if (cores >= 8 && ramGb >= 8.0) return DeviceTier.HIGH Android 12 以降は Build.VERSION.MEDIA_PERFORMANCE_CLASS を見る。 27

Slide 28

Slide 28 text

端末ごとに変えるのは、推論ではなく アバターへの反映周期 変えない 推論のモデルと 推論の頻度 カメラの画像が届くたびに推論は続く 変える 結果を読み出して アバターへ送る周期 30 → 24 → 15 → 10 28

Slide 29

Slide 29 text

02 MediaPipeとは 01 しくみ 02 MediaPipe 03 なぜ 04 点から作る 05 端末差 06 共通SDK 29

Slide 30

Slide 30 text

MediaPipeは、端末の上で動く機械学習の部品集 出しているのは Google コードが公開されている OSS(Apache-2.0) どこで動く 端末の上 カメラの映像を、 端末から出さずに その場で処理 MediaPipe 公式ドキュメント・GitHub(Google) いつから 2019年から 公開が続いている フレームワーク 30

Slide 31

Slide 31 text

MediaPipeは、ソースが公開され、開発が続くOSS GitHub google-ai-edge/mediapipe(公式リポジトリの画面) 31

Slide 32

Slide 32 text

顔も手も体も、返ってくるのは点 顔は478点 手は21点 体は33点 32

Slide 33

Slide 33 text

iOSのARKitは、Appleが端末ごと設計している MediaPipe オープンで、 どの環境でも動く モデルの中身も公開 ARKit Apple端末専用。 センサーから係数まで 一続き 中身は公開されていない 端末から係数・アニメーションAPIまで、1社が一続きで設計している 33

Slide 34

Slide 34 text

表情の係数まで返すのは、Face Landmarker ARCore Augmented Faces 姿勢と 468点の メッシュ ML Kit Face Mesh 468点の メッシュ 表情の分類は返さない (公式の比較表) MediaPipe Face Landmarker 478点と 52個の係数 点に加えて、表情の係数 まで返す 34

Slide 35

Slide 35 text

他社もAndroidでは、まずMediaPipeを検討する 採用して運用している例 MediaPipeベースの 内製で本番運用 相性の悪い機種には軽い別実装を残す 自社開発を選んだ例 検出が不安定として 自社モデルを開発 MediaPipeは 評価の基準に置かれている 35

Slide 36

Slide 36 text

Face Landmarkerは、顔の点と表情の係数と向きを返す MediaPipe 公式ドキュメント / Java Tasks API リファレンス 36

Slide 37

Slide 37 text

52個の係数は、画像ではなく478点から出ている 37

Slide 38

Slide 38 text

03 なぜ、Androidでは 頬がふくらまないのか 01 しくみ 02 MediaPipe 03 なぜ 04 点から作る 05 端末差 06 共通SDK 38

Slide 39

Slide 39 text

係数の名前が同じなら、共通に扱えるはずだった MediaPipe Android → 同じ名前の 51個 共通のコードで 扱えそうだ ← ARKit iOS 51個は、MediaPipeの52個から_neutralを除いた、ARKitと名前まで共通の 部分。この見立てが、どこまで通用するのかをこの章で確かめる 39

Slide 40

Slide 40 text

頬がふくらまない報告は、3年前から続いている 報告 報告 2023-03 MediaPipe Issue → 2023-05 cheekPuffが ほぼ0のまま google-ai-edge/mediapipe Issue #4436 / #5329 まだOpen → いま ラベルは Google側の回答待ち 40

Slide 41

Slide 41 text

Googleは「有効にしていない」と答えた 2日後には「BlendShapeV2はまだ完成していない」とも回答している google-ai-edge/mediapipe Issue #4436(実際の画面) 41

Slide 42

Slide 42 text

他社のツールも、同じ制限として案内している ユーザーからの質問 “I cannot puff my cheeks” ほっぺをふくらませられない Warudo 公式ドキュメント(MediaPipe のページ) ツール側の回答 “a known limitation of MediaPipe” MediaPipeで知られている制限。 Googleの修正待ち、と続く 42

Slide 43

Slide 43 text

ARKitとMediaPipeの出力を、同じ時刻で対応づけた iPhoneで収録 ARKitを動かしながら 画像も保存する → ARKitの出力 その場で記録 → 保存した画像を MediaPipeへ あとから通す 同じ瞬間の記録同士を対応づけた比較。 2つに同じ画像を入力した実験ではない 43

Slide 44

Slide 44 text

ふくらませた区間で、動いたのはARKitだけ この収録で、同じ時刻の出力として並べた結果 44

Slide 45

Slide 45 text

MediaPipeで、動く係数と動かない係数が分かれた 上がった あご・笑顔・ 目の見開き ゼロ近くのまま 最初から天井 頬・鼻しわ 口すぼめ 2回の別の収録でも、動かない側は同じ。 ARKitは比較の基準で、正解値ではない 45

Slide 46

Slide 46 text

アバターの頬を動かす入力は、3つとも動かない側 cheekPuffと 左右の cheekSquint 1本の式へ → 足し合わせて 頬の位置にする 頬はほぼ動かない → 見えないほど わずか 3つの入力 46

Slide 47

Slide 47 text

顔には、478個の点が打たれる MediaPipe公式の顔モデル(468点)から描き起こした。虹彩の10点を加えて478点 47

Slide 48

Slide 48 text

52個の係数を作るのに使うのは、146点だけ MediaPipe 公式ドキュメントのモデル表(入力形状 1 × 146 × 2) 48

Slide 49

Slide 49 text

146個の点は、ソースに配列で書かれている static constexpr std::array kLandmarksSubsetIdxs = { 0, 1, 4, 5, 6, 7, 8, 10, 13, 14, 17, 21, 33, 37, 39, 40, 46, 52, ... }; この146個の、横と縦だけを渡す。奥行きは渡さない google-ai-edge/mediapipe face_blendshapes_graph.cc(Apache-2.0) 49

Slide 50

Slide 50 text

その146点に、頬の表面を示す点は含まれない 50

Slide 51

Slide 51 text

頬をふくらませたとき、動いたのは唇の係数だった 上がるのは「上下のくちびるを巻き込んだ度」と 「口が横に寄った度」。頬の項目は0.0のまま 51

Slide 52

Slide 52 text

頬の点を足せば直る、という単純な話ではない 論文に書いてあること 同じ顔を作る係数の 組み合わせは、 1つではない 別の組み合わせでも 同じ顔ができる Grishchenko et al., arXiv:2309.05782 学習のしかた 係数から作った顔が 点と合うかも見て学習する 係数の誤差だけを見ていない 52

Slide 53

Slide 53 text

cheekPuffは、制限なしなら0.9999まで上がる 点の位置を計算で動かして、 いちばん上がる形を探させると 0.9999 ただし、そのとき点は 人の顔の形をしていない 53

Slide 54

Slide 54 text

実際に記録した顔の動きの範囲では、0.0443止まり 同じ探索を、記録した顔の 動きの範囲に縛ると 0.0443 しきい値 0.1 に届かない。 計算で探せる上限で、実測値ではない 0.1は「ふくらんだ」と判定するしきい値。 同じ縛りで、あごの開きは0.9787まで上がる 54

Slide 55

Slide 55 text

倍率としきい値の調整だけでは、頬と笑顔を 見分けられない どれも「頬をふくらませた時」の値。大小が逆さまなので、 何倍にしても、しきい値を下げても、頬より先に笑顔が届く 55

Slide 56

Slide 56 text

cheekPuffは出力されるが、実際の顔の動きでは しきい値まで上がらない 反応 経路 係数は そのまま届く 途中で増幅も減衰もない 実際の範囲では しきい値に 届かない 0.1で「ふくらんだ」と 判定 つぎ 点はいつも 返ってくる 係数がだめなら、点から 56

Slide 57

Slide 57 text

04 係数(BlendShapes)がだめなら、 点から作れないか 01 しくみ 02 MediaPipe 03 なぜ 04 点から作る 05 端末差 06 共通SDK 57

Slide 58

Slide 58 text

係数がだめなら、選べる方法は3つ 道1 係数を直す 倍率やしきい値を 変える。 3章で見たとおり 大小が逆で解けない 道2 道3 点から作る 学習で作る 478点から 頬の動きを測る。 この章の主役 点や見た目から 係数を推定する。 この章の後半で触れる 58

Slide 59

Slide 59 text

係数が動かなくても、点はいつも返ってくる 478点 設定に関係なく いつも返る → 146点 → 52個の係数 頬はゼロ近くのまま だから、係数(BlendShape)は使わない、 その手前にある点から頬を幾何学的に判定することにした 59

Slide 60

Slide 60 text

頬だけに反応する測り方を、478点から探した 絞る線は2つ。①ふくらませている間は反応する。 ②ほかの動きでは反応しない 60

Slide 61

Slide 61 text

23万通りを試して、条件を満たすものは無かった 試した組み合わせ 234,136 113個から3つを選ぶ 2つとも満たした数 0 ①は満たせる。 ②が満たせない 頬周辺を測る方法を113個作り、3個ずつ組み合わせて総当たりした 61

Slide 62

Slide 62 text

いちばん惜しいものでも、口をすぼめると反応した ふくらませていないのに ふくらませたとき おおむね反応する 取りこぼしもある 口すぼめ・息吹き・ 鼻すぼめでも反応する 頬以外の動きと 区別がつかない 小鼻・頬の内側・口幅の組み合わせは、 頬だけの信号にならない、という結果になる 62

Slide 63

Slide 63 text

頬から離れて、顔のいちばん外側の線を見た 外周の点は係数モデルも受け取っている。ただ、 その動きはcheekPuffへ反映されない。だから点を直接読む 63

Slide 64

Slide 64 text

頬をふくらませたときだけ、下顎の輪郭が外へ動いた 数字は、目の間隔を100としたときの外への出方。 この収録では、ふくらませたときだけプラスになった 64

Slide 65

Slide 65 text

3つの条件を組み合わせて、規則にした 65

Slide 66

Slide 66 text

係数で判定はできないが、幾何ソルバーで判定は可能 係数では判定できない 幾何ソルバーによる判定はできる 66

Slide 67

Slide 67 text

3つの条件は、共通側のKotlinに入っている const val MOUTH_WIDTH_MAX = 3.02 const val OVAL_JAW_MIN = 1.54 const val OVAL_CHIN_MAX = 2.68 val fired = mouthWidth <= MOUTH_WIDTH_MAX && ovalJaw >= OVAL_JAW_MIN && ovalChin <= OVAL_CHIN_MAX 単位は目の間隔を100とした変化。共通SDKの合成経路へつなぐのはこれから 67

Slide 68

Slide 68 text

しきい値も時間の区切りも、撮る前に固定した しきい値 成績が変わらない 区間の真ん中に置く 成績がいちばん 良くなる値は選ばない。 同じデータで選ぶと、 そのデータ用の値になる 時間の区切り 4秒間のうち 半分以上で「検出」 基準の無表情を取り、 両端0.5秒は数えない。 撮影の前に決めて固定した 68

Slide 69

Slide 69 text

この収録では、頬を膨らませた7本すべてに反応し、 ほかの11本には反応しなかった 頬をふくらませた 7本 すべてで検出 反応したコマは8割以上 ふくらませていない 11本 誤作動ゼロ 息吹き・口すぼめ・笑顔・発話・ 無表情で一度も反応しない 採点したのは、規則を作ったのと同じ収録の18本。 片頬2本と首振り2本は別扱い 69

Slide 70

Slide 70 text

頬をふくらませた 7本とは 頬を膨らませた7本の動画の内訳は以下です。 ・軽く膨らませた動画✖2 ・中位に膨らませた動画✖2 ・最大限に膨らませた動画✖3 頬を最大限膨らませたもの以外でも判定できるよう実験 70

Slide 71

Slide 71 text

同じデータで測った成績は、性能ではない 22本の収録で 規則を作る → 同じ収録で 成績を出す → 別の収録で 1回だけ採点 ここでも 問題なく判定できた 71

Slide 72

Slide 72 text

検証用の規則は実装済み。 次は本番アプリにも導入し検証する 規則があり、 取れた収録がある 何も出ない これまで Androidの頬は ずっとゼロ → いま 取れない収録との差は 候補2つまで絞った 撮り直して 1回だけ採点 → これから 本番アプリへの 統合および検証は 72

Slide 73

Slide 73 text

別案として、478点すべてを学習させた 渡したもの 1本ずつ外して採点 478点の座標 そのまま 順番は 正しく並んだ 人が3つ選ぶ代わりに、 選ばずに全部渡す ふくらませた7本が、 ほかの11本より上 先に決めた合格ライン 越えたのは 7本中2本 誤作動は0本 時刻と順番だけ、顔の姿勢だけ、ラベルを5通り入れ替えた対照は、 どれもこれより低い 73

Slide 74

Slide 74 text

学習でも、頬と口の動きを分けられなかった 頬の見た目から学習 同じ収録では 正例6本すべてを分離 ただし、口だけを見る対照も 同じように分離した 点からARKitの反応を学習 製品条件の収録では 話しているだけで 反応し続けた ふくらませていない区間も しきい値を超えたまま 頬の小画像には口の領域が4割近く重なる。 学習に入れていない動きや条件では、頬だけを取り出せない 74

Slide 75

Slide 75 text

ぷく顔は、3つの状態で受け渡す設計案 立ち上がり 保持 ふくらませたら 出る → ふくらませてい → る間、出続ける 解除 やめたら戻る アバター側 → なめらかに 動かす 連続値を待たず、状態で渡す。なめらかさはアバター側の補間が作る。 75

Slide 76

Slide 76 text

05 4つの実装を、一つのFaceTrackerの 実装へ集める 01 しくみ 02 MediaPipe 03 なぜ 04 点から作る 05 端末差 06 共通SDK 76

Slide 77

Slide 77 text

同じ約束に集めれば、AndroidとiOS以外の環境も乗る いまのAvvy 共通SDK AndroidとiOSの 2つ 一つのFaceTrackerの 約束に集める 同じ並びを 人がそろえている Android・iOS・ Web・Desktop の4つ 共通SDKは、Avvyの顔トラッキングから共通部分を抜き出したもの。 KMPを使う利点。 77

Slide 78

Slide 78 text

4つの環境のフェイストラッキングを、一つに集める 78

Slide 79

Slide 79 text

OSごとに残すのは、カメラ・顔の推定・詰め替えまで 79

Slide 80

Slide 80 text

共通側は「FaceTrackerを作る」と宣言するだけ // commonMain public expect fun createFaceTracker( platformContext: PlatformContext, config: FaceTrackerConfig = FaceTrackerConfig(), ): FaceTracker expectは「ここはOSごとに用意する」で 各プラットフォームのインターフェースを定義。 ビルドする環境に合わせて、 Kotlinのコンパイラがその環境の実装(actual)を選ぶ 80

Slide 81

Slide 81 text

環境に合わせて、コンパイラが4つの実装から選ぶ // androidMain public actual fun createFaceTracker(...): FaceTracker = MediaPipeFaceTracker(platformContext, config) // iosMain public actual fun createFaceTracker(...): FaceTracker = ARKitFaceTracker(config) // wasmJsMain → MediaPipeWasmFaceTracker(config) // jvmMain → OnnxFaceTracker(config)。依存が無ければ NoOp 同じ名前のactualを、4つの環境ごとに1つずつ置く 81

Slide 82

Slide 82 text

4つとも、start・stop・trackingData・stateを同じ形 で備える // commonMain — 4つの実装が返す、同じ約束 public interface FaceTracker : Releasable { val trackingData: Flow val state: StateFlow suspend fun start() suspend fun stop() } stateはIDLE・STARTING・TRACKING・ STOPPED・ERROR・RELEASEDの6つ 82

Slide 83

Slide 83 text

同じなのは使い方と出力の形。中身と能力は別 Android iOS MediaPipe ARKit 較正・平滑化を 利用できる 較正・平滑化を 利用できる Web Desktop MediaPipe (Wasm) ONNX Runtime 較正・平滑化は 未対応 UNSUPPORTED 較正・平滑化は 未対応 UNSUPPORTED 83

Slide 84

Slide 84 text

52項目は1つのenumに集め、名前は機械変換する public enum class BlendShape { // ARKit由来の52項目。定義はここ1回 EYE_BLINK_LEFT, ..., CHEEK_PUFF, ..., TONGUE_OUT; public val arKitName: String // → eyeBlinkLeft get() = arKitNames.getValue(this) // 小文字にして "_" で分け、2語目からは先頭を大文字にする val parts = entry.name.lowercase().split("_") parts.first() + parts.drop(1).joinToString("") { part -> part.replaceFirstChar { it.uppercase() } 名前をここに1回だけ書く。手書きの対応表は無い 84

Slide 85

Slide 85 text

詰め替え方は、環境ごとに違う 85

Slide 86

Slide 86 text

詰め替えのコードは、環境ごとに違う // androidMain — MediaPipeのカテゴリ名を、名前で引く if (name == "_neutral") continue // 52項目に無い名前は除外 val shape = nameToBlendShape[name] ?: continue // iosMain — eyeBlink_L を eyeBlinkLeft に直してから引く name.endsWith("_L") -> name.dropLast(2) + "Left" name.endsWith("_R") -> name.dropLast(2) + "Right" // jvmMain — 点から計算した値を、 enumのキーへ直接入れる output[BlendShape.JAW_OPEN] = remap(innerMouthHeight, ...) 86

Slide 87

Slide 87 text

較正や平滑化は、詰め替えの後に行う 詰め替え → 候補 → 較正 → 平滑化 → 利用項目 後ろの4段をcommonMainに置く 87

Slide 88

Slide 88 text

後処理はcommonMainにあるが、適用範囲は 環境ごとに異なる 88

Slide 89

Slide 89 text

既定で有効なのは平滑化だけ。ほかは明示的に選ぶ public data class FaceTrackerConfig( val smoothingConfig: SmoothingConfig = SmoothingConfig.Ema(), // 平滑化(前の値と混ぜる)は既定ON val enhancerConfig: BlendShapeEnhancerConfig = BlendShapeEnhancerConfig.None, // 候補作りは既定None val calibrationConfig: CalibrationConfig = CalibrationConfig(), // enabled = false ...) Emaは直前の値と混ぜる、いちばん単純な平滑化 89

Slide 90

Slide 90 text

共通の出口はFlow。相手に合わせて橋渡しする 90

Slide 91

Slide 91 text

実装の差は境界へ閉じ込め、能力の差は隠さない 91

Slide 92

Slide 92 text

まとめ なぜ どうしたか 設計 頬の表面の点が 入力に無い 点から規則を 作って実装した 能力の差は 隠さない 52個を作る入力は 146点のXYだけ 次は本番アプリで 確かめる 値・確かめた能力・ 使う経路を分ける 92