Download tools/render_overlay.py from DarthSoluke/EgoNetDatasetResearch: direct link, hf CLI and curl.
- Browser
- Download file 3.45 kB
-
https://huggingface.co/spaces/DarthSoluke/EgoNetDatasetResearch/resolve/main/tools/render_overlay.py
- Command line
-
hf download hf://spaces/DarthSoluke/EgoNetDatasetResearch/tools/render_overlay.py
-
curl -L -o render_overlay.py https://huggingface.co/spaces/DarthSoluke/EgoNetDatasetResearch/resolve/main/tools/render_overlay.py
3.45 kB
| #!/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() | |