浙江省机器人竞赛“空中机器人”赛项系列文章(三):动态避障
在上一篇文章中,你的无人机学会了“看”——利用3D激光雷达判断前方是否有障碍。但这还远远不够。当省赛场地中的O2、O6立柱开始规律移动,当你的无人机需要在狭窄通道中与动态障碍共舞时,简单的“停”或“绕”将瞬间失效。动态避障,是空中机器人从“自动化”迈向“智能化”的关键一跃,也是本届省赛的核心难点之一。

灵智实验室在本篇将为你彻底揭开动态避障的技术面纱。我们将深入解析控制库 offboard_control_lib.py中已实现的3D点云多目标跟踪与动态窗口算法(DWA),手把手教你如何让无人机拥有“预判”能力,在复杂动态环境中游刃有余。
回顾:从静态感知到动态挑战
系列二中,我们使用了 process_3d_radar_data函数。它是一个出色的“静态快照”分析器,能告诉你此刻前方是否畅通。然而,对于移动的障碍物,它存在天然局限:
无目标标识:无法区分障碍物A和B,更无法知道上一帧的A就是这一帧的A。
无速度信息:无法获知障碍物的运动速度和方向,因而无法预测其下一步位置。
决策滞后:基于瞬间状态的决策,在高速动态场景下极易失效,可能导致无人机“撞向障碍物的未来位置”。
要解决这些问题,我们需要一套完整的感知-跟踪-预测-规划流水线。这正是我们控制库中 pointcloud_callback和 compute_dwa等函数所构建的体系。
第一部分:智能感知——3D点云多目标跟踪流水线
当3D激光雷达的原始点云数据流入系统,控制库通过一系列精密的处理步骤,将其转化为对环境中每一个障碍物的结构化认知。整个过程如下图所示,形成了一个从数据到决策的完整闭环:
1.1 点云预处理:去芜存菁
原始点云包含大量噪声。preprocess_pointcloud函数是第一位“清道夫”:
1.地面过滤 (ground_z_thresh): 去除接近地面的点(FLU坐标系中Z值较小的点),这些点通常来自地面反射,对避障无意义。
2.距离过滤 (min_range, max_range): 只保留有效测距范围内的点,忽略过近(可能是机身)和过远的噪声。
3.前方扇形聚焦 (forward_range, lateral_range): 这是关键优化!并非处理所有方向点云,而是只关注无人机正前方扇形区域内的点。这大幅减少了计算量,让算法能运行在机载计算机上。参数 forward_range=6和 lateral_range=8意味着关注前方6米、左右各4米范围内的障碍物。
1.2 聚类与跟踪:识别并锁定每一个目标
预处理后的点云是一团“散沙”。我们需要知道哪些点属于同一个物体。
DBSCAN聚类:cluster_points函数使用DBSCAN算法。它将彼此距离小于 dbscan_eps(例如0.35米) 的点归为同一簇。这能很好地将一个障碍物(如直径0.4米的柱子)的点云从背景中分离出来,并计算出该簇的中心点(x, y, z)和包围盒尺寸(dx, dy, dz)。
多目标跟踪:track_and_compute_speed是核心。它通过数据关联解决“哪一个是哪一个”的问题。
-
关联:将当前帧检测到的障碍物中心点,与上一帧跟踪列表中的障碍物位置进行匹配(基于最近邻原则,阈值association_threshold)。匹配成功则认为是同一个物体,更新其信息;匹配失败则视为新出现的障碍物,分配新ID。
-
速度估计:对于匹配上的障碍物,根据其位置变化和两帧之间的时间差 dt,可以计算出瞬时速度。通过低通滤波(参数alpha)平滑速度数据,得到稳定可靠的速度估计 (vx, vy, vz)。这是预测的基础。
-
状态管理:每个被跟踪的障碍物都有一个miss_count。如果连续多帧(max_miss_frames)未被检测到,则将其从跟踪列表中移除,意味着它可能已离开视野或消失。
1.3 障碍物分类与决策支持
跟踪之后,我们对障碍物有了深刻认知。classify_and_print_obstacles函数根据尺寸和速度对其进行智能分类:
墙 (walls): 尺寸(dx, dy, dz)任一大于0.5米的物体。通常是场地边界。
静止柱子 (static_pillars): 尺寸较小且速度小于0.1 m/s的物体。例如场地中固定的障碍柱。
移动柱子 (moving_pillars): 尺寸较小但速度大于0.1 m/s的物体。这就是省赛中的动态障碍O2、O6!
分类带来了巨大的决策优势。例如,在代码的 pointcloud_callback末尾,我们调用 widest_corridor_between_walls函数。这个函数利用静止柱子(static_pillars)的位置信息,在两侧的“墙”之间,计算出当前最宽阔、可安全通行的走廊中轴线坐标(y_star)。这为无人机提供了高层导航的绝佳参考路径——沿着最宽阔的地方走总是更安全。
第二部分:智慧决策——动态窗口算法(DWA)详解
感知系统告诉我们“世界是什么样”,而DWA算法则决定“现在该怎么飞”。compute_dwa函数实现了这一经典局部规划器。
2.1 DWA的核心思想
DWA的智慧在于,它不在庞大的地理空间中搜索路径,而是在无人机当前可达的速度空间 (vx, vy, w) 中进行搜索。它模拟无人机在未来短时间内(predict_time,如2秒)以不同速度组合运动产生的轨迹,并给每条轨迹打分,最后选择分数最高的那组速度来执行。
2.2 评分标准:安全、快速、对准目标
DWA为每条模拟轨迹从三个维度评分:
1.朝向目标 (heading_score): 轨迹终点朝向与目标点方向的接近程度。分值越高,越对准目标。
2.前进速度 (vel_score): 轨迹的平均速度大小。在安全的前提下,鼓励更快移动。
3.远离障碍 (clear_score): 轨迹上所有点与最近障碍物的距离。距离越远越安全。
4.抵达目标 (goal_score): 轨迹终点与局部目标点的距离。鼓励靠近目标。
最终得分是这些分项的加权和(alpha, beta, gamma)。通过调整权重,你可以定义无人机的“性格”:是激进还是保守。
2.3 与感知的融合
DWA算法依赖感知系统提供的障碍物信息。在代码中,它通过将当前2D激光雷达数据(lidar_scan)转换为障碍物点列表,并构建KD-Tree (cKDTree) 来快速查询模拟轨迹上的点与所有障碍物的最近距离。这正是动态避障的关键:用于碰撞检测的障碍物信息是实时更新的,包含了移动障碍物的最新位置。
第三部分:实战整合——在省赛任务中应用动态避障
理论已备,现在来看如何在你自己的比赛脚本中调用这些强大的功能。
3.1 启用高级感知与跟踪
控制库的感知跟踪功能默认在 pointcloud_callback中通过一个开关 self.mod控制。你需要先在主程序中打开它:
def main():
vehicle = Vehicle()
vehicle.drone.arm()
vehicle.drone.takeoff(1.5)
# 关键步骤:启用动态感知与跟踪模式
vehicle.drone.mod = True
try:
# 此时,vehicle.drone的以下属性将被实时更新:
# - static_pillars: 字典,包含所有静止柱子的ID、位置、尺寸、速度。
# - moving_pillars: 字典,包含所有移动柱子的ID、位置、尺寸、速度。
# - corridor_y: 浮点数,基于当前静止柱子计算出的建议走廊中轴y坐标。
# - pillars_avoidance: 列表,只包含位于前方避障区域(1<x<4.5, y>0.5)的静止柱子坐标。
time.sleep(1.0) # 给感知系统一点时间初始化
# 示例:打印当前检测到的移动柱子
if vehicle.drone.moving_pillars:
for oid, data in vehicle.drone.moving_pillars.items():
pos = data['pos']
vel = data['vel']
print(f"移动柱子 ID{oid}: 位置[{pos[0]:.1f}, {pos[1]:.1f}], 速度[{vel[0]:.1f}, {vel[1]:.1f}] m/s")
else:
print("未检测到移动柱子。")
3.2 调用DWA进行动态避障飞行
基础的位置指令 fly_to_trajectory_setpoint是“盲飞”。要启用避障,必须使用DWA版本:
# 目标点:穿越动态障碍区,前往(4.0, 3.0, 1.5)
target_x, target_y, target_z = 4.0, 3.0, 1.5
target_yaw = math.radians(90.0)
# 使用DWA避障模式飞向目标
# 此函数内部会:1. 检查前方安全距离;2. 触发DWA计算;3. 发布速度控制指令。
success = vehicle.drone.fly_to_trajectory_setpoint_dwa(target_x, target_y, target_z, target_yaw, timeout=30.0)
if success:
print("成功抵达目标,并规避了所有动态障碍!")
else:
print("飞行任务超时或中断。")
fly_to_trajectory_setpoint_dwa函数内部逻辑是智能切换的:
当雷达检测到前方safe_dist(默认1.5米)内有障碍时,自动切换到DWA速度控制模式。
当障碍物离开,恢复为位置控制模式,直飞目标。
在DWA模式下,它每秒调用compute_dwa函数数次,根据最新的目标位置和实时障碍物信息,计算出最优的 (vx_enu, vy_enu, vz_enu, yaw_rate_enu)速度指令,实现流畅绕行。
3.3 完整实战代码示例
结合以上所有知识点,一个应对省赛动态区域的完整任务片段如下:
import rclpy
import math
from offboard_control_lib import Vehicle
import time
import numpy as np
def main():
vehicle = Vehicle()
vehicle.drone.arm()
vehicle.drone.takeoff(1.5)
vehicle.drone.mod = True # 启用高级感知跟踪
time.sleep(2.0) # 等待感知系统稳定
try:
print("=== 阶段1: 前往动态区域边缘 ===")
# 先飞到动态区域前的观测点
vehicle.drone.fly_to_trajectory_setpoint(1.0, vehicle.drone.corridor_y, 1.5, math.radians(90.0))
print(f"已到达观测点,建议走廊中轴: y={vehicle.drone.corridor_y:.2f}")
print("=== 阶段2: 观察动态障碍物 ===")
for _ in range(5): # 观察5秒
if vehicle.drone.moving_pillars:
for oid, data in vehicle.drone.moving_pillars.items():
pos = data['pos']
if 1.0 < pos[0] < 4.0: # 如果移动柱子进入目标区域
print(f"监测到动态柱子{oid}在区域内,速度{np.linalg.norm(data['vel']):.2f}m/s")
time.sleep(1)
print("=== 阶段3: 启动DWA,穿越动态区域 ===")
# 目标点设在动态区域另一侧
final_x, final_y = 4.5, vehicle.drone.corridor_y
success = vehicle.drone.fly_to_trajectory_setpoint_dwa(final_x, final_y, 1.5, math.radians(90.0), timeout=45.0)
if success:
print("=== 阶段4: 穿越成功,准备降落 ===")
vehicle.drone.hover(2.0)
vehicle.drone.land()
else:
print("!!! 穿越动态区域失败,执行紧急悬停 !!!")
vehicle.drone.hover(5.0)
# 此处可加入紧急处理逻辑,如尝试原路返回
vehicle.drone.land(vehicle.drone.home_position[0], vehicle.drone.home_position[1], 0.0)
except KeyboardInterrupt:
print("程序被用户中断,紧急降落...")
vehicle.drone.land()
finally:
vehicle.close()
if __name__ == '__main__':
main()
总结与下篇预告
通过本篇,你已经掌握了让无人机在动态环境中生存的核心科技:实时3D多目标跟踪赋予了无人机识别与预测移动障碍物的“慧眼”;动态窗口算法(DWA)则赋予了它在瞬息万变的环境中实时规划局部安全路径的“大脑”。二者结合,你的无人机已具备应对省赛中最棘手的动态障碍挑战的能力。
然而,一个顶尖的空中机器人不仅需要“避得开”,还需要“穿得快”。省赛中的竞速门是比拼速度与精度的关键环节。在下一篇 系列四(竞速门穿越篇) 中,灵智实验室将带你探索:
视觉识别精准定位:如何利用机载相机,在高速运动中稳定检测并定位竞速门框。
高速穿越控制策略:如何设计轨迹生成与控制算法,让无人机以最优姿态和速度平稳、精准地穿过狭窄的门框。
感知-控制的紧耦合:如何将视觉识别结果实时反馈给飞控,实现闭环的穿越控制,确保百发百中。
从“避障”到“穿门”,让我们共同挑战速度与精度的极限。
灵智实验室,深耕机器人感知与决策核心技术,助力创新,赋能未来。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)