3. 空间与路径表示

大家好,我是你们这堂课的主讲。今天咱们聊一个非常基础、但又特别容易搞混的话题——空间与路径表示。

说实话,我刚开始做机器人那会儿,也在这上面栽过跟头。有一次调试机械臂,明明工作空间里看着路径挺顺的,结果一跑起来,机械臂直接撞上了旁边的立柱。后来才发现,我一直在用工作空间规划路径,压根没考虑构型空间里的障碍物。嗯,从那以后,我再也不敢轻视这两个空间的区别了。

一、工作空间 vs 构型空间:两个世界

先问大家一个问题:你平时看到的机器人,是在哪个空间里运动的?

答案很直观——我们肉眼看到的,是机器人在工作空间里的运动。工作空间就是机器人末端执行器(比如夹爪、焊枪)能到达的所有位置的集合。说白了,就是机器人“手”能摸到的范围。

但做规划的时候,我几乎从来不在工作空间里直接规划路径。为什么?因为机器人的关节角度和末端位置之间,存在一个非常复杂的映射关系。你想想看,工作空间里一个简单的直线运动,映射到关节空间里,可能是一个极其扭曲的曲线。

所以,我们引入了构型空间(Configuration Space,简称C-Space)。这个概念是我个人觉得机器人学里最优雅的设计之一。

核心定义:构型空间是机器人所有可能构型(即所有关节角度的组合)构成的抽象空间。一个点在这个空间里,就代表机器人一个完整的姿态。

举个例子:一个6轴工业机械臂,每个关节都有一个角度值。那么它的构型空间就是6维的。每个维度对应一个关节的角度范围。这个空间里的一个点,就是一组关节角度 (θ₁, θ₂, θ₃, θ₄, θ₅, θ₆)。

工作空间和构型空间的关系,我习惯用一个比喻来理解:

  • 工作空间:就像你站在地图上看到的城市街道,是“结果”的集合。
  • 构型空间:就像你身体所有关节的“状态”集合。你走路时,膝盖弯了多少度、手臂摆了多少度,这些状态组合起来,才决定了你最终站在哪个位置。

为什么要费劲把问题转到构型空间里?因为在构型空间里,机器人本身变成了一个点。障碍物也从复杂的形状,变成了“机器人不能进入的区域”。这样一来,路径规划问题就简化成了:在一个高维空间里,找一个点从起点到终点的无碰撞路径。

避坑指南:我曾经犯过一个错误——在构型空间里规划时,忘了考虑自碰撞。比如一个7轴协作机器人,手臂可能自己碰到自己。这在构型空间里表现为“禁止区域”的边界条件。一定要把自碰撞也建模进去,否则规划出来的路径,机器人自己就拧成麻花了。

二、路径 vs 轨迹:时间这个维度

好,接下来咱们聊聊路径和轨迹的区别。这两个词在日常对话里经常混用,但在机器人学里,它们有本质的不同。

路径(Path):只关心“走哪条路”,不关心“什么时候走到”。它是一系列空间点的有序集合。比如,从A点到B点,中间经过C点、D点。至于你是用1秒还是10秒走完,路径本身不关心。

轨迹(Trajectory):路径 + 时间信息。它给路径上的每个点都加上了时间戳,或者更精确地说,加上了速度、加速度等运动学约束。

我举个例子你就明白了:

假设你要从厨房走到卧室。路径就是:出厨房门 → 经过走廊 → 进卧室门。但轨迹会告诉你:前2秒以0.5m/s的速度走到走廊中间,然后减速到0.2m/s绕过地上的玩具,最后3秒加速到0.8m/s冲进卧室。

在机器人领域,路径规划通常只给出几何上的无碰撞路线。而轨迹规划,则要保证机器人能平滑、安全地沿着这条路线运动,不能有速度突变,不能有加速度过大的情况。

一句话总结:路径是“去哪”,轨迹是“怎么去、多快去”。

三、它们之间的关系:一张图说清楚

为了让大家更直观地理解这几个概念的关系,我画了一张流程图。这张图是我做项目时经常贴在工位上的,每次做规划前都看一眼,提醒自己别搞混了。

空间与路径表示:核心逻辑关系 工作空间 末端执行器的位置集合 正运动学 构型空间 (C-Space) 所有关节角度的组合 逆运动学 路径规划 无碰撞几何路径 添加时间信息 轨迹规划 带速度/加速度约束 核心思路:在工作空间定义任务,在构型空间做规划,最后生成带时间的轨迹

