浙江省机器人竞赛“空中机器人”赛项系列文章(二):自主飞行基础
在上一篇中,我们搭建了高度拟真的省赛仿真环境。现在,是时候让你的无人机摆脱摇杆,用代码赋予它真正的“智慧”,迈出全自主飞行的第一步。灵智实验室将继续伴随你,深入无人机的“大脑”——PX4飞控与ROS2,并解锁其“眼睛”——3D激光雷达的感知能力。
系列首篇,我们成功构建了虚拟赛场。本篇,我们将正式进入核心战场:编写自主控制代码。你将学习如何通过ROS2节点,向无人机发送精确的指令,实现从解锁、起飞、到定点飞行的全流程自动化。更重要的是,我们将引入无人机的“感知之眼”——3D激光雷达,并详解如何利用其点云数据,实现前方障碍物的实时判断,为后续的动态避障打下坚实基础。
第一部分:掌控飞控——基础自主飞行全流程
自主飞行的核心,在于让无人机理解并执行你的代码指令,而非手动遥控信号。这通过PX4的 Offboard(离板)模式 实现。在此模式下,飞控完全听从外部机载计算机(在我们的仿真中,就是你的ROS2节点)发送的轨迹或姿态设定点。
我们将基于提供的 offboard_control_lib.py控制库,展示一个完整的基础任务链。
1.1 初始化与连接
一切始于创建控制句柄。Vehicle类封装了所有复杂的ROS2节点生命周期管理和通信线程。
import rclpy
import math
from offboard_control_lib import Vehicle
import time
def main():
# 创建 Vehicle 实例,它将自动初始化ROS2、启动控制节点和心跳线程
vehicle = Vehicle()
当你实例化 Vehicle对象时,后台发生了以下关键操作:
启动 OffboardControlROS2节点。
启动多线程执行器,确保回调函数(如位置、姿态更新)能并发处理。
启动心跳线程,以固定频率(如20Hz)向PX4发送控制模式信号,维持Offboard模式的激活。
1.2 解锁与起飞
在发送任何移动指令前,必须先解锁电机,并让无人机起飞至安全高度。
# 1. 解锁无人机。内部会发送VEHICLE_CMD_COMPONENT_ARM_DISARM命令,并记录当前点为“Home”点。
vehicle.drone.arm()
# 2. 自主起飞至指定高度(例如1.5米)。此函数为阻塞式,会持续发送上升设定点直到到达目标高度。
vehicle.drone.takeoff(1.5)
takeoff函数的本质,是持续将目标位置设定点更新为 (home_x, home_y, home_z + takeoff_height),并等待无人机当前位置与目标位置的误差小于容差阈值。
1.3 定点飞行与航向控制
起飞后,你可以指挥无人机飞向赛场内的任意坐标点。
# 3. 飞向第一个目标点P1 (x, y, z) 并保持0度航向(机头朝东,ENU坐标系)
target_x = 2.0
target_y = 0.0
target_z = 1.5
target_yaw = math.radians(0.0) # 注意:控制库使用弧度制
vehicle.drone.fly_to_trajectory_setpoint(target_x, target_y, target_z, target_yaw)
fly_to_trajectory_setpoint函数同样是阻塞式的。它内部会:
- 调用 update_position_setpoint设定目标。
- 循环检查当前无人机位置与目标点的欧氏距离和航向角差。
- 当两者均小于容忍度(DISTANCE_TOLERANCE, YAW_TOLERANCE)时,函数返回成功。
你还可以控制无人机进行纯旋转(改变航向):
# 4. 在当前位置旋转机头至90度(机头朝北)
current_x = vehicle.drone.vehicle_local_position_enu.x
current_y = vehicle.drone.vehicle_local_position_enu.y
vehicle.drone.fly_to_trajectory_setpoint(current_x, current_y, target_z, math.radians(90.0))
1.4 降落与资源清理
任务完成后,安全降落并清理ROS2资源至关重要。
# 5. 自动降落。可指定降落地点的经纬度高(或使用NaN表示当前位置),这里使用默认值。
vehicle.drone.land()
# 6. 最后,务必清理资源,关闭所有线程和ROS2上下文。
vehicle.close()
至此,你已经掌握了让无人机在虚拟赛场中实现“点对点”自主移动的全部基础技能。但这只是“瞎子”飞行。要应对省赛中动态、复杂的障碍物,我们必须为无人机装上“眼睛”。
第二部分:开启“天眼”——3D激光雷达感知与前方障碍判断
在省赛场地中,无人机需要穿越静态障碍柱和竞速门。盲目飞行必然导致“炸机”。我们为无人机模型 x290_mono_cam_down_lidar_3d_depth集成了3D激光雷达,它每秒产生大量点云数据,描述着无人机周围环境的精确三维结构。
控制库中的 process_3d_radar_data(distance, a)函数,正是处理这些原始点云,并将其转化为前方通行可行性判断的利器。
2.1 函数原理深入解析
这个函数是自主导航的感知核心,其工作流程如下图所示,它完成了从原始点到安全判读的完整链条:

