无人机屋顶降落

屋顶降落的 ROS 侧实现:订阅桥接层发布的激光点云,检测水平屋顶平面, 在屋顶轮廓上取最大内切圆圆心作为安全降落点,控制无人机自主飞抵其上方后 垂直降落在屋顶上。

源码位于 src/air/rooftop_landing, 模块的完整说明(运行步骤、参数、话题、常见问题)见 README.md。


1. 它解决什么问题

无人机要「落在屋顶上」,比落在平坦地面难得多:

  • 屋顶面积有限,落点必须远离边缘,否则容易撞到女儿墙或直接掉下去;
  • 屋顶上可能有天窗、通风管、水箱等障碍,地面点云里还有大片「地面」平面的干扰;
  • 需要先识别出哪个平面才是屋顶,再决定落在哪里。

本模块的做法是:

  1. 用 RANSAC 从点云里拟合平面,只保留接近水平的面;
  2. 用平面高度与面积把屋顶和地面区分开 —— 默认以检出的最低水平面(地面) 为基准,比它高 ground_margin 米以上才算屋顶,因此不要求地图原点落在地面 高度上(换地图实测过的坑:某地图原点在地面之上约 25 m,固定阈值会把所有 候选面都滤掉);
  3. 先把平面上互不相连的点群拆开(连通域聚类,避免少数远处的噪点把轮廓撑大), 再逐块投影成 2D 轮廓(凸包),用 polylabel 求最大内切圆圆心 —— 即离屋顶边界最远的点,也就是几何上最安全的落点;
  4. 按「最高 / 最近 / 最宽敞」策略选出目标屋顶,下发目标点飞抵其上方, 再以恒定小速度垂直下降,直到高度接近屋顶。

2. 架构位置

本模块不直接连仿真器,只与桥接层交互:

Windows 宿主                                    Ubuntu 客户机
┌──────────────────┐                        ┌────────────────────────────┐
│ CarlaAir / AirSim│◀── TCP 41451 ─────────▶│ carlair_ros_bridge         │
│  无人机 + LiDAR  │                        │  /uav/odom  /lidar/points  │
└──────────────────┘                        │  /uav/cmd_vel  /uav/goal   │
                                            └────────────┬───────────────┘
                                                         │ 订阅 / 发布
                                            ┌────────────▼───────────────┐
                                            │ rooftop_landing(本模块)  │
                                            │  检测屋顶 → 选落点 → 降落  │
                                            └────────────────────────────┘

连仿真器、NED↔ENU 坐标换算、速度限幅与指令超时保护都在桥接层完成,因此本模块 与键盘控制、规划器等模块共用同一条 /uav/cmd_vel 通道,互不耦合。


3. 运行

前置:按 无人机与 ROS 的桥接模块 的 2.1 节在宿主机启动 CarlaAir 仿真器,等终端输出 CarlaAir is ready.(此时 41451 端口就绪)。 仿真器窗口不要最小化,否则 AirSim 取相机会阻塞。

# 客户机侧 1:启动桥接(连仿真、发布传感器)
source ~/uav_ws/devel/setup.bash
roslaunch carlair_ros_bridge main.launch
# auto_takeoff 默认为 true:桥接层会 takeoff() 并 hover(),起飞后悬停在约 1 m

# 客户机侧 2:启动屋顶降落(作业要求的 launch 启动)
roslaunch rooftop_landing main.launch

可选参数:

roslaunch rooftop_landing main.launch safe_altitude:=6.0 prefer:=closest
roslaunch rooftop_landing main.launch z_up:=true          # 若高度约定为 ENU 上为正
roslaunch rooftop_landing main.launch use_rviz:=false     # 不开 RViz
roslaunch rooftop_landing main.launch autostart:=false    # 手动用 /rooftop/start 触发

运行时的日志会依次给出「检测到几个候选面 → 选中哪个屋顶(高度/面积/落点/内切圆半径) → 下发目标点 → 到达后开始下降 → 触地」的完整链路,便于逐段核对。


4. 话题接口

方向 话题 类型 说明
订阅 /lidar/points sensor_msgs/PointCloud2 激光点云
订阅 /uav/odom nav_msgs/Odometry 位姿与速度
订阅 /uav/status std_msgs/String 桥接状态机
订阅 /rooftop/start std_msgs/Bool 手动开始一次任务(autostart:=false 时用)
发布 /uav/goal geometry_msgs/Point 降落点上方的目标点
发布 /uav/cmd_vel geometry_msgs/Twist 降落阶段的垂直速度
发布 /rooftop/landing_point geometry_msgs/Point 选中的降落点(z = 屋顶高度,上为正)
发布 /rooftop/markers visualization_msgs/MarkerArray 屋顶轮廓与落点可视化

RViz 中打开 PointCloud2(/lidar/points) 与 MarkerArray(/rooftop/markers), 把 Fixed Frame 设为 world,即可看到点云与识别出的屋顶轮廓(橙色线框) 和降落点(绿色球体)。


5. 本地测试(无需 ROS / 仿真器)

python3 tests/test_rooftop_landing_local.py

100 项全部通过。其中「端到端感知」用合成城市场景(地面 + 楼房 + 墙面 + 离群点) 验证算法:应挑出 15 m 高的屋顶,落点接近屋顶中心,内切圆半径接近 9 m; 「凸包鲁棒性回归」另外验证了少量同高度噪点不会把轮廓撑大、相邻但不相连的 两栋楼会被拆成两个候选;「高度基准」用地图原点在地面之上约 25 m 的场景 回归「换地图后仍能挑出屋顶」,并用真机上量到的两个场景做回归: 比地面高 1.9 m 的细长条必须被默认 4.0 m 的地面余量滤掉、 只检出 1 个平面时必须不降落(而不是把地面当屋顶报 LANDED 成功)。

这套测试的意义在于:在没有显卡、没有仿真器、甚至没有 ROS 的机器上也能回归, 方便在没有条件启动整套仿真的情况下先验证算法改动。

另有 tests/test_rooftop_landing_ros_sim.py:自带一个假桥接层, 只需要 ROS 即可跑完 DETECT → APPROACH → DESCEND → LANDED 全流程,不需要仿真器 (它只能证明「本节点 + 桥接协议」这一层是通的,不等价于真实链路)。

真机链路(仿真器 + carlair_ros_bridge + 真实点云)的验证范围与结论见模块 README.md §8.3。


本文档的修改使用 AI 大模型辅助起草,作者对提交内容的正确性与完整性负全部责任。

可复现的验证方式(命令 + 预期输出,详见模块 README §8):

# 1) 本地回归:不需要 ROS / 仿真器 / GPU,只依赖 numpy
python3 tests/test_rooftop_landing_local.py
#    预期结尾:结果:100/100 通过

# 2) 闭环冒烟:需要 ROS Noetic;自带假桥接层,不需要仿真器
roslaunch rooftop_landing main.launch use_rviz:=false   # 终端 1
python3 tests/test_rooftop_landing_ros_sim.py           # 终端 2
#    预期结尾:出现过 LANDED : 是

本页不再引用任何没有给出复现步骤的环境结论。