File size: 3,446 Bytes
1a36f9f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | #!/usr/bin/env python3
"""把手部网格(MANO、SOMA 或任意三角网格)投影到 RGB 上,生成半透明叠加图。
只依赖 numpy 和 Pillow。可单独运行,也被 server_sync.py 调用:
python tools/render_overlay.py --image frame.png --mesh hand.npz --out frame_gt.png
mesh.npz 需要:
verts (N, 3) 相机坐标系下的顶点,单位米,z 朝前
faces (F, 3) 三角面索引
K (3, 3) 内参(也可用 --K 传入 JSON)
可选:
verts 可以是 (H, N, 3),表示多只手;faces 共用
joints (J, 3) 或 (H, J, 3),会画成小圆点
"""
import argparse
import json
import numpy as np
from PIL import Image, ImageDraw
HAND_COLORS = [(70, 140, 230), (235, 140, 50)] # 右手蓝、左手橙(按传入顺序)
def project(p, K):
z = np.clip(p[:, 2], 1e-6, None)
return np.stack([K[0, 0] * p[:, 0] / z + K[0, 2], K[1, 1] * p[:, 1] / z + K[1, 2]], 1)
def overlay(img, verts, faces, K, joints=None, alpha=0.55, colors=HAND_COLORS):
"""img: PIL.Image;verts: (N,3) 或 (H,N,3);返回新的 PIL.Image。"""
base = img.convert("RGBA")
layer = Image.new("RGBA", base.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(layer)
V = np.asarray(verts, float)
if V.ndim == 2:
V = V[None]
F = np.asarray(faces, int)
K = np.asarray(K, float)
light = np.array([0.3, -0.4, -0.85])
light /= np.linalg.norm(light)
tris = [] # (depth, polygon, rgba)
for h, v in enumerate(V):
col = np.array(colors[h % len(colors)], float)
uv = project(v, K)
a, b, c = v[F[:, 0]], v[F[:, 1]], v[F[:, 2]]
n = np.cross(b - a, c - a)
n /= np.linalg.norm(n, axis=1, keepdims=True) + 1e-12
shade = 0.45 + 0.55 * np.abs(n @ light)
depth = (a[:, 2] + b[:, 2] + c[:, 2]) / 3
ok = (a[:, 2] > 0) & (b[:, 2] > 0) & (c[:, 2] > 0)
for i in np.nonzero(ok)[0]:
poly = [tuple(uv[j]) for j in F[i]]
rgb = tuple(int(x) for x in np.clip(col * shade[i], 0, 255))
tris.append((depth[i], poly, rgb + (int(255 * alpha),)))
for _, poly, rgba in sorted(tris, key=lambda t: -t[0]): # 远的先画
draw.polygon(poly, fill=rgba)
out = Image.alpha_composite(base, layer)
if joints is not None:
J = np.asarray(joints, float)
if J.ndim == 2:
J = J[None]
d = ImageDraw.Draw(out)
r = max(2, round(min(out.size) / 220))
for j in J:
for x, y in project(j, K):
d.ellipse([x - r, y - r, x + r, y + r], fill=(255, 255, 255, 255), outline=(20, 20, 20, 255))
return out.convert("RGB")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--image", required=True)
ap.add_argument("--mesh", required=True, help="npz:verts, faces, 可选 K / joints")
ap.add_argument("--K", help="内参 JSON,例如 [[fx,0,cx],[0,fy,cy],[0,0,1]]")
ap.add_argument("--alpha", type=float, default=0.55)
ap.add_argument("--out", required=True)
a = ap.parse_args()
m = np.load(a.mesh)
K = np.array(json.loads(a.K)) if a.K else m["K"]
img = overlay(Image.open(a.image), m["verts"], m["faces"], K,
joints=m["joints"] if "joints" in m.files else None, alpha=a.alpha)
img.save(a.out)
print("写出", a.out)
if __name__ == "__main__":
main()
|