1. 智能点云预处理
原始点云包含地面点、噪声和过远无效点。函数首先进行三重过滤:
- (Z_THRESHOLD):只保留垂直方向(Z轴)上接近水平面的点(如 |z| < 0.1m),有效滤除地面和天花板干扰。
- :移除距离过近(如<0.3m,可能是机身)和过远的点,聚焦于相关空间。
- 离群点剔除:使用中值滤波和Z分数检验,剔除扫描噪声,确保数据稳定性。
2. 核心:前方“安全走廊”分析
预处理后,函数基于无人机的机体坐标系(FLU: Forward-Left-Up) 进行分析:
- :在机头前方、左右宽度为 a(无人机机架安全宽度)的“走廊”内,寻找所有障碍物点,并计算其中最近的一个距离 min_front_dist。这是决定能否直行的最关键指标。
- :判断在计划前进的 distance米距离内,上述“走廊”中是否存在任何障碍物。结果用布尔值 has_passable_area表示。这是决策的逻辑基础。
- :不仅看正前方,函数还会扫描无人机周围 distance距离内的所有障碍物,利用几何关系(障碍物距离、机身宽度 a)计算出所有可供无人机安全通过的角度区间 [theta_start, theta_end]。这为后续的绕行决策提供了备选路径。
2.2 在任务决策中的应用
有了这个感知函数,你的无人机代码就具备了“看”的能力。决策逻辑变得直观而强大:
# 示例:在P1点,判断前方4米、0.2米宽度的走廊内是否安全
min_front_dist, is_front_clear = vehicle.drone.process_3d_radar_data(4.0, 0.2)
x = vehicle.drone.vehicle_local_position_enu.x
y = vehicle.drone.vehicle_local_position_enu.y
z = 1.5
if is_front_clear:
# 前方4米内无障碍,可以安全前进2米
vehicle.drone.fly_to_trajectory_setpoint(x, y + 2, z, math.radians(90.0))
else:
# 前方有障碍!触发处理逻辑:可以尝试绕行(利用计算出的可通过间隙),或悬停等待。
# 此处为示例,我们选择先悬停
vehicle.drone.hover(3.0) # 悬停3秒
# TODO: 下一篇将实现的绕行或避障逻辑
print(f"警告!前方{min_front_dist:.2f}米处检测到障碍物。")
第三部分:综合实战——一个完整的感知决策飞行案例
让我们将基础飞行与3D感知结合起来,完成一个模拟省赛任务的简单循环:起飞 → 探测前进 → 旋转扫描 → 二次感知决策 → 降落。
import rclpy
import math
from offboard_control_lib import Vehicle
import time
def main():
vehicle = Vehicle()
vehicle.drone.arm()
vehicle.drone.takeoff(1.5) # 起飞至1.5米
try:
# === 第一阶段:首次感知与前进 ===
# 探测正前方4米,宽度0.1米的走廊
min_dist, clear_to_go = vehicle.drone.process_3d_radar_data(4, 0.1)
if clear_to_go:
# 前方畅通,可以飞到(最近障碍物距离-0.5米)的位置,留出安全余量
x = min_dist - 0.5
y = 0.0
z = 1.5
vehicle.drone.fly_to_trajectory_setpoint(x, y, z, math.radians(0.0))
print("第一阶段:安全前进完成。")
else:
# 如果前方不通,这里可以改为其他策略,如直接悬停。本例中保守悬停。
print("第一阶段:前方受阻,取消前进。")
vehicle.drone.hover(2.0)
# === 第二阶段:旋转扫描环境 ===
# 假设到达P1点后,需要调整机头方向并重新感知
current_x = vehicle.drone.vehicle_local_position_enu.x
current_y = vehicle.drone.vehicle_local_position_enu.y
# 机头缓慢旋转至90度(朝北),同时可间断性调用感知函数
vehicle.drone.fly_to_trajectory_setpoint(current_x, current_y, z, math.radians(90.0))
time.sleep(1) # 稳定一下
# === 第三阶段:新方向上的感知与决策 ===
# 在90度朝向上,再次进行感知。这次检查侧向(原坐标系的前方)情况
min_dist_side, clear_side = vehicle.drone.process_3d_radar_data(4, 0.2)
if clear_side:
# 侧向通道畅通,向Y轴正方向(北)飞行2米
vehicle.drone.fly_to_trajectory_setpoint(current_x, current_y + 2, z, math.radians(90.0))
print("第三阶段:侧向安全,完成移动。")
else:
print(f"第三阶段:侧向{min_dist_side:.2f}米处有障碍,取消移动。")
vehicle.drone.hover(2.0)
# === 任务结束,返航降落 ===
vehicle.drone.land()
except KeyboardInterrupt:
print("程序被用户中断。")
finally:
vehicle.close()
if __name__ == '__main__':
main()
总结与下篇预告
在本篇中,你掌握了自主飞行的基石:Offboard控制、解锁、起飞、定点飞行与降落。同时,你获得了无人机的关键感知能力:利用process_3d_radar_data函数实时判断前方障碍物,并据此做出简单的飞行决策。你的无人机从此不再是“盲人摸象”,而是拥有了在静态环境中安全航行的初级视觉。
然而,真实的省赛挑战远不止于此。场地中的障碍物是动态移动的(如O2, O6立柱),简单的“停或走”无法应对。在下一篇系列三(动态避障篇) 中,灵智实验室将带你深入探索:
实时点云聚类与跟踪:如何从海量点云中实时分离并锁定每一个移动的障碍物目标。
速度估计与轨迹预测:如何计算障碍物的移动速度和方向,预测其下一秒的位置。
动态窗口算法(DWA)或人工势场法的本地实时避障规划:如何让无人机在复杂动态环境中,实时计算出一条既通向目标又安全无碰撞的局部路径。从“看得见”到“躲得开”,我们将一起赋予无人机真正的自主避障智慧。敬请关注!
灵智实验室,专注于机器人核心技术的研发与教育赋能。本文涉及的完整代码库与仿真环境,均可在我们的开源项目中获取,助力每一位参赛者的创新之路。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)