Spaces:
Running
Running
File size: 9,171 Bytes
d646dd7 | 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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | #!/usr/bin/env python3
"""
手部关键点提取 CLI —— 对已录制会话离线处理。
用法:
python scripts/process_hands.py kpts /path/to/session --mode bare
python scripts/process_hands.py label /path/to/session
python scripts/process_hands.py all /path/to/session --mode bare
"""
import argparse
import os
import sys
import subprocess
import shutil
import numpy as np
import cv2
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.hand_tracking import process_session, label_session, load_hand_kpts, draw_kpts_overlay
def _progress_bar(cur: int, total: int, width: int = 40):
ratio = min(cur / max(total, 1), 1.0)
filled = int(width * ratio)
bar = "█" * filled + "░" * (width - filled)
sys.stderr.write(f"\r [{bar}] {cur}/{total} ({ratio*100:.0f}%)")
sys.stderr.flush()
def cmd_kpts(args):
"""提取手部关键点(2D + 可选 3D)。"""
print(f"提取手部关键点: {args.session}")
print(f" 模式: {args.mode}, 设备: {args.device}")
sys.stderr.write("\n")
result = process_session(
session_path=args.session,
mode=args.mode,
det_device=args.device,
pose_device=args.device,
progress_cb=_progress_bar,
status_cb=lambda msg: print(f" [{msg}]"),
)
sys.stderr.write("\n")
if result["success"]:
print(f"✅ 完成: {result['frames']} 帧, "
f"{result['elapsed']:.1f}s, {result['fps']:.1f} fps")
else:
print(f"❌ 失败: {result['error']}")
sys.exit(1)
def cmd_label(args):
"""手势自动标注。"""
print(f"手势标注: {args.session}")
result = label_session(
session_path=args.session,
progress_cb=_progress_bar,
)
sys.stderr.write("\n")
if result["success"]:
print(f"✅ 完成: {result['frames']} 帧, {result['elapsed']:.1f}s")
else:
print(f"❌ 失败: {result['error']}")
sys.exit(1)
def cmd_all(args):
"""先提取关键点,再标注。"""
cmd_kpts(args)
print()
cmd_label(args)
def cmd_show(args):
"""显示已有关键点信息。"""
kpts = load_hand_kpts(args.session)
if kpts:
n = len(kpts)
sample = kpts[min(kpts.keys())]
print(f"手部关键点数据: {n} 帧")
print(f" 维度: {len(sample['hand_data'])}")
print(f" 最多手数: {sample['num_hands']}")
else:
print("❌ 未找到手部关键点数据")
sys.exit(1)
def _find_video(session_path: str) -> str:
"""在 session 目录下找第一个可用的 RGB 视频文件。"""
from core.helpers import detect_session_format, egodata_video_path, egodata_metadata_path
import json
vdir = os.path.join(session_path, "videos")
if not os.path.isdir(vdir):
return ""
fmt = detect_session_format(session_path)
# 先尝试从 metadata 获取相机列表
if fmt == "egodata":
meta_path = egodata_metadata_path(session_path)
if os.path.isfile(meta_path):
with open(meta_path) as f:
cameras = json.load(f).get("cameras", {})
for cam_name in cameras:
if cameras[cam_name].get("type") == "depth":
continue
for vp in [egodata_video_path(session_path, cam_name),
os.path.join(vdir, f"{cam_name}.mp4")]:
if os.path.isfile(vp):
return vp
# 直接扫描 videos/ 目录
for entry in sorted(os.listdir(vdir)):
if "depth" in entry.lower():
continue
full = os.path.join(vdir, entry)
if os.path.isfile(full) and entry.endswith(".mp4"):
return full
# 子目录结构
sub_dir = os.path.join(full, "chunk-0000")
if os.path.isdir(sub_dir):
for fname in os.listdir(sub_dir):
if fname.endswith(".mp4"):
return os.path.join(sub_dir, fname)
return ""
def cmd_render(args):
"""将手部关键点叠加到视频上,导出可视化视频。"""
import time
from core.helpers import keypoints_video_dir
session_path = args.session
kpts = load_hand_kpts(session_path)
if not kpts:
print("❌ 未找到手部关键点数据,请先运行 kpts 命令")
sys.exit(1)
video_path = _find_video(session_path)
if not video_path:
print("❌ 未找到可播放的视频文件")
sys.exit(1)
cam_name = os.path.splitext(os.path.basename(video_path))[0]
out_dir = keypoints_video_dir(session_path)
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, f"{cam_name}_hand_kpts.mp4")
cap = cv2.VideoCapture(video_path)
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cap.get(cv2.CAP_PROP_FPS)
if fps <= 0:
fps = 30.0
ret, first = cap.read()
if not ret:
print("❌ 无法读取视频帧")
sys.exit(1)
h, w = first.shape[:2]
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
# ffmpeg 优先,cv2 回退
writer = None
ffmpeg_proc = None
if shutil.which("ffmpeg"):
ffmpeg_cmd = [
"ffmpeg", "-y", "-f", "rawvideo", "-vcodec", "rawvideo",
"-s", f"{w}x{h}", "-pix_fmt", "bgr24", "-r", str(fps),
"-i", "-", "-c:v", "libx264", "-preset", "fast", "-crf", "23",
"-pix_fmt", "yuv420p", out_path,
]
try:
ffmpeg_proc = subprocess.Popen(
ffmpeg_cmd, stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except Exception:
ffmpeg_proc = None
if not ffmpeg_proc:
for codec in ["avc1", "mp4v"]:
fourcc = cv2.VideoWriter_fourcc(*codec)
writer = cv2.VideoWriter(out_path, fourcc, fps, (w, h))
if writer.isOpened():
break
print(f"输入视频: {video_path} ({total} 帧, {fps} fps)")
print(f"手部关键点: {len(kpts)} 帧")
print(f"输出: {out_path}")
t0 = time.perf_counter()
fi = 0
while True:
ret, frame = cap.read()
if not ret:
break
kpt = kpts.get(fi)
if kpt is not None and np.any(kpt["hand_data"] > 0):
frame = draw_kpts_overlay(frame, kpt["hand_data"], kpt["track_ids"])
if ffmpeg_proc:
try:
ffmpeg_proc.stdin.write(frame.tobytes())
except BrokenPipeError:
break
else:
writer.write(frame)
fi += 1
if fi % 50 == 0:
_progress_bar(fi, total)
cap.release()
if ffmpeg_proc:
ffmpeg_proc.stdin.close()
ffmpeg_proc.wait()
else:
writer.release()
elapsed = time.perf_counter() - t0
_progress_bar(total, total)
sys.stderr.write("\n")
size_mb = os.path.getsize(out_path) / (1024 * 1024)
print(f"✅ 完成: {out_path}")
print(f" {fi} 帧, {elapsed:.1f}s ({fi / elapsed:.1f} fps), {size_mb:.1f} MB")
def main():
parser = argparse.ArgumentParser(
description="手部关键点提取与标注",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python scripts/process_hands.py kpts data/recordings/Test005/episode_000001 --mode bare
python scripts/process_hands.py render data/recordings/Test005/episode_000001
python scripts/process_hands.py all data/recordings/Test005/episode_000001 --mode bare
python scripts/process_hands.py show data/recordings/Test005/episode_000001
""",
)
sub = parser.add_subparsers(dest="command", help="子命令")
p_kpts = sub.add_parser("kpts", help="提取手部关键点")
p_kpts.add_argument("session", help="会话目录路径")
p_kpts.add_argument("--mode", default="glove",
choices=["glove", "bare"], help="追踪模式")
p_kpts.add_argument("--device", default="cuda", help="推理设备")
p_label = sub.add_parser("label", help="手势自动标注")
p_label.add_argument("session", help="会话目录路径")
p_all = sub.add_parser("all", help="提取 + 标注")
p_all.add_argument("session", help="会话目录路径")
p_all.add_argument("--mode", default="glove",
choices=["glove", "bare"], help="追踪模式")
p_all.add_argument("--device", default="cuda", help="推理设备")
p_show = sub.add_parser("show", help="查看已有数据")
p_show.add_argument("session", help="会话目录路径")
p_render = sub.add_parser("render", help="导出关键点可视化视频")
p_render.add_argument("session", help="会话目录路径")
args = parser.parse_args()
if args.command == "kpts":
cmd_kpts(args)
elif args.command == "label":
cmd_label(args)
elif args.command == "all":
cmd_all(args)
elif args.command == "show":
cmd_show(args)
elif args.command == "render":
cmd_render(args)
else:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()
|