在上一篇中,我们搭建了高度拟真的省赛仿真环境。现在,是时候让你的无人机摆脱摇杆,用代码赋予它真正的“智慧”,迈出全自主飞行的第一步。灵智实验室将继续伴随你,深入无人机的“大脑”——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函数同样是阻塞式的。它内部会:

  1. 调用 update_position_setpoint设定目标。
  2. 循环检查当前无人机位置与目标点的欧氏距离航向角差
  3. 当两者均小于容忍度(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)人工势场法的本地实时避障规划:如何让无人机在复杂动态环境中,实时计算出一条既通向目标又安全无碰撞的局部路径。从“看得见”到“躲得开”,我们将一起赋予无人机真正的自主避障智慧。敬请关注!

灵智实验室,专注于机器人核心技术的研发与教育赋能。本文涉及的完整代码库与仿真环境,均可在我们的开源项目中获取,助力每一位参赛者的创新之路。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