なんでも立体化スクリプト
この記事はdclab Advent Calendar 2025の2025-12-08の記事です。
https://gyazo.com/f969ed5b84a5428d7213ffff46dc358e
小栗研B3横田峻祐です。
Depth-Anything-v2を使って小栗先生のプロジェクションマッピングをします。
hr.icon
材料:
GithubからクローンしたDepth-Anything-v2のリポジトリ …1個
Visual Studio Code   …一個
ストレージの残量 …2GBぐらい
Creality Print …1個
Creality Ender3 V3 SE …一基
/icons/hrd.icon
🄐
小栗先生の写真   …1枚
Mac Book Air   …一個
プロジェクター                     …一基
hr.icon
作り方:
⑴とりあえず動かす
まずはgithubのreadmeを見てセッティングをします。
code:setup.sh
git clone https://github.com/DepthAnything/Depth-Anything-V2
cd Depth-Anything-V2
pip install -r requirements.txt
これをやって、
https://gyazo.com/98ce4ab4fd849652aa4f695cbed1c008
↑ここからいい感じのやつをダウンロードして、
code:run.sh
python run.py \
--encoder <vits | vitb | vitl | vitg> \
--img-path <path> --outdir <outdir> \
--input-size <size> --pred-only --grayscale
↑ここからいい感じにオプションを選んだりして
https://gyazo.com/b78797bde8d0f46a6ba70eb640147c87
↑コイツを食わせて動かすとこんな感じになります。
https://gyazo.com/e9c2be63933e4c894362fe71d8ce7998
⑵コピペ
READMEからコイツをコピペ:
code:sample.py
import cv2
import torch
from depth_anything_v2.dpt import DepthAnythingV2
DEVICE = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
model_configs = {
'vits': {'encoder': 'vits', 'features': 64, 'out_channels': 48, 96, 192, 384},
'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': 96, 192, 384, 768},
'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': 256, 512, 1024, 1024},
'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': 1536, 1536, 1536, 1536}
}
encoder = 'vitl' # or 'vits', 'vitb', 'vitg'
model = DepthAnythingV2(**model_configsencoder)
model.load_state_dict(torch.load(f'checkpoints/depth_anything_v2_{encoder}.pth', map_location='cpu'))
model = model.to(DEVICE).eval()
raw_img = cv2.imread('your/image/path')
depth = model.infer_image(raw_img) # HxW raw depth map in numpy
encoderをダウンロードしたモデルに変更してcv2.imreadのパスを例の写真に変更
最終行にcv2.imwrite('depth_map.png', (depth / depth.max() * 255).astype('uint8'))を追加して実行すると…
https://gyazo.com/d16a137843efbf1cbf1074b4e31a434e
比較のために元画像
https://scrapbox.io/files/694a45024b3133942f918532.webp
いい感じ
⑴モデルを作る
stlモデルを作ります:
今の状態のイメージは
table:image
1 19 32 14 21 13 12
24 20 50 24 32 21 15
24 30 70 89 24 100 120
2 8 40 ....
みたいな感じで画素ごとに推定された深度が表示されてます。
最大で255最小で0です。
これをレリーフに改造していきます
こんな感じで:
https://gyazo.com/f459f8a6b2cf0e848e6d44931178e372
copilot使ったりなんやかんやしてこんな感じのスクリプトができます
code:show.py
import cv2
import torch
import numpy as np
from stl import mesh
from depth_anything_v2.dpt import DepthAnythingV2
DEVICE = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
model_configs = {
'vits': {'encoder': 'vits', 'features': 64, 'out_channels': 48, 96, 192, 384},
'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': 96, 192, 384, 768},
'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': 256, 512, 1024, 1024},
'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': 1536, 1536, 1536, 1536}
}
encoder = 'vitb' # or 'vits', 'vitb', 'vitg'
model = DepthAnythingV2(**model_configsencoder)
model.load_state_dict(torch.load(f'checkpoints/depth_anything_v2_{encoder}.pth', map_location='cpu'))
model = model.to(DEVICE).eval()
raw_img = cv2.imread('../WPj2AAk.webp') # HxWx3 BGR image
depth = model.infer_image(raw_img) # HxW raw depth map in numpy
cv2.imwrite('depth_map.png', (depth / depth.max() * 255).astype('uint8'))
#===== Generate 3D mesh from depth map and save as STL =====#
# variables
bottom_clip = 0.5
gradient = 300
depth_normalized = (depth - depth.min()) / (depth.max() - depth.min())
H, W = depth.shape0, depth.shape1
bottomVertexes = np.array([x, 1-y, 0.0 for y in range(H) for x in range(W)])
topVertexes = np.array([[x, 1-y, (depth_normalizedy, x- bottom_clip) * gradient ] for y in range(H) for x in range(W)])
for y in range(H):
for x in range(W):
if topVertexesy * W + x2 <= 0:
topVertexesy * W + x2 = 10
allVertexes = np.vstack((topVertexes, bottomVertexes))
# Top surface faces (consistent CCW when viewed from +Z)
faces = []
for y in range(H - 1):
for x in range(W - 1):
tl = y * W + x
tr = tl + 1
bl = (y + 1) * W + x
br = bl + 1
faces.append(tl, tr, bl)
faces.append(tr, br, bl)
# Bottom surface faces (reverse winding so normals face -Z)
offset = H * W
for y in range(H - 1):
for x in range(W - 1):
tl = offset + (y * W + x)
tr = tl + 1
bl = offset + ((y + 1) * W + x)
br = bl + 1
faces.append(tl, bl, tr)
faces.append(tr, bl, br)
# Side walls: build perimeter once and create quads between consecutive perimeter vertices
perim = []
# top row left->right
for x in range(0, W):
perim.append(0 * W + x)
# right column top->bottom (skip top)
for y in range(1, H):
perim.append(y * W + (W - 1))
# bottom row right->left (skip corner already added)
if H > 1:
for x in range(W - 2, -1, -1):
perim.append((H - 1) * W + x)
# left column bottom->top (skip corners)
if W > 1:
for y in range(H - 2, 0, -1):
perim.append(y * W + 0)
perim = np.array(perim, dtype=int)
perim_len = perim.shape0
for i in range(perim_len):
a = int(perimi)
b = int(perim(i + 1) % perim_len)
bot_a = offset + a
bot_b = offset + b
# quad a, b, bot_b, bot_a -> two triangles
# reverse winding so normals point outward from the solid
faces.append(bot_b, b, a)
faces.append(bot_a, bot_b, a)
faces = np.array(faces)
# Flip winding for all faces (reverse vertex order) so every polygon is inverted
faces = faces:, ::-1.copy()
depth_mesh = mesh.Mesh(np.zeros(faces.shape0, dtype=mesh.Mesh.dtype))
for i, f in enumerate(faces):
depth_mesh.vectorsi = allVertexesf, :
depth_mesh.save('depth_mesh.stl')
AIにポリゴンの向きの調整をさせまくったのでとんでもないスパゲティプログラムになってしまった。
可読性?何それ
そしてできたのがこれ
https://gyazo.com/1cbe15185e96c83f4c57c92938be469f
⑶プリント
このstlをCreality Printにぶちこんでスライスします。
そして印刷したのがこれ
https://gyazo.com/b705d131dfa6ca681dabd7e877a897ee
右は失敗作
あとはこれにプロジェクターで写真を写すだけ
https://scrapbox.io/files/694a4ffaad44a53a6ee1085e.mov
完成!