前视相机的 FPS 看着很漂亮,底盘却总在拐弯后才开始刹车。检查日志才发现,侧视相机偶尔晚到一帧,nvstreammux 为了凑 batch 把前视画面留在队列里。DeepStream 能把解码、预处理、TensorRT 推理、跟踪和元数据串起来,但这些队列也会把机器人看到的世界变旧。

多路相机采集硬件解码与预处理组批进入 TensorRT目标跟踪与元数据ROS 2 节点消费结果
时延图每个队列都可能让帧变旧,最终应以规划器消费时的感知年龄衡量。
采集时间戳记录相机曝光时刻,不能在 ROS 2 发布时用 now() 替代解码与预处理检查硬件路径、格式转换和不必要的内存复制streammuxbatch 大小和等待超时共同影响吞吐与排队时间TensorRT区分模型执行时间、上下文预热和输入队列等待跟踪与桥接保留帧号、原始时间戳和明确的失效状态规划器消费用 consume_time 减 capture_time 得到真正的感知年龄

先找出是哪一级在等

在典型 GStreamer 图中,source 负责输入,解码器将压缩视频变成帧,nvstreammux 汇聚多路帧,nvinfer 执行 TensorRT 推理,nvtracker 在帧间保持目标 ID,后续组件再做显示、编码或消息输出。每个组件都可能有自己的队列和缓存策略。

1
2
3
4
5
6
camera / RTSP source
-> hardware decoder
-> nvstreammux (batch, timeout)
-> nvinfer (TensorRT engine)
-> nvtracker / custom postprocess
-> ROS 2 bridge (objects, original timestamp)

对机器人来说,最值得盯住的是 nvstreammux 的批大小与等待超时。较大的 batch 可能提高 GPU 利用率,却会让一条已经到达的前视帧等待其他相机;这是视频分析服务可以接受、移动机器人却常常不能接受的取舍。

机器人真正要看的三个时间

至少区分三个指标:

  • 吞吐量:每秒处理多少帧,适合评估资源上限。
  • 推理延迟:帧进入模型到得到网络输出的时间。
  • 感知年龄:控制或规划使用结果时,距原始相机曝光已过去的时间。

例如 30 FPS 的系统可能每帧推理只需 12 ms,但若 pipeline 累积了 8 帧,规划器拿到的其实是 250 ms 前的世界。对移动底盘而言,这比模型快慢更危险。

1
2
perception_age = planner_consume_time - camera_capture_stamp
pipeline_wait = inference_start - camera_capture_stamp

所有进入 ROS 2 的检测、跟踪和深度结果都应保留原始采集时间戳,而不是只在发布时填 now()。否则日志会把“旧帧很快发布”误判成“新帧很快处理”。

视频管线不能替控制器做决定

ROS 2 bridge 最好发布结构化、有限的语义信息,例如目标类别、2D/3D 位置、置信度、帧号和时间戳。任务层再根据对象年龄、置信度和当前地图决定是否执行动作。控制器不直接依赖视频管线存活:当相机/DeepStream 进程异常时,应获得明确的失效状态,进入低速、暂停或停车策略。

多相机校准也不可省略。相机间的外参、曝光时间和时钟不同步,会让跟踪器把同一目标看成两个目标,或让后续三维融合出现错位。先用单相机跑通时间戳和失效处理,再增加 batch 和更多传感器,排障成本会低很多。

参考:DeepStream SDK Documentation · GStreamer Application Development Manual

证据边界:本文描述的是多相机管线的排查方法,没有给出某块 Jetson、某个 batch 大小或某种相机组合的实测 FPS 和 P99。具体取值应以目标设备的时间戳、队列长度和温度稳定后的记录为准。