2. 坐标系与坐标变换:全局坐标系、车辆坐标系、Frenet坐标系及其在边界约束下的转换

做路径规划,说白了就是回答三个问题:我在哪?我要去哪?怎么去?这三个问题,每一个都离不开坐标系。我刚开始做自动驾驶那会儿,就吃过坐标系搞混的亏——明明规划出来的路径挺漂亮,结果车一跑就偏,查了半天才发现是坐标转换的矩阵写反了。嗯,从那以后,我对坐标系这块就格外上心。

今天咱们就聊聊路径规划里最常用的三种坐标系:全局坐标系、车辆坐标系、Frenet坐标系。尤其是Frenet坐标系,它在处理道路边界约束时,简直是神器。

2.1 全局坐标系:给世界一个绝对位置

全局坐标系,也叫世界坐标系。它是个固定不动的参考系,通常用经纬度(WGS84)或者UTM投影坐标来表示。你可以把它想象成一张大地图,路上的每个点、每辆车、每个行人,都能用唯一的(x, y)坐标标出来。

在实际工程中,我们一般用UTM坐标。为什么?因为它是平面直角坐标,计算距离、角度都方便。经纬度是球面坐标,算个两点距离还得上大圆公式,太麻烦。

关键点:全局坐标系是静态的,不随车辆移动而改变。它是所有其他坐标系的“锚点”。

2.2 车辆坐标系:以车为本的视角

车辆坐标系就灵活多了。它以车辆自身为原点,通常定义如下:

  • x轴:指向车辆正前方
  • y轴:指向车辆左侧(也有用右侧的,看具体标准)
  • z轴:垂直向上

这个坐标系最大的好处是——传感器数据天然就在这个坐标系里。激光雷达扫出来的点云,毫米波雷达检测到的障碍物,都是以车为中心报告的。你想想看,如果每次都要把传感器数据先转到全局坐标再处理,那延迟得多高?

我个人习惯在车辆坐标系里做局部路径规划,比如避障、换道。因为这时候我只关心“障碍物在我左边几米”,而不关心“障碍物在经纬度多少”。

小技巧:车辆坐标系的原点通常放在后轴中心。为什么?因为车辆的运动模型(比如自行车模型)就是以后轴中心为参考点建立的。放在这里,坐标变换最简洁。

2.3 Frenet坐标系:沿着道路走

好了,重点来了。Frenet坐标系,我愿称之为“路径规划者的瑞士军刀”。

它用两个变量描述一个点:

  • s:沿着参考线的纵向距离(弧长)
  • d:垂直于参考线的横向偏移(左正右负或反之)

为什么说它好用?因为道路是有边界的。在全局坐标系里,你很难直观地知道“这个点离左边界多远”。但在Frenet坐标系里,d值直接告诉你横向位置。你只要保证d值在道路左右边界对应的范围内,路径就一定不会出界。

我在项目中遇到过一个问题:用全局坐标做路径优化,结果优化出来的路径虽然平滑,但有一小段压了路沿。查了半天才发现是约束条件写得太复杂,没覆盖到那个弯道。后来换成Frenet坐标系,横向约束直接写成 d_min ≤ d ≤ d_max,简单粗暴,再也没出过问题。

核心思想:Frenet坐标系把二维的路径规划问题,解耦成纵向(s方向)和横向(d方向)两个一维问题。纵向管速度、跟车距离;横向管车道保持、换道。两个问题分开优化,再组合起来,复杂度直线下降。

2.4 坐标变换:从全局到Frenet

光有坐标系还不够,你得能在它们之间自由切换。最常用的变换有两个:

2.4.1 全局坐标 → Frenet坐标

给定一个全局点 (x, y) 和一条参考线(通常是道路中心线),求它的 (s, d)。

步骤很简单:

  1. 在参考线上找到离 (x, y) 最近的点,记为 (x_r, y_r)
  2. 这个最近点对应的弧长就是 s
  3. 计算 (x, y) 到 (x_r, y_r) 的向量,投影到参考线的法线方向上,得到 d

代码实现大概长这样:

def global_to_frenet(x, y, ref_line):
    # ref_line: 参考线上的离散点列表 [(x0,y0), (x1,y1), ...]
    # 1. 找最近点
    min_dist = float('inf')
    nearest_idx = 0
    for i, (rx, ry) in enumerate(ref_line):
        dist = (x - rx)**2 + (y - ry)**2
        if dist < min_dist:
            min_dist = dist
            nearest_idx = i
    
    # 2. 计算s(累加弧长)
    s = 0
    for i in range(nearest_idx):
        dx = ref_line[i+1][0] - ref_line[i][0]
        dy = ref_line[i+1][1] - ref_line[i][1]
        s += math.sqrt(dx*dx + dy*dy)
    
    # 3. 计算d(横向偏移,带符号)
    rx, ry = ref_line[nearest_idx]
    # 参考线在该点的切线方向
    if nearest_idx < len(ref_line) - 1:
        dx = ref_line[nearest_idx+1][0] - rx
        dy = ref_line[nearest_idx+1][1] - ry
    else:
        dx = rx - ref_line[nearest_idx-1][0]
        dy = ry - ref_line[nearest_idx-1][1]
    
    # 法线方向(左转90度)
    nx, ny = -dy, dx
    norm = math.sqrt(nx*nx + ny*ny)
    nx, ny = nx/norm, ny/norm
    
    d = (x - rx)*nx + (y - ry)*ny
    
    return s, d
