ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ROS2多路相机视频流转换实战:用image2rtsp实现RTSP推流

ROS2多路相机视频流转换实战:用image2rtsp实现RTSP推流 在机器人项目里折腾过多路相机的人几乎都撞过同一堵墙话题里面的画面明明好好的可真要把它拿去给上位机看、给浏览器看、给另一个工位上的显示器看立马抓瞎。rqt_image_view开个三四路就卡出幻灯片效果OpenCV窗口自己写个转发服务又得重新处理编解码、并发、跨平台最后做出来的东西只在自己电脑上跑得动。我最近在一个巡检机器人项目里做多路相机采集四路USB相机加一路网络相机被“视频流怎么高效率地出ROS2”这个问题卡了好几天最后用image2rtsp这个包把实时视频流转换的问题彻底理顺了。这篇就把我的完整踩坑过程和最终方案写出来项目背景、安装、配置、参数、故障排查一条龙给同样被这问题卡住的朋友做个参考。1. 多路相机采集为什么要专门做视频流转换1.1 从话题到RTSP多路画面“出不去”的痛点先说说我实际遇到的问题。机器人本体上跑着ROS2装了好几组相机图像话题在系统里刷得飞起ros2 topic hz /camera_0/image_raw测出来稳定在30帧。但问题来了调试的时候我在机器人旁边可以通过rqt_image_view直接看可一旦我需要站在两三米外的调试电脑上看画面或者把画面投到车间大屏甚至让另一个城市的同事远程瞄一眼这条路就断了。按常规思路很多人会开一个OpenCV窗口然后通过网络把画面传出去。但真正多路相机采集场景下这条路有四个很现实的问题第一多路同时imshow的话每路视频都要单独跑一个HTTP或者Socket服务代码量不小而且每一路都是独立进程资源管理、断线重连、端口分配全得自己写。第二OpenCV默认走的是帧内编码的MJPEG或者原始帧带宽占用高得离谱四路1080p在Wi-Fi下基本别想流畅。第三跨平台观看麻烦。想在手机上瞄一眼想在浏览器里直接开想用VLC这种通用播放器收流用OpenCV这套得自己再套一层WebRTC或者HLS复杂度直接翻倍。第四机器人端往往资源紧张CPU要跑感知算法、导航、底盘控制如果视频转发还要占用几个核心整个系统都会受影响。我当时的判断是这事不该自己造轮子应该找一条“把ROS2图像话题转成标准视频流”的成熟路子。标准方式无非就是RTSP、RTMP、WebRTC、HLS这几种。RTMP在Web端还得转HLS延迟太高WebRTC要搭信令服务器综合下来RTSP是最通用、最轻、延迟也可控的方案。所以目标就变成了把一个或多个ROS2图像话题实时编码成RTSP流让任意支持RTSP的播放器直接拉流。1.2 image2rtsp是什么一条GStreamer管道解决的事image2rtsp这个开源包简单说就是专门干这件事的。它运行在ROS2节点里订阅指定的sensor_msgs/Image话题把图像数据通过GStreamer的appsrc送入管道经过颜色空间转换、编码、封装最后通过内置的RTSP Server对外推流。推出来的地址就是标准的rtsp://设备IP:端口/路径VLC、PotPlayer、ffplay都能直接打开。这个包解决了一个核心问题你不需要关心编码器和RTSP Server的细节只要给它一个话题名和一个输出路径它就能跑起来。而且因为它底层是GStreamer编码这块的可控性非常高机器上有NVIDIA显卡就自动用nvv4l2h264enc硬件编码没有就退回x264enc软件编码编码器本身的参数也可以通过配置项透传下去。当时我对比过几个方案有基于RTP直接推流的、有基于ROS2 topic转发到另一台机器再显示的还有用NVIDIA DeepStream整套框架的。最后选image2rtsp理由有三个第一它够轻不需要部署一整套DeepStream第二它上游就是GStreamer生态扩展能力强第三它支持多话题在一个RTSP Server里以不同路径输出也支持把多路画面拼成一个mosaic大图刚好覆盖多路相机采集的两种观看需求。1.3 什么时候该用、什么时候别硬上不是所有项目都需要引入视频流转换。如果你的应用只在单机上调试rqt_image_view就够如果你需要超低延迟操作视觉反馈比如机械臂实时视觉伺服RTSP这种走编码器的方案就不合适延迟再低也比不上共享内存直读话题。更好的选择是共享内存传输。我个人的经验边界是这样的需要跨设备观看、需要多人同时看、需要把画面接入现有视频系统时用image2rtsp这类方案是对的。纯本机显示且延迟敏感直接rqt_image_view或者共享内存方案。需要做算法分析那应该直接在原始图像话题上做不要从RTSP流里拿数据去跑算法绕一圈没有意义。在巡检机器人这种场景里“观看类应用”才是视频流转换的主战场把图像话题转成RTSP流之后地面站、Web端、录像系统都能直接消费互不影响这个价值非常大。2. 环境准备ROS2版本、GStreamer依赖与image2rtsp安装2.1 ROS2版本选择和安装建议image2rtsp对ROS2的版本要求不算苛刻我在Humble和Jazzy上都跑通过。如果你的系统是Ubuntu 22.04用ROS2 Humble最稳妥如果是Ubuntu 24.04那就用ROS2 Jazzy。这两个都是LTS版本社区资料多遇到问题也容易搜到答案。安装ROS2本身这里不多说网上一键脚本很多新手直接用自动化脚本装也能省不少事。装完之后务必确认环境变量没问题一个常见的坑是开了新终端后ros2命令找不到大概率是没执行source /opt/ros/distro/setup.bash或者没写进~/.bashrc。我建议装完第一时间跑一下ros2 doctor把环境问题先暴露出来别等后面推流出问题了再回头查环境。另外强烈建议把colcon装好因为image2rtsp是源码编译安装的不是apt直接能装的包。sudo apt install python3-colcon-common-extensions2.2 安装GStreamer相关依赖image2rtsp依赖GStreamer的开发库和常用插件。我第一次编译的时候就是少了几个插件包编译能过但跑起来之后GStreamer的pipeline起不来报了一个很含糊的“could not link”错误排查了半天才发现是插件缺失。所以这里先把依赖装全sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good sudo apt install gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly sudo apt install gstreamer1.0-tools如果你用的是NVIDIA Jetson或者有NVIDIA独立显卡建议再确认一下GStreamer能否识别到硬件编码器。以Jetson为例装完系统自带插件后可以用下面这条命令验证gst-inspect-1.0 | grep nvv4l2h264enc能输出nvv4l2h264enc说明硬件编码器已经就绪。如果是台式机带N卡通常还需要装对应的驱动和GStreamer插件版本这部分不同显卡差异比较大最好先跑一下这条命令确认。2.3 拉取并编译image2rtsp接下来把源码拉下来编译。个人建议不要在根用户下直接编译用普通用户即可mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src git clone https://github.com/radarhere/image2rtsp.git cd ~/ros2_ws rosdep install --from-paths src --ignore-src -r -y colcon build --symlink-install source install/setup.bashcolcon build的时候如果提示找不到某个package先检查rosdep install是否执行成功。另外因为image2rtsp依赖rclcpp和sensor_msgs编译前确保这些基础依赖都在通常装了完整版ROS2 Desktop的不会有问题但如果装的是ROS2 Base版需要单独补一下ros-distro-sensor-msgs和ros-distro-rclcpp。编译完成后验证一下节点是否注册成功ros2 pkg list | grep image2rtsp能看到包名说明编译安装成功可以进入下一步了。3. 多路相机推流实操从单路跑通到八路同出3.1 预备工作确认图像话题与相机驱动推流之前先把相机话题搞清楚。我项目里用usb_cam驱动USB相机用v4l2_camera或者自定义SDK驱动网络相机。不管哪种先确认话题名、消息类型、帧率、分辨率ros2 topic list ros2 topic info /camera_0/image_raw --verbose ros2 topic hz /camera_0/image_raw这一步有两个信息很关键一是话题名后面配置image2rtsp的ros_image_topic参数不能写错二是话题的QoS策略image2rtsp默认按sensor_dataQoS订阅如果你相机的发布端改成了reliable后面会出现订阅不到消息的怪问题这个在第4部分详细展开。我在项目里习惯把所有相机话题统一命名成/cameras/id/image_raw的格式比如/cameras/cam0/image_raw、/cameras/cam1/image_raw。这样后面配置image2rtsp多路参数时会清爽很多也能避免不同驱动默认话题名冲突的问题。3.2 单路推流最快验证完整体链路先别急着上多路拿一路相机把链路跑通再说。在ROS2环境里启动image2rtsp节点最简单的命令行方式ros2 run image2rtsp image2rtsp --ros-args \ -p ros_image_topic:/cameras/cam0/image_raw \ -p rtsp_server_path:/0 \ -p port:8554 \ -p fps:15 \ -p bite_rate:8000000 \ -p encode:h264注意参数名bite_rate这个包的作者其实拼写习惯是“bite”少一个t指的就是码率。这里我设置成8000000也就是8Mbps对应1080p15fps的H.264视频画质和码率算是比较均衡的。启动后如果一切正常终端里会打印出RTSP Server监听信息。然后在同一台机器上开VLC打开网络串流输入rtsp://localhost:8554/0能看到实时画面说明链路已经通了。如果VLC和节点不在同一台机器把localhost换成节点的IP地址。这一步验证完成后再往多路扩展。3.3 多路推流端口规划与launch文件编排多路相机采集场景下每路相机都要单独推流。image2rtsp支持在一个节点内通过参数数组配置多路话题也支持启动多个节点各推一路。我在实际项目里测试过两种方式最后还是选了“每路一个节点 独立端口”的方案。先看一下单节点多路怎么配。用YAML参数文件的话大概是这样image2rtsp: ros__parameters: ros_image_topic: [/cameras/cam0/image_raw, /cameras/cam1/image_raw, /cameras/cam2/image_raw] rtsp_server_path: [/0, /1, /2] port: 8554 fps: 15 bite_rate: 8000000 encode: h264这种方式的优点是只需要一个节点、一个端口部署简单。但有一个隐患如果某一路相机掉线或者话题异常整组流可能会受影响而且故障排查时无法单独重启某一路。所以我更推荐的方式是每路相机单独启动一个image2rtsp节点用不同的端口通过一个Python launch文件统一管理。这样看起来端口多了几个但故障隔离性非常好某一路挂了不影响其他路需要重启哪路就重启哪路。下面是我项目里实际用的launch文件骨架from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): cameras [ {id: cam0, port: 8550}, {id: cam1, port: 8551}, {id: cam2, port: 8552}, {id: cam3, port: 8553}, ] nodes [] for cam in cameras: nodes.append( Node( packageimage2rtsp, executableimage2rtsp, namefimage2rtsp_{cam[id]}, parameters[{ ros_image_topic: f/cameras/{cam[id]}/image_raw, rtsp_server_path: f/{cam[id]}, port: cam[port], fps: 15, bite_rate: 8000000, encode: h264, }], outputscreen, respawnTrue, respawn_delay5.0, ) ) return LaunchDescription(nodes)这里respawn参数值得说一下。机器人运行过程中相机偶尔断流其实很正常respawnTrue能让节点在崩溃后自动重启5秒后重新拉流比手动恢复省心太多。缺点是重启期间这一路画面会短暂中断但大部分监控和调试场景都能接受。启动之后每路相机对应的地址就是rtsp://设备IP:8550/cam0rtsp://设备IP:8551/cam1以此类推3.4 多路画面拼接mosaic模式除了每路单独推image2rtsp还支持把多路图像拼成一个画布通过一个RTSP地址同时看到所有相机。这个功能在需要“全局总览”的巡检场景里非常实用地面站操作员一个窗口就能扫完所有画面。配置方式也很简单在参数里开启mosaic然后同样以数组形式传入多个话题image2rtsp: ros__parameters: ros_image_topic: [/cameras/cam0/image_raw, /cameras/cam1/image_raw, /cameras/cam2/image_raw, /cameras/cam3/image_raw] rtsp_server_path: /mosaic port: 8554 fps: 15 bite_rate: 12000000 mosaics: true开启mosaic后节点会自动按输入话题数量排布画面布局四路就拼成2x2九路就拼成3x3不用手动指定位置。这个功能实测很好用但有两点要注意第一各路输入分辨率最好一致。如果一路是1080p一路是720p拼出来的画布会对齐到同一尺寸小分辨率的画面会被拉伸观感上会有明显差异。第二叠加后的总像素不能超过编码器能力上限。以Jetson平台的H.264硬件编码器为例单路编码的最大分辨率通常是4096x4096左右超过这个上限pipeline会起不来。比如九路1080p拼成3x3总画布是5760x3240远超上限这时候就得把单路分辨率降下来或者减少拼接路数。用软件编码器理论上限更大但CPU压力会非常高不推荐。我的建议是如果现场需要总览用mosaic模式单路分辨率降到720p总画布压缩到编码器能处理的范围内如果需要单路详细画面用多端口方案保持1080p全分辨率。两者同时开也行各占一份编码资源但要提前评估设备CPU和GPU的余量。4. 参数调优、编码选型与性能实测4.1 分辨率、码率与帧率怎么组合最稳多路相机采集场景下资源是有限的画面质量、延迟、CPU占用三者必须做取舍。我做了一套比较保守的组合稳定跑了很长时间分辨率推荐码率推荐帧率适用场景640x4801-2 Mbps10-15移动端低带宽观看、多路总览1280x7204-6 Mbps15-20常规巡检、视觉调试1920x10808-12 Mbps15-25质检、需要看清细节的画面3840x216020-30 Mbps10-15单路高精度观测一般不建议多路推帧率这块很多人有个误区觉得相机是30帧推流也一定要30帧。实际在调试和监控场景15帧足够肉眼观看而且帧率降一半带宽和编码压力差不多也降一半。如果画面里有快速运动的物体比如AGV小车跑动20帧以上会更顺滑但15帧也能看出运动轨迹。码率方面我个人经验是1080p从8Mbps起步往上加码率的画质收益会递减。码率设低了画面会出现块状模糊尤其是在画面边缘和快速变化的区域这时候不要一味降码率可以考虑降分辨率而不是降质量。4.2 CPU软编与GPU硬编的性能差异image2rtsp的encode参数可以指定编码器。默认情况下它会根据系统情况自动选择一个可用的H.264编码器但如果你有明确偏好可以手动指定。软件编码用x264enc硬件编码用nvv4l2h264encNVIDIA平台或者v4l2h264enc。在Jetson系列设备上硬件编码器的效果我非常推荐。实测下来四路720p15fps软编大约要占满4个CPU核心而同一配置换成硬编CPU占用几乎可以忽略不计编码器由GPU硬件完成。如果机器上有NVIDIA独显驱动装好的前提下GStreamer能直接调用CUDA加速的硬件编码模块。不过要注意不是所有NVIDIA显卡都支持同一个nvv4l2h264enc插件这个插件主要面向Jetson和部分专业卡消费级显卡上可能需要用nvh264enc或者其他基于NVENC的插件。具体以你系统里gst-inspect-1.0 | grep 264输出的结果为准。4.3 QoS策略图像话题订阅不到的隐形原因这是我在多路相机项目里踩过最隐蔽的一个坑单独拎出来说。ROS2的话题通信有QoS策略发布端和订阅端的QoS如果不匹配消息就会“静默丢弃”节点不报错、话题存在但就是收不到数据。相机驱动发布图像话题时大多数用的都是sensor_dataQoS也就是best_effort可靠性、短队列。而某些通用节点默认用reliable策略这就导致两者不兼容。image2rtsp默认按sensor_data策略订阅图像话题所以正常情况下和相机驱动是匹配的。但如果你做了话题转发、桥接、或者用ros2 run的方式从其他网络域接收话题QoS策略就可能被重置。判断方法很简单ros2 topic info /cameras/cam0/image_raw --verbose输出里会显示Publisher和Subscriber各自声明的QoS配置。如果发现不一致检查发布端驱动是不是被配置成了reliable或者订阅端是不是有额外参数覆盖。在launch文件里也可以通过qos_overrides参数强制设置Node( packageimage2rtsp, executableimage2rtsp, parameters[{ ros_image_topic: /cameras/cam0/image_raw, rtsp_server_path: /0, port: 8550, fps: 15, bite_rate: 8000000, qos_overrides: { /cameras/cam0/image_raw: { reliability: best_effort, durability: volatile, } }, }], )这段配置的作用是强制订阅端使用best_effort策略和相机驱动保持一致。不过要注意qos_overrides这种方式对不同类型的节点支持程度不一样如果你的节点里用的是rclcpp::QoS而不是rclcpp::SensorDataQoS可能需要改代码或者在参数里直接指定。这个属于比较深的问题自己改源码时留意一下rclcpp的QoS构造函数签名就行。5. 排查实录多路推流最常见的几类故障5.1 问题速查表这几类问题是我在项目里真实遇到过的整理成表格方便对照排查现象可能原因排查与解决VLC打开RTSP地址黑屏话题名错误、图像话题没数据ros2 topic list确认话题名ros2 topic hz确认数据流画面延迟越来越高网络带宽不足、VLC缓存过大降低码率或帧率VLC播放器中降低网络缓存值多路画面不同步各路独立编码导致时钟偏差需要同步时改用mosaic模式多路在同一pipeline内输出节点启动后马上退出GStreamer插件缺失、编码器不支持查看节点日志用gst-inspect-1.0检查指定编码器是否存在某一路突然断流相机掉线、USB带宽被抢占检查相机驱动日志先断掉其他路再测带宽CPU占用异常高软编导致换成nvv4l2h264enc硬件编码或降低分辨率帧率话题存在但收不到图QoS策略不匹配ros2 topic info --verbose查看双方QoS配置5.2 三个容易被忽略的“非软件”坑除了软件配置多路相机采集项目里还有几个硬件和系统层面的坑单纯调参根本解决不了这里专门说一下。第一个是USB带宽问题。多路USB相机如果接在同一个USB控制器上带宽是共享的。USB3.0理论带宽5Gbps实际可用大概1.5-2Gbps而四路1080p原始图像如果不经过压缩直接传输每路大概要占200-400Mbps几路加起来很容易把USB总线打满。表现就是相机帧率集体下降图像一卡一卡。排查方法是插几路到不同的USB控制器上或者把相机的输出分辨率降到720p甚至降到10fps减轻链路压力。第二个是网卡中断绑定问题。如果推流节点和拉流客户端走的是同一个千兆网口当带宽接近极限时网络中断处理可能抢占CPU影响编码线程。实测下来多路1080p推流时建议用独立的千兆或者更高带宽的网卡或者把管理网络和视频网络分开哪怕只是逻辑上的VLAN隔离也能让问题域更清晰。第三个是供电问题。多路USB相机如果通过同一个HUB供电电流不足会导致相机随机掉线。这个在工业现场尤其常见因为现场用线长压降大。我当时有几路相机不定期断流排查了很久最后发现是HUB供电不稳。解决办法是换带独立供电的工业级HUB或者用POE供电的IP相机替代USB相机供电和通信分开稳定性会好很多。写在最后image2rtsp这个包帮我解决了一个很实际的问题把ROS2里零散的图像话题变成一组标准、稳定的RTSP视频流让多路相机采集的画面能够方便地流转到各个终端。如果你也在做类似的项目我个人的建议是先单路跑通再上多路先软编调通再切硬编先本地验证再跨网络部署。每一步都留足排查时间。视频流这块的问题很多时候不是软件不行而是链路里的某个小环节没对齐比如QoS、带宽、供电。希望这篇实操记录能帮你少踩几个坑。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进