这张图展示了整个流程:

  1. 我们通常在工作空间里定义任务目标(比如“把螺丝拧到那个位置”)。
  2. 然后通过逆运动学,把目标位置映射到构型空间里。
  3. 在构型空间里做路径规划,找到一条无碰撞的几何路径。
  4. 最后,给这条路径加上时间信息(速度、加速度约束),生成可执行的轨迹。

四、实际项目中的选择:什么时候用哪个?

我根据自己多年的项目经验,整理了一个表格。大家在选方案的时候可以参考:

场景 推荐空间 原因
移动机器人导航(地面) 工作空间(2D栅格地图) 地面移动机器人自由度低,工作空间和构型空间几乎等价,直接在工作空间规划更直观
机械臂避障 构型空间 工作空间里看着没障碍,但关节可能撞到东西。必须在构型空间里规划才能保证安全
人形机器人全身运动 构型空间(高维) 自由度太多,工作空间根本无法描述完整状态。必须用构型空间
视觉伺服抓取 工作空间 + 构型空间混合 视觉信息在工作空间里处理,但抓取规划要转到构型空间里做

重要提醒:千万不要以为构型空间规划是万能的。当机器人自由度很高时(比如人形机器人有30+个关节),构型空间的维度会变得非常高,规划算法的计算量会爆炸。这时候就需要用一些降维技巧,或者改用采样-based的方法(比如RRT*)。我做过一个项目,用A*在10维构型空间里规划,结果跑了半小时还没出结果……后来换了RRT,几秒钟就搞定了。

五、代码示例:从路径到轨迹的转换

最后,我给大家看一段简单的Python代码。它演示了如何把一条路径(一系列点)转换成一条带速度约束的轨迹。

import numpy as np

# 假设我们有一条路径:一系列位置点
path_points = np.array([
    [0.0, 0.0, 0.0],   # 起点
    [0.5, 0.2, 0.1],
    [1.0, 0.5, 0.2],
    [1.5, 0.8, 0.3],
    [2.0, 1.0, 0.5]    # 终点
])

# 设定最大速度和加速度
max_velocity = 0.5  # m/s
max_acceleration = 0.2  # m/s²

def path_to_trajectory(path, v_max, a_max):
    """
    把路径转换成轨迹(带时间戳)
    这里用最简单的梯形速度规划
    """
    # 计算每段路径的长度
    distances = np.linalg.norm(np.diff(path, axis=0), axis=1)
    total_distance = np.sum(distances)
    
    # 计算加速段和减速段的时间
    t_acc = v_max / a_max
    d_acc = 0.5 * a_max * t_acc**2
    
    # 如果总距离太短,无法达到最大速度
    if total_distance < 2 * d_acc:
        # 用三角形速度曲线
        t_acc = np.sqrt(total_distance / a_max)
        t_const = 0
    else:
        # 梯形速度曲线
        d_const = total_distance - 2 * d_acc
        t_const = d_const / v_max
    
    total_time = 2 * t_acc + t_const
    
    # 生成时间戳
    num_points = len(path)
    time_stamps = np.linspace(0, total_time, num_points)
    
    # 计算每个点的速度(近似)
    velocities = np.zeros(num_points)
    for i in range(1, num_points):
        dt = time_stamps[i] - time_stamps[i-1]
        if dt > 0:
            velocities[i] = distances[i-1] / dt
    
    return time_stamps, velocities

# 执行转换
time_stamps, velocities = path_to_trajectory(
    path_points, max_velocity, max_acceleration
)

print("时间戳:", time_stamps)
print("速度:", velocities)

这段代码虽然简单,但核心思想都在里面了:路径只告诉你怎么走,轨迹告诉你什么时候走到、走多快。实际项目中,你还要考虑加速度的连续性、加加速度(Jerk)的限制,否则机器人运动起来会抖得厉害。

个人经验:我建议大家在写轨迹规划代码时,一定要加上加加速度(Jerk)的限制。很多新手只约束速度和加速度,结果机器人运动时虽然速度变化平滑,但加速度突变会导致末端抖动。尤其是在做精密装配时,这种抖动会让零件对不准。我曾经在一个芯片贴装项目里吃过这个亏,后来加了Jerk约束,问题就解决了。

好了,今天的内容就到这里。空间与路径表示是机器人学的地基,搞清楚了,后面的规划算法才能建得稳。希望大家在实际项目中,能灵活运用这些概念,少走弯路。