Screenshot
以前から、インカメラで目の位置を追いかければ、透けて見える壁紙や、写真が立体に見える壁紙が作れるのでは、と考えていました。ただ、計算やキャリブレーションが面倒で手を付けずにいました。今回それを Claude Code で1つのアプリにまとめたのが「透過・3D壁紙」です。
Android 11 以上(arm64)
開発機: OPPO Find N6
処理はすべて端末内
よく透明なスマホのようなコンセプト動画がありますが、それを実現しようとしたアプリ。当初は光学迷彩壁紙という名前にしようかと思っていましたが商標上断念しました。
カメラの映像をそのまま全画面に出しても、透けては見えません。カメラの位置と目の位置が違い、画角も違うからです。透けて見せるには、画面を窓枠とみなし、今の目の位置からその窓越しに見える範囲だけを描く必要があります。
図にすると次の通りです。図の距離は縮めています。
このアプリには、表示する内容が3つあります。
どれもホーム画面のライブ壁紙に設定できます。とはいえカメラを常時使うので実用的ではなく、ネタアプリです。写真3D は、カメラを使わずにジャイロの傾きだけで視差を出す設定もあり、バッテリーへの負担が小さくなります。
インカメラの映像から MediaPipe の Face Landmarker で顔の特徴点を取り、両目の位置を求めます。距離は両目の間隔(瞳孔間距離)から推定し、顔の向きで補正します。虹彩の大きさから推定するより、検出の誤差の影響が小さかったためです。
透けて見える正しい絵は、片方の目の位置でしか成り立ちません。そこで基準にする目を「両目の中間・右目・左目」から選べるようにしました。利き目を選ぶと、両目を開けたままでも自然に見えます。
手に持つと、端末は細かく回転します。目は空間の中ではほぼ止まっているので、ジャイロで求めた端末の姿勢を使い、目の位置を空間に固定した座標で扱います。端末が回った分だけ、端末から見た目の位置を即座に補正できます。
実機で測ると、カメラで撮ってから目の位置が出るまでに約 110〜135ms かかっていました。描いてから画面に光るまでにも、さらに数フレームかかります。頭を動かすと、映像が後からついてくる感じが残りました。
そこで、目の位置を「位置と速度」で推定するカルマンフィルタ(等速度モデル)に置き換え、画面に表示される時刻まで速度で進めた位置を描くようにしました。止まっているときに速度の誤差で揺れないよう、ある程度の速さで動いているときだけ先読みを効かせます。検出は 1 秒に 20〜25 回なので、検出のたびに位置を直すと表示がカクつきます。直した分は約 50ms かけて少しずつ反映します。
写真3D では、1枚の写真から奥行きを推定し、凹凸のある格子(メッシュ)として描きます。写真の各点は、標準の目の位置(画面の正面 35cm)から見ると元の写真とまったく同じに見えるよう、その目から伸ばした線の上に置きます。正面から見ると元の写真のまま、頭を動かすと奥行きに応じてずれて見えます。
奥行きの推定には Depth Anything V2 の Small モデル(int8 量子化、約 27MB)を使い、ONNX Runtime で端末内で動かしています。Depth Anything V2 は Small だけが Apache License 2.0 で、商用でも使えます。
推定した奥行きは、人の輪郭のような境目でなだらかにぼやけます。この中間の値の部分は、視点を動かすと引き伸ばされてギザギザになります。そこで写真の色と、MediaPipe の人物切り抜きモデルで求めた人の範囲を手がかりに、近くの画素の重み付き中央値で境目をくっきりさせています。
手前の物と奥の物の境目では、視点を動かすとメッシュが大きく引き伸ばされます。そこで、写真を「前景」と、手前の物を取り除いたと仮定した「背景層」の2層で描きます。前景の引き伸ばされた部分は、シェーダーで奥行きの等高線に沿って切り抜きます。三角形ごとに捨てると格子の形のギザギザになるので、三角形の中を通る線で切って、なめらかにつなげています。
背景層づくりは何度もやり直しました。最初は隙間を「周りでいちばん奥の面」で埋めていました。すると、顔と髪の間の隙間から奥の花畑が見え、人が透けているように見えました。
次に「すぐ後ろの面」で埋めるようにすると、透け感は消えました。ただし今度は、輪郭から一定の幅のところに段差ができ、視点を大きく動かすと手前の物に厚みのある側面の壁があるように見えました。
最終的には、手前の物の内側も「隙間が開きうる幅」まで後ろの面に下げる形に落ち着きました。幅は、手前と奥の奥行きの差に比例させています。隠れていた部分の色は、すぐ後ろが遠くの背景なら描き足しモデルの MI-GAN で模様を続けます。手の後ろの体のように別の手前の物なら、奥行きが近い画素の色だけで埋めます。
奥行きに応じたぼかしも付けられます。写真に人の目があれば、顔の特徴点から目の位置を探し、その奥行きにピントを合わせます。ポートレートらしい見え方になります。
透過と写真3D がそろうと、「写真の人だけを、今いる場所の景色に重ねたら」という組み合わせが自然に浮かびます。部品はほとんどそろっていました。メインカメラの景色を窓として描き、その上に、写真の人だけを残した立体のメッシュを重ねます。
残す範囲は、写真に写った人と、設定した奥行きより手前の物です。人の範囲は、写真3D で境目を整えるのに使っていた人物の切り抜きモデルの結果を、そのまま保存して使い回しています。
やってみると、写真3D のための工夫が、ここではことごとく裏目に出ました。
色や明るさを景色に合わせる補正と、足元の影はまだありません。そのため、少し浮いて見えます。とはいえ、頭を動かすと、写真の人と後ろの本物の景色が別々にずれていき、窓の向こうに人がいるような感覚はちゃんと出ます。
ホーム画面の壁紙だけでなく、別のアプリに窓を開けることもやってみました。アプリをフローティングウィンドウで表示すると、ウィンドウの位置と大きさに合わせて景色を描き、アプリに窓が開いたように見せます。(フローティングウィンドウ対応機のみ)
一番難しいと思ったのが端末に合わせたキャリブレーションですが、初回にガイド付きの調整で済ませられます。
OPPO Find N6のような折りたたみスマホでは、両方の画面で個別に設定できます。
設定画面は「表示」「立体感」「視点」の3つに分け、よく使う項目だけを並べました。細かい項目は「詳細」の中にあり、ガイド付き調整もそこからやり直せます。
| 起きたこと | 原因と対策 |
|---|---|
| 奥行き推定の途中でアプリが強制終了する | ONNX Runtime 1.23.2 が、SME はあるが SME2 はない CPU で SME2 の命令を実行していた(SIGILL)。1.27.0 に上げて解決 |
| 壁紙からカメラを開けない | Android は画面に出ていないアプリのカメラを拒否する。カメラ種別のフォアグラウンドサービスを、アプリが前面にある間に起動しておく |
| Android 15 で互換性の警告が出る | ネイティブライブラリが 16KB ページサイズに未対応だった。MediaPipe 0.10.35 などに上げ、全ライブラリのアラインメントを確認 |
| 写真の輪郭がギザギザ・透ける・壁に見える | 境目の整え方、前景の切り抜き方、背景層の作り方を段階的に変更(前述の通り) |
| 人物+透過で、体に穴が空き、輪郭が階段状になる | 写真3D 用の「引き伸ばされた部分を捨てる」処理を止め、切り抜いた外側の奥行きを人の奥行きで埋め、人の範囲を写真の色で細かくした(前述の通り) |
OPPO Find N6でしか動作確認していないので、他機種で動くかは分かりませんが、作成したアプリを載せておきます。インストールは自己責任でお願いします。
SHA-256: 0d5e8a74e6a5a268ca7ad4c779a44d74988ef1d0276afe0b1766bd40774d7f42 touka-3d-wallpaper-0.2.apk
動画撮影用に、目の代わりに撮影するカメラの位置を QR コードで測る機能もあります(設定の『視点の決め方』→『QR』)。印刷用の QR コードは下記です。
使っているライブラリとモデル:MediaPipe(Apache 2.0)、ONNX Runtime(MIT)、Depth Anything V2 Small(Apache 2.0)、MI-GAN(MIT)、ML Kit。アプリ内の「ライセンス」から全文を確認できます。