EXPERIMENT / 配套代码

video.py

← 返回文章
"""本地视频/摄像头 + YOLOv8 ONNX + 跟踪 + 控制意图,只写日志,不驱动电机。"""

import argparse, json, time, math
from pathlib import Path
import cv2
import onnxruntime as ort
from detector import preprocess, decode
from tracker import Tracker
from controller import Controller


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--model", type=Path, required=True)
    source = parser.add_mutually_exclusive_group(required=True)
    source.add_argument("--video", type=Path)
    source.add_argument("--camera", type=int)
    parser.add_argument("--classes", type=int, required=True)
    parser.add_argument("--output", type=Path, default=Path("runs-video"))
    parser.add_argument("--max-frames", type=int, default=300)
    args = parser.parse_args()
    if args.classes < 1 or args.max_frames < 1:
        parser.error("positive classes/max-frames required")
    session = ort.InferenceSession(str(args.model), providers=["CPUExecutionProvider"])
    inputs = session.get_inputs()
    if len(inputs) != 1 or len(session.get_outputs()) != 1:
        raise ValueError("one input/output required")
    inp = inputs[0]
    if (
        inp.type != "tensor(float)"
        or len(inp.shape) != 4
        or inp.shape[:2] != [1, 3]
        or not isinstance(inp.shape[2], int)
        or inp.shape[2] != inp.shape[3]
    ):
        raise ValueError("static FP32 [1,3,S,S] expected")
    cap = cv2.VideoCapture(str(args.video) if args.video else args.camera)
    if not cap.isOpened():
        raise RuntimeError("cannot open input")
    fps = cap.get(cv2.CAP_PROP_FPS)
    if args.video and (not math.isfinite(fps) or fps <= 0):
        cap.release()
        raise ValueError("video FPS unavailable; use explicit timestamped replay")
    tracker = Tracker()
    controller = Controller()
    previous = None
    args.output.mkdir(parents=True, exist_ok=True)
    count = 0
    try:
        with (args.output / "frames.jsonl").open("w") as log:
            for frame_id in range(args.max_frames):
                start = time.monotonic()
                ok, image = cap.read()
                if not ok:
                    break
                timestamp = frame_id / fps if args.video else time.monotonic()
                dt = (
                    timestamp - previous
                    if previous is not None
                    else (1 / fps if math.isfinite(fps) and fps > 0 else 0.033)
                )
                previous = timestamp
                x, meta = preprocess(image, inp.shape[2])
                raw = session.run(None, {inp.name: x})[0]
                detections = decode(raw, meta, args.classes, score=0.1)
                tracks = tracker.step(
                    [d for d in detections if d["score"] >= 0.6],
                    dt=max(dt, 1e-6),
                    low=[d for d in detections if 0.1 <= d["score"] < 0.6],
                )
                elapsed = time.monotonic() - start
                command = controller.step(
                    tracks, image.shape[1], max(dt, 1e-6), elapsed
                )
                log.write(
                    json.dumps(
                        {
                            "frame_id": frame_id,
                            "media_time_s": timestamp,
                            "local_processing_s": elapsed,
                            "tracks": tracks,
                            "command": command,
                        }
                    )
                    + "\n"
                )
                count += 1
            log.write(
                json.dumps(
                    {
                        "event": "end",
                        "command": {"speed": 0, "reason": "input_end"},
                        "motor_io": False,
                    }
                )
                + "\n"
            )
    finally:
        cap.release()
    print(
        "processed frames",
        count,
        "no motor I/O; local processing excludes sensor buffering",
    )


if __name__ == "__main__":
    main()