
这一期Airsim动态不聊那些炫酷的视觉算法先讲一个最基础但也最容易让人卡壳的事把AirSim和ROS真正“接上”。AirSim是微软开源的无人机/汽车仿真环境底层跑在Unreal Engine上而ROS是机器人领域最常用的中间件框架。问题是AirSim并不会主动把画面、里程计、GPS发布成ROS话题ROS节点也不可能直接调用AirSim的虚幻世界接口。中间的桥梁就是标题里说的这个ROS Wrapper。这篇就记录我从环境准备、编译、启动到排障的完整过程适合那些正准备把AirSim接入ROS做感知、规划或控制仿真又不想在接口上浪费太多时间的朋友。1. wrapper这个“包装器”到底在AirSim和ROS之间干了什么很多第一次接触AirSim ROS Wrapper的人会把wrapper理解成一个“驱动包”装上就能用。理解其实不够准确。它更像是一个“翻译官快递员”把AirSim私有通道上的数据翻译成ROS世界里的标准消息再分发到对应的话题上同时把ROS侧发来的控制指令翻译成AirSim能听懂的RPC请求。1.1 一个Simulator、两个世界RPC协议与ROS话题AirSim本身并不依赖ROS。它在Unreal Engine里完成物理仿真、相机渲染、激光雷达扫描对外提供的是一套基于TCP Socket的RPC服务默认监听本机41451端口消息序列化走MsgPack。你可以用官方Python SDK也可以用C SDK去连这个RPC服务拿图像、拿状态、发指令。ROS那边则是另一套逻辑所有数据都抽象成topic、service、tf这些概念。一个用ROS写的自动驾驶算法只知道去订阅/airsim_node/vehicle_1/odom它不知道也根本不需要知道RPC协议和MsgPack是什么。ROS Wrapper做的事情就是把这两套世界缝起来。它内部创建一个RPC客户端连上AirSim拿到传感器数据后封装成ROS消息发布到以/airsim_node/vehicle_1/...开头的话题下。同时它订阅ROS侧的控制话题把TwistStamped、PoseStamped这类指令转换成AirSim的RPC调用。也就是说wrapper本质上就是一个运行在ROS环境里的“桥接节点”。1.2 为什么不直接写Python脚本还要用官方Wrapper有这个疑问很正常。我刚上手AirSim时直接用Python API写过一个桥接脚本循环里调client.getImage()、client.getMultirotorState()再用rospy.Publisher发出去。小demo跑通没问题但一旦传感器多了就开始难受。官方Wrapper的价值主要体现在几个方面多传感器同步图像、IMU、里程计、GPS是一套完整的状态快照不是各发各的。多机支持通过vehicle_name参数区分同一份仿真环境里跑多台车/无人机每个wrapper实例只对指定的那台车辆操作。性能更好C/RPC客户端比Python逐帧回调要高不少尤其在640x480以上分辨率多路相机同时出图Python脚本的瓶颈很明显。参数化配置可以用ROS的参数服务器动态配置接入现有launch体系方便和大系统整合。当然如果你只是临时验证一个思路Python脚本也够。但一旦你的算法要同时用到多路图像、点云、TF坐标你就会觉得官方wrapper把脏活累活全包了是件多省心的事。1.3 ROS1和ROS2两套包装器并存还有个常见误解是“一个wrapper包ROS1和ROS2都能用”。实际上AirSim仓库里ros/目录给ROS1ros2/目录给ROS2两套代码之间存在不少差别构建系统一个是catkin_make一个是colcon build消息类型和launch方式也不一样。后面我会先以ROS1 Noetic版为主线讲再单独说说ROS2 Humble版的情况因为两者遇到的问题并不完全相同。2. 动手前先对“版本日历”这套组合我踩过的最少安装这种带ROS依赖的工程最怕的就是版本错位。我见过太多人一上来就git clone然后直接编译报错了再回头查版本结果最后发现是ROS版本和Ubuntu版本不匹配。这一步别偷懒。2.1 系统和ROS版本怎么选AirSim支持的ROS版本跟ROS官方LTS走。我这几年用下来比较稳的组合是操作系统ROS版本AirSim目录推荐度Ubuntu 18.04ROS Melodicros/能用但环境偏老Ubuntu 20.04ROS Noeticros/最推荐资料最多坑最少Ubuntu 22.04ROS2 Humbleros2/新项目推荐但Wrapper问题要自己多踩Ubuntu 24.04ROS2 Jazzyros2/部分版本支持不建议折腾如果你跟我一样是拿来做算法验证不要在一台机器上同时装ROS1和ROS2。不是反对多版本共存而是每次都要小心翼翼地去source不同环境很容易乱。我早期在20.04上装了Noetic后又装Foxy结果每次开终端都要确认一遍环境变量后来重装了系统才彻底消停。2.2 ROS安装的两种方式ROS本体安装我最常用还是官方apt源一步步按官方wiki来。但国内网络环境有时候拉取apt源会很慢或者出现“无法定位软件包”之类的问题。这时候社区里的一键安装脚本就很有用比如不少人提到的小鱼一键安装。这类脚本本质上是替你把apt sources.list、rosdep这些步骤串起来省去手动配源的麻烦但它只解决ROS主体安装AirSim本身的依赖还得自己处理别指望一条命令全搞定。装完ROS后务必确认rosversion -d能正常输出再继续。2.3 AirSim源码编译与UE环境AirSim本身需要编译这个不能跳过。先克隆仓库git clone https://github.com/microsoft/AirSim.git cd AirSim ./setup.sh ./build.shsetup.sh负责下载UE依赖和Python包build.sh编译AirSim的核心库。这里注意build.sh只是编译库文件不会生成一个可运行的UE项目。你还需要一份AirSim模拟器环境也就是一个编译好的Unreal工程。最省事的办法是直接用官方release里的Blocks示例环境或者自己按文档生成一个UE4/UE5工程。For wrapper调试来说Blocks就够用了没必要自己搭一个地图。编译过程中setup.sh会在根目录拉两个UE插件版本比如4.27或5.x需要科学网络环境的人可能会卡住。这里我不展开网络问题只说一句如果setup脚本拉取依赖失败优先检查网络源和代理配置这是最常见的卡点。2.4 冒烟测试先让模拟器自己跑起来别急着编译wrapper。先做一次“模拟器冒烟测试”确认AirSim本身能跑、RPC端口能连上否则后面Wrapper连不上你根本不知道是模拟器的问题还是Wrapper的问题。方法是启动Blocks模拟器然后在另一个终端里运行官方Python脚本pip install airsim python3 -c import airsim; c airsim.MultirotorClient(); c.confirmConnection(); print(RPC OK)如果这行脚本能输出RPC OK说明AirSim的RPC服务正常端口无误。如果这里就飘红先别往下走去查模拟器进程和防火墙。这个测试只需10秒钟但能帮你把后面一整轮排障时间省掉。3. 编译AirSim ROS WrapperROS1 Noetic版完整操作假设你已经完成了ROS Noetic安装、模拟器能正常启动、Python SDK能连上RPC。现在正式进入wrapper的编译环节。3.1 认识ros目录里的包结构AirSim源码里的ros/目录本身就是一个完整的Catkin工作空间源码目录里面包含的包大致有airsim_ros_pkgs元包负责依赖声明。airsim_ros核心实现wrapper节点和消息定义都在这里。airsim_ros_tutorials示例脚本包括怎么发控制指令的demo。如果你打开airsim_ros的CMakeLists.txt会发现它依赖AirSim的C库还需要roscpp、std_msgs、geometry_msgs、sensor_msgs、nav_msgs等常规ROS包。这些依赖在ROS Noetic桌面版安装时一般已经带上缺哪个补哪个就行。3.2 创建工作空间与符号链接source /opt/ros/noetic/setup.bash mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src ln -s ~/AirSim/ros airsim_ros_pkgs这里我用的是符号链接而不是直接cp。好处是以后AirSim仓库更新wrapper代码也会同步更新不需要手动重复拷贝。如果你用的是catkin_make直接执行cd ~/catkin_ws catkin_make如果偏好catkin build也可以逻辑一样但需要先pip install catkin-tools。我建议新手直接用catkin_make少装一样是一样。3.3 让CMake找到AirSim库编译时最典型的报错是Could not find a package configuration file provided by AirSim这是因为wrapper的CMakeLists.txt里执行了find_package(AirSim REQUIRED)但CMake不知道去哪里找AirSim的配置文件。AirSim编译完成后在~/AirSim/cmake目录下应该能看到AirSimConfig.cmake之类的文件。你需要在构建时把路径告诉CMakecd ~/catkin_ws catkin_make -DAirSim_DIR:PATH$HOME/AirSim/cmake如果这个目录下没有找到配置文件大概率是前面./build.sh没跑完整回头重新编译AirSim。还有一种办法是把AirSim路径挂进CMAKE_PREFIX_PATHexport CMAKE_PREFIX_PATH$HOME/AirSim/cmake:$CMAKE_PREFIX_PATH然后再跑catkin_make。3.4 编译报错三连缺路径、缺包、缺依赖把我在编译过程中遇到的三个报错直接列出来你们对照处理报错1找不到AirSim配置办法上面已经写了传-DAirSim_DIR:PATH。注意如果你在catkin_make里传参后续每次重新编译都要带所以我更习惯把它写进环境变量一劳永逸。报错2找不到msgpack相关头文件Wrapper的RPC底层依赖msgpack-c。可以通过系统包安装sudo apt install libmsgpack-dev然后重新编译。有些老版本还要求Python侧的msgpack-rpc-python顺手也装一下pip install msgpack-rpc-python报错3geographiclib库缺失GPS消息里需要地理坐标转换编译时会依赖GeographicLib。装上就行sudo apt install libgeographic-dev编译成功后会生成devel/setup.bashsource一下source ~/catkin_ws/devel/setup.bash rospack find airsim_ros_pkgs如果最后一行输出了包路径wrapper编译这关就算过了。4. 启动Wrapper并验证数据流从话题到图像再到控制编译通过只是开始真正跑通数据流才算是“装好了”。这一节我希望你跟我一样按顺序做一遍不要跳。4.1 先写一个能跑的最小settings.jsonAirSim在启动时会读取settings.json这个文件的路径通常在~/Documents/AirSim/settings.jsonLinux下是/home/用户名/Documents/AirSim/settings.json。我第一次没配置直接启动结果Wrapper能连上但摄像头话题就是空。后来才发现是相机没在settings里开。一个能跑通的最小配置长这样{ SettingsVersion: 1.2, SimMode: Multirotor, ClockSpeed: 1, RpcPort: 41451, CameraDefaults: { CaptureSettings: [ { ImageType: 0, Width: 640, Height: 480, FOV_Degrees: 90, CompressMode: 0 } ] }, Vehicles: { vehicle_1: { VehicleType: SimpleFlight, AutoCreate: true } } }几个关键点RpcPort必须和Wrapper连接端口一致CompressMode设为0也就是未压缩图像能让后面图像话题的排查省掉一大半麻烦AutoCreate设为true启动模拟器时会自动创建vehicle_1这架无人机。4.2 启动顺序和launch命令顺序很重要先启动模拟器等世界加载完再启动ROS节点。因为Wrapper节点在启动时会立刻尝试连接RPC端口模拟器没起来就会大量刷“连接失败”日志。虽然它内部有重连机制但看着那一屏红字真没必要。模拟器启动后新建一个终端加载ROS环境后执行source /opt/ros/noetic/setup.bash source ~/catkin_ws/devel/setup.bash roslaunch airsim_ros_pkgs airsim_node.launch如果一切正常终端里会出现节点注册信息不会刷红色报错。多车场景下可以用参数指定车辆名roslaunch airsim_ros_pkgs airsim_node.launch vehicle_name:vehicle_14.3 rostopic list里应该看到什么启动成功后另开终端执行rostopic list你应该能看到一组以/airsim_node/vehicle_1/开头的话题大致包括/airsim_node/vehicle_1/odom里程计/airsim_node/vehicle_1/gpsGPS定位/airsim_node/vehicle_1/imu惯性测量单元/airsim_node/vehicle_1/camera_1/RGB可见光相机图像/airsim_node/vehicle_1/camera_1/Segmentation分割图/airsim_node/vehicle_1/camera_1/Depth深度图/airsim_node/vehicle_1/lidar_1/PointCloud2激光雷达点云如果settings里开了雷达看到这些话题存在说明Wrapper已经成功订阅了AirSim的数据流剩下的就是验证内容正确性。4.4 最小验证看图发速度指令先验证图像rosrun rqt_image_view rqt_image_view /airsim_node/vehicle_1/camera_1/RGB如果能看到模拟器机载相机的画面整个数据链路已经从“AirSim - RPC - Wrapper - ROS话题”完整打通了。再验证控制链路。在ROS侧发布一个速度指令看模拟器里的无人机是否响应。需要注意很多版本的Wrapper启动时并不会自动开启API控制需要先调相关服务或通过Python SDK执行enableApiControl。我习惯先在Python侧确认import airsim client airsim.MultirotorClient() client.enableApiControl(True) client.armDisarm(True) client.takeoffAsync().join()起飞后再回到ROS侧发布指令rostopic pub -1 /airsim_node/vehicle_1/cmd_vel geometry_msgs/TwistStamped {header: auto, twist: {linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}}如果无人机往前飞了控制链路也通了。到这里wrapper才算是真正“安装完成”。5. 安装和调试中的四个坑每个都不只花了一小时这个部分才是本文真正的精华。下面每个问题都是我在实际操作中踩过的不是文档里能翻到的。5.1 RPC连不上先ping再查端口最后看启动顺序Wrapper日志里报RpcClient exception或者Python脚本confirmConnection()直接挂别急着怀疑wrapper配置。我总结了一个“三层排查法”第一层模拟器进程是否还在ps aux | grep AirSim。有时候UE工程会因为崩溃留下僵尸进程或者你启动的是另一个不相关的项目。第二层端口是否在监听netstat -tlnp | grep 41451。如果端口没起来说明AirSim的RPC服务没启动成功去查settings.json里的RpcPort是不是和预期一致。第三层用Python SDK实测连接client.ping()。如果Python都连不上基本跟wrapper无关问题在模拟器侧反过来说Python能连上而wrapper连不上那才需要去看wrapper的环境变量和版本。还有一个特别容易忽略的顺序问题先在settings.json里写了RpcPort但模拟器是在修改前启动的端口还是旧值。改完配置一定重启模拟器。5.2 find_package找不到AirSimCMake路径问题这个我在第3.3节提过但值得单独说。很多人在编译时报错后会跑到网上去搜“AirSim find_package失败”结果搜到一堆不相关问题。我的排查经验是先看看~/AirSim/cmake目录下到底有没有配置文件。如果没有说明./build.sh没跑完或者中途报错但你没注意到。这时候重新执行./build.sh盯到最后一个输出。如果目录里有配置文件但编译还是找不到多半是catkin_make没有把这个路径传给CMake老老实实加上-DAirSim_DIR:PATH$HOME/AirSim/cmake再编译一次。5.3 图像有话题但黑屏或灰屏这个话题让我一度以为相机坏了。启动模拟器后从UE窗口看一切正常但ROS侧图像就是灰蒙蒙一片或者rqt_image_view里提示解码失败。根本原因是图像编码类型没对上。AirSim输出的图像如果设成CompressMode: 0是原始的BGR/RGB数据话题类型一般是sensor_msgs/Image但有些版本默认会开压缩此时图像数据是JPEG编码的ROS侧可能需要订阅compressed后缀的话题或者先通过image_transport做解压。如果你想省心在settings.json里把CompressMode设为0订阅原始图像话题。这样虽然带宽大一些但最少坑。5.4 多机场景下所有飞机都在动同一个多机仿真时wrapper如果每个车都开着默认vehicle_name结果就是所有数据都在读第一个车发指令也会串台。解决方法是每个车辆单独启动一个launch并显式指定vehicle_name。同时settings.json里每个车辆都得有独立的命名Vehicles: { vehicle_1: { VehicleType: SimpleFlight, AutoCreate: true }, vehicle_2: { VehicleType: SimpleFlight, AutoCreate: true } }然后分别在两个终端里roslaunch airsim_ros_pkgs airsim_node.launch vehicle_name:vehicle_1 roslaunch airsim_ros_pkgs airsim_node.launch vehicle_name:vehicle_2这样两个wrapper节点会分别连接各自的RPC客户端数据和控制互不干扰。这个习惯从单机阶段就要养成别等上车队了再改。5.5 附加TF和坐标系如果你的算法需要用到tf注意wrapper发布的是从world到vehicle_1的TF关系。如果后续你在Rviz里看不到模型或坐标错乱优先检查wrapper是否启动了TF发布以及坐标系名称是不是和你算法里写的一致。这一类问题不是安装范畴但会让你以为“wrapper没装好”。6. 换到ROS2Humble版Wrapper的差异体验如果你的项目起点就是ROS2比如用的HumbleAirSim仓库里对应的ros2/目录就是另一套玩法了。6.1 构建方式和启动命令ROS2版不再用catkin_make而是用colcon。工作空间创建方式类似source /opt/ros/humble/setup.bash mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ln -s ~/AirSim/ros2 airsim_ros_pkgs cd ~/ros2_ws colcon build启动时也用ROS2的格式source install/setup.bash ros2 launch airsim_ros_pkgs airsim_node.launch.py话题列表大体沿用/airsim_node/vehicle_1/...的命名控制指令发布工具从rostopic pub换成ros2 topic pub。6.2 QoS与图像话题的坑ROS2和ROS1最大的区别之一是QoS策略。ROS1里订阅者和发布者只要话题名对上就行ROS2里还要求QoS兼容。我第一次在ROS2版里用rqt_image_view看图像话题明明在却没有图像折腾半天发现是订阅端的QoS Profile和发布端不匹配。这种问题的排查思路是用ros2 topic info /话题名 --verbose查看发布者的QoS参数然后在订阅时显式设置reliability、durability等参数。如果你只是自己用把两者都设成best_effort和volatile基本能解决90%的“有话题没数据”问题。6.3 我的取舍建议虽然我上面吐槽了ROS2版的一点问题但如果是从零开始的项目我还是建议直接用ROS2 Humble。原因为不是ROS1不够好而是新的算法库、新的学习资料都在向ROS2迁移AirSim的ROS2 wrapper也在持续迭代。只是你要有心理准备遇到问题时网上可参考的讨论确实比ROS1版少。我自己的习惯是跑通ROS1版作为“翻译器”对照再在ROS2版里做正式开发。另外ROS2版的Wrapper在启动后会涉及节点的生命周期管理有时候你会看到节点状态不是active数据不输出需要手动调用相关接口激活。不同分支行为不一样遇到时先查版本别急着重编译。装wrapper这件事难吗其实不难但它的坑都在细节里。只要你按着“先验证AirSim再编译wrapper最后验证数据流”的顺序走大部分问题都能提前挡掉。我个人的经验是永远保留一个能跑通的最小settings.json别随便把官方的复杂配置全塞进去编译前多花10分钟确认依赖和版本比编译挂了再一条条谷歌省太多时间。wrapper跑起来之后你就可以专心做真正想做的事了——无论是接感知算法、写控制策略还是拖一群无人机做多机实验。