VINS 直连灰度 DMA-BUF:为什么不再走 ROS 图像 Topic
VINS 的视觉前端最终需要的是灰度图。如果相机流水线已经产出 NV12,Y 平面本身就是灰度数据。继续把它转成 BGR、封装成 ROS Image、跨进程复制,再由 VINS 转回灰度,是一条成本很高的绕路。
原来的数据路径
Camera buffer
→ BGR materialization
→ ROS Image serialization
→ ROS transport / copy
→ cv_bridge
→ grayscale conversion
→ VINS frontend这条路径的优点是接口通用、调试方便,但在多目、固定频率的板端系统里,CPU 和内存带宽会被格式转换与复制持续占用。
目标路径
Camera Pipeline
→ NV12 DMA-BUF atlas
→ 取 Y plane 的 geometry + fd
→ Unix SOCK_SEQPACKET + SCM_RIGHTS
→ VINS mmap 为 CV_8UC1
→ feature tracking图像不再通过 ROS topic 进入 VINS,但这不意味着整个 VINS 都脱离 ROS:
- IMU 仍然通过 ROS 输入;
- odometry 与状态仍然通过 ROS 输出;
- 可选的调试图像 topic 可以继续保留;
- 只把高带宽、可零拷贝的图像主链迁出 ROS。
为什么使用 atlas
多目系统可以把同侧多个方向的图像纵向放在一张 atlas 中。VINS 根据 ROI 的 Y 区间还原 camera id 和局部坐标,不必为每个方向单独物化一张新图。
left atlas right atlas
┌──────────┐ ┌──────────┐
│ camera 0 │ │ camera 1 │
├──────────┤ ├──────────┤
│ camera 2 │ │ camera 3 │
├──────────┤ ├──────────┤
│ camera 4 │ │ camera 5 │
├──────────┤ ├──────────┤
│ camera 6 │ │ camera 7 │
└──────────┘ └──────────┘四个方向仍共享同一个机体状态。每个相机通过标定外参从 T_world_body 得到自己的 pose,各方向的视觉 residual、IMU 因子和边缘化先验共同进入同一个滑窗优化,而不是四套独立 VINS 最后再拼接。
跨进程 packet 至少要带什么
struct StereoGrayPacketV1 {
uint32_t magic;
uint16_t version;
uint64_t sequence;
uint64_t left_timestamp_ns;
uint64_t right_timestamp_ns;
PlaneDesc left;
PlaneDesc right;
};
struct PlaneDesc {
uint32_t width;
uint32_t height;
uint32_t stride;
uint32_t offset;
uint32_t size;
};接收端要验证 magic、版本、sequence、左右时间差、geometry、stride、offset 与溢出边界。对于 fd,还要在 mmap 前用 fstat 或等价方式确认真实容量足够覆盖 offset + stride × height。
功能跑通不等于生命周期安全
如果发送端在 SCM_RIGHTS 发送 fd 后立即归还 buffer,慢消费者可能读到已经被相机重新写入的内容。表面上 sequence 正常、频率正常,像素却可能属于下一帧,甚至出现撕裂。
因此,完整迁移必须补上 ACK/lease retention 或等价的回收协议。减少复制是性能优化,定义所有权才是生产化。
验收要点
- VINS 不再订阅左右 ROS 图像 topic;
- IMU 输入与 odometry 输出仍保持兼容;
- fd、geometry、stride 与 capacity 全部校验;
- 断开与重连后 sequence 连续性可解释;
- 慢消费者不会读到已复用 buffer;
- 性能收益来自板端 A/B,而不是只看代码路径。
这类迁移的价值不只在于“绕开 ROS”,而是让高带宽图像数据遵循硬件原生内存路径,同时把 ROS 留在它更擅长的控制、状态和低带宽消息层。