feat(data-only-viz): action capture webcam script
Implement Task 10 of action-head plan. Records webcam frames + timestamps for action-head training using OpenCV. Outputs MP4 video + timestamp text file to ~/.cache/av-live-action/raw/ with 672x672 square crop, configurable fps/session/camera, interactive q-quit.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""Record webcam frames + timestamps for action-head training.
|
||||
|
||||
Usage:
|
||||
uv run python -m data_only_viz.scripts.capture_actions \
|
||||
--session sess03 --duration 600
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
LOG = logging.getLogger("capture_actions")
|
||||
RAW_DIR = Path("~/.cache/av-live-action/raw").expanduser()
|
||||
|
||||
|
||||
def capture(session: str, duration_s: float,
|
||||
cam_index: int = 0, fps: int = 30,
|
||||
size: int = 672) -> Path:
|
||||
RAW_DIR.mkdir(parents=True, exist_ok=True)
|
||||
out = RAW_DIR / f"{session}.mp4"
|
||||
ts_out = RAW_DIR / f"{session}.ts.txt"
|
||||
cap = cv2.VideoCapture(cam_index)
|
||||
if not cap.isOpened():
|
||||
raise RuntimeError(f"cannot open camera {cam_index}")
|
||||
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
||||
writer = cv2.VideoWriter(str(out), fourcc, fps, (size, size))
|
||||
try:
|
||||
t_start = time.perf_counter()
|
||||
with ts_out.open("w") as ts_f:
|
||||
n = 0
|
||||
while time.perf_counter() - t_start < duration_s:
|
||||
ok, frame = cap.read()
|
||||
if not ok:
|
||||
LOG.warning("frame read failed")
|
||||
break
|
||||
h, w = frame.shape[:2]
|
||||
side = min(h, w)
|
||||
y0 = (h - side) // 2
|
||||
x0 = (w - side) // 2
|
||||
crop = frame[y0:y0 + side, x0:x0 + side]
|
||||
resized = cv2.resize(crop, (size, size))
|
||||
writer.write(resized)
|
||||
ts_f.write(f"{n} {time.perf_counter() - t_start:.6f}\n")
|
||||
n += 1
|
||||
cv2.imshow("capture (q=quit)", resized)
|
||||
if cv2.waitKey(1) & 0xFF == ord("q"):
|
||||
break
|
||||
LOG.info("wrote %s (%d frames)", out, n)
|
||||
return out
|
||||
finally:
|
||||
cap.release()
|
||||
writer.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
def _cli() -> None:
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--session", required=True)
|
||||
p.add_argument("--duration", type=float, default=600.0)
|
||||
p.add_argument("--cam-index", type=int, default=0)
|
||||
p.add_argument("--fps", type=int, default=30)
|
||||
args = p.parse_args()
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format="%(asctime)s [%(name)s] %(message)s")
|
||||
capture(args.session, args.duration,
|
||||
cam_index=args.cam_index, fps=args.fps)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_cli()
|
||||
Reference in New Issue
Block a user