深度マップで写真をシャドウボックス風の立体作品にした
深度推定モデル(MiDaS)で画像から深度マップを生成して、複数のレイヤーに分割。3D空間に配置して、シャドウボックスのような立体写真を作った。
平面の写真が、まるで奥行きのある作品になる。
やりたかったこと
写真を立体的に表現したかった。
シャドウボックスって知ってる?同じ絵を何枚も重ねて、深度ごとに切り抜いて、立体的に見せる技法。
深度マップがあれば、これをデジタルで再現できるんじゃないか。写真を深度ごとにレイヤーに分割して、3D空間に配置する。
深度マップの生成
MiDaSモデルで画像の深度マップを生成する。
const inferenceManager = new GPUInferenceManager("/ai_models/midas_model_DPT_Hybrid_512.onnx");
// 画像を読み込んで推論
const imageElement = new Image();
imageElement.src = 'images/augmented_image.jpg';
await imageElement.decode();
// 深度推論
const result = await inferenceManager.runInference(imageElement);
const depthData = result.outputData; // 512×512の深度マップ
推論は512×512で実行。静止画なので、リアルタイム性は不要。
最初は256×256で試した。深度の精度が低かった。細かい物体の境界が分からない。
1024×1024にしたら、精度は上がったけど、推論時間が長くなった。約3秒。
512×512なら、約1秒で推論が終わる。精度も十分。
レイヤーの分割
深度マップを使って、画像を複数のレイヤーに分割する。
function splitImageIntoLayers(imageData, depthData, layerCount) {
const layers = [];
// 深度の範囲を計算
let minDepth = Infinity, maxDepth = -Infinity;
depthData.forEach(d => {
minDepth = Math.min(minDepth, d);
maxDepth = Math.max(maxDepth, d);
});
// レイヤーごとに分割
for (let i = 0; i < layerCount; i++) {
const layerCanvas = document.createElement('canvas');
layerCanvas.width = 512;
layerCanvas.height = 512;
const ctx = layerCanvas.getContext('2d');
const layerImageData = ctx.createImageData(512, 512);
// 深度の閾値
const depthThresholdMin = minDepth + (maxDepth - minDepth) * (i / layerCount);
const depthThresholdMax = minDepth + (maxDepth - minDepth) * ((i + 1) / layerCount);
// ピクセルをコピー
for (let j = 0; j < depthData.length; j++) {
if (depthData[j] >= depthThresholdMin && depthData[j] < depthThresholdMax) {
// このレイヤーに属するピクセルをコピー
layerImageData.data[j * 4] = imageData.data[j * 4];
layerImageData.data[j * 4 + 1] = imageData.data[j * 4 + 1];
layerImageData.data[j * 4 + 2] = imageData.data[j * 4 + 2];
layerImageData.data[j * 4 + 3] = 255;
} else {
// 透明にする
layerImageData.data[j * 4 + 3] = 0;
}
}
ctx.putImageData(layerImageData, 0, 0);
layers.push(layerCanvas);
}
return layers;
}
レイヤー数は8個に設定した。
最初は4個で試した。分割が粗すぎて、立体感が弱かった。
16個にしたら、分割が細かすぎた。レイヤーが薄くなりすぎて、見えにくくなった。
8個がちょうどいい。立体感がハッキリして、見やすい。
3D空間への配置
各レイヤーを、深度に応じて3D空間に配置する。
function createLayerMeshes(layers, spacing) {
const meshes = [];
layers.forEach((layerCanvas, index) => {
// レイヤーのテクスチャを作成
const texture = new THREE.CanvasTexture(layerCanvas);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
// 透明度を持つマテリアル
const material = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
side: THREE.DoubleSide
});
// PlaneGeometryで平面を作成
const geometry = new THREE.PlaneGeometry(160, 90);
const mesh = new THREE.Mesh(geometry, material);
// Z座標で配置(奥から手前へ)
mesh.position.z = index * spacing;
meshes.push(mesh);
scene.add(mesh);
});
return meshes;
}
// レイヤー間の間隔は5.0
const layerMeshes = createLayerMeshes(layers, 5.0);
レイヤー間の間隔(spacing)は5.0に設定した。
最初は1.0で試した。レイヤーが近すぎて、立体感が分かりにくかった。
10.0にしたら、レイヤーが離れすぎた。写真がバラバラに見えた。
5.0がちょうどいい。立体感がハッキリして、写真としてのまとまりもある。
ハマったところ
レイヤーの透明度処理
最初、透明部分の処理を間違えてた。
// NG: 透明部分が黒になる
layerImageData.data[j * 4] = 0;
layerImageData.data[j * 4 + 1] = 0;
layerImageData.data[j * 4 + 2] = 0;
layerImageData.data[j * 4 + 3] = 0;
RGB値を0にしてたから、透明部分が黒く表示された。
RGBはそのままで、Alpha値だけ0にする必要があった。
// OK: Alpha値だけ0
layerImageData.data[j * 4 + 3] = 0;
これで、透明部分が正しく処理されるようになった。
深度の閾値計算
深度を均等に分割したら、レイヤーの厚さが不均等になった。
遠い部分は広く、近い部分は狭い。深度マップの分布が均等じゃないから。
ヒストグラムを作って、ピクセル数が均等になるように閾値を計算したかったけど、複雑すぎた。
結局、均等分割のまま。視覚的には問題なかった。
Three.jsのレンダリング順序
レイヤーの描画順序が重要。
最初、追加順にレンダリングしてた。奥のレイヤーが手前のレイヤーの上に描画されることがあった。
// NG: 追加順
layers.forEach((layer, i) => {
scene.add(layer);
});
renderOrderを設定したら、正しい順序で描画されるようになった。
// OK: renderOrderを設定
layers.forEach((layer, i) => {
layer.renderOrder = i;
scene.add(layer);
});
パフォーマンス
静止画なので、リアルタイム性は不要。
| 処理 | 時間 |
|------|------|
| 深度推論(512×512) | 約1秒 |
| レイヤー分割(8層) | 約0.5秒 |
| 3D配置 | 約0.1秒 |
| 合計 | 約1.6秒 |
初回の読み込みに約1.6秒。その後は60fpsで描画。
スマホだと、深度推論に約3秒かかった。レイヤー分割も約1秒。合計約4秒。まあ許容範囲。
結果
深度マップで写真をシャドウボックス風の立体作品にできた。
- MiDaS深度推定で深度マップ生成(512×512、約1秒)
- 8層のレイヤーに分割(深度の均等分割)
- 3D空間に配置(レイヤー間隔5.0)
- renderOrderで正しい描画順序
レイヤー数とレイヤー間隔の調整が重要だった。8層と5.0が最適。
平面の写真が、立体的な作品になる。OrbitControlsで回転させると、奥行きが分かる。面白い表現手法だと思う。
まとめ
今回は、深度マップを使って写真をシャドウボックス風の立体作品に変換する実験を行いました。
ポイントは以下の3つ:
- MiDaS深度推定(512×512、約1秒)で深度マップ生成
- 8層のレイヤーに分割(深度の均等分割)
- 3D空間に配置(レイヤー間隔5.0、renderOrderで描画順序制御)
レイヤー数とレイヤー間隔の調整で、立体感を最適化した。
深度マップを使った写真表現に興味がある方の参考になれば嬉しいです。
さらに深く学ぶには
この記事で興味を持った方におすすめのリンク:
自分の関連記事:
最後まで読んでくださり、ありがとうございました。