注意:找最近点时,如果参考线曲率变化剧烈,可能会有多个局部最近点。我建议用二分搜索或者KD树来加速,别傻乎乎地遍历所有点——尤其是参考线有几千个点的时候,遍历一次够你喝一壶的。

2.4.2 Frenet坐标 → 全局坐标

反过来,给定 (s, d) 和参考线,求 (x, y)。这个更简单:

  1. 找到参考线上弧长为 s 的点 (x_r, y_r)
  2. 计算该点的法线方向 (n_x, n_y)
  3. 全局点 = 参考点 + d × 法线方向
def frenet_to_global(s, d, ref_line, cum_lengths):
    # cum_lengths: 参考线上每个点对应的累积弧长
    # 1. 找到s对应的参考点(插值)
    idx = np.searchsorted(cum_lengths, s) - 1
    idx = max(0, min(idx, len(ref_line)-2))
    
    # 线性插值
    ratio = (s - cum_lengths[idx]) / (cum_lengths[idx+1] - cum_lengths[idx])
    x_r = ref_line[idx][0] + ratio * (ref_line[idx+1][0] - ref_line[idx][0])
    y_r = ref_line[idx][1] + ratio * (ref_line[idx+1][1] - ref_line[idx][1])
    
    # 2. 法线方向
    dx = ref_line[idx+1][0] - ref_line[idx][0]
    dy = ref_line[idx+1][1] - ref_line[idx][1]
    nx, ny = -dy, dx
    norm = math.sqrt(nx*nx + ny*ny)
    nx, ny = nx/norm, ny/norm
    
    # 3. 全局坐标
    x = x_r + d * nx
    y = y_r + d * ny
    
    return x, y

2.5 边界约束下的坐标变换:实战中的坑

理论说完了,咱们聊聊实战。道路边界约束,说白了就是:你的路径不能超出左右路沿。

在Frenet坐标系下,这个约束写起来特别直观:

d_left_boundary(s) ≤ d(s) ≤ d_right_boundary(s)

其中 d_left_boundary(s) 和 d_right_boundary(s) 是沿着参考线变化的边界函数。弯道内侧的边界d值小,外侧的d值大。

我曾经踩过一个坑:边界函数直接用固定值。结果在弯道处,规划出来的路径虽然满足 |d| ≤ 3.5m,但实际已经压到路沿了。为什么?因为弯道内侧的可用宽度比直道窄!

正确的做法是:

  • 从高精地图里提取道路左右边界的离散点
  • 把这些点也转换到Frenet坐标系下
  • 对每个s,插值出对应的 d_left 和 d_right

这样,不管道路怎么弯,你的约束都是准确的。

避坑指南:我曾经在高速场景下,直接用固定宽度3.75m(中国标准车道宽)作为约束。结果遇到一个施工路段,车道临时变窄到3.0m。车差点蹭到锥桶。后来我改成实时读取地图中的动态边界信息,才算彻底解决。

2.6 三种坐标系的对比

坐标系 适用场景 优点 缺点
全局坐标系 全局路径规划、地图匹配 绝对位置,不依赖参考线 难以表达道路拓扑和横向约束
车辆坐标系 传感器融合、局部避障 与传感器数据天然对齐,实时性好 随车辆运动而变化,不适合长距离规划
Frenet坐标系 路径优化、车道保持、换道 解耦纵横向,边界约束直观 依赖参考线质量,曲率大时精度下降

2.7 知识体系总览

下面这张图,帮你把三种坐标系的关系和变换路径理清楚:

全局坐标系 (x, y) 或 经纬度 车辆坐标系 (x_v, y_v) Frenet坐标系 (s, d) 旋转+平移 投影到参考线 逆变换 参考线+法线偏移 核心变换关系 1. 全局 ↔ 车辆:刚性变换(旋转矩阵 R + 平移向量 t) 2. 全局 → Frenet:找最近参考点,计算弧长 s 和横向偏移 d 3. Frenet → 全局:沿参考线走 s,再沿法线偏移 d 注意:Frenet变换依赖参考线质量。 参考线不平滑或曲率突变,会导致 d 值计算不准确。 实战建议: • 参考线至少每0.5m一个点,弯道处加密到0.1m

嗯,坐标系这块内容不少,但它是路径规划的基石。你想想看,如果坐标系都搞不清楚,后面做优化、做约束,那不都是空中楼阁吗?我个人建议,刚开始接触的同学,先拿一小段直道和弯道数据,手写一遍三种坐标系的互相转换,跑通之后再用到自己的规划器里。这一步走扎实了,后面会顺很多。

可可精品资料库