1. 道路边界约束概述
各位同学,大家好。今天我们来聊聊路径规划里一个最基础、也最容易被忽视的问题——道路边界。
说实话,我刚开始做自动驾驶那会儿,觉得路径规划嘛,不就是让车从A点走到B点?后来在测试场里跑了一次,车差点骑上路沿,我才意识到:没有边界约束的路径规划,就像蒙着眼睛开车。
为什么需要道路边界?
你想想看,车辆在道路上行驶,不是自由粒子。它必须待在「可行驶区域」内。这个区域,就是由道路边界定义的。
具体来说,道路边界的作用有三点:
- 安全第一:防止车辆驶出路面,避免碰撞路沿、护栏或掉下路基。我在项目中遇到过,有一次车辆在弯道中因为边界约束太松,直接压上了路肩,幸好速度不快。
- 规划约束:给路径搜索算法提供硬性限制。说白了,就是告诉算法:「你只能在红线里面找路」。
- 舒适性保障:合理的边界约束能让车辆平滑行驶,不会突然猛打方向。
核心观点:道路边界不是「可选」的,而是路径规划的硬约束。没有它,规划出的路径就是一张废纸。
边界类型:路沿、护栏、车道线
实际道路上的边界,大致分三类。我按工程中遇到的频率来排个序:
| 类型 | 典型特征 | 数学表达方式 | 我的经验 |
|---|---|---|---|
| 路沿(Curb) | 高度差明显,物理不可穿越 | 分段线性函数 / 样条曲线 | 激光雷达最容易检测到,但雨天容易误判 |
| 护栏(Barrier) | 连续刚性结构,高度较高 | 多边形 / 线段集合 | 高速路上常见,但弯道处容易漏检 |
| 车道线(Lane Marking) | 地面标记,视觉特征明显 | 多项式曲线(如三次多项式) | 视觉方案的主力,但磨损或遮挡时很头疼 |
一个小技巧:实际工程中,我习惯把路沿和护栏作为硬边界(车辆绝对不能压),车道线作为软边界(变道时可以压,但正常行驶要保持在内部)。这样分层处理,规划起来更灵活。
数学表达:怎么把边界变成算法能懂的语言?
嗯,这里要上点干货了。边界在现实世界里是物理实体,但在算法里,它必须是一串数字。
我个人最常用的三种数学表达方式:
1. 分段线性函数
说白了,就是用一堆线段连起来。适合路沿这种形状比较规则的边界。
// 伪代码示例:路沿边界表达
struct CurbBoundary {
vector<Point2D> points; // 离散点序列
double height; // 路沿高度
bool is_continuous; // 是否连续
};
// 判断点是否在边界内
bool IsInsideBoundary(Point2D vehicle_pos, CurbBoundary boundary) {
// 用射线法判断点在多边形内
// 我踩过的坑:这里一定要考虑车辆轮廓,不能只用车后轴中心点
}
2. 多项式曲线
车道线常用三次多项式拟合。为什么是三次?因为曲率连续,车跑起来舒服。
// 三次多项式车道线模型
// y = a0 + a1*x + a2*x^2 + a3*x^3
struct LaneBoundary {
double a0, a1, a2, a3; // 多项式系数
double confidence; // 置信度,0~1
};
// 计算某x处的边界y值
double GetBoundaryY(double x, LaneBoundary lb) {
return lb.a0 + lb.a1*x + lb.a2*x*x + lb.a3*x*x*x;
}
注意:多项式拟合有个坑——外推不可靠。我曾经在高速上遇到一个急弯,多项式在远距离处直接飞出去了。后来我加了距离截断,超过30米就改用直线外推。
3. 栅格化表达
这个适合复杂场景。把地图切成小格子,每个格子标记「可通行」或「不可通行」。
// 栅格地图边界表达
struct GridMap {
int width, height; // 栅格尺寸
double resolution; // 分辨率,比如0.1m
vector<vector<bool>> grid; // true=可通行, false=障碍/边界外
};
// 检查位置是否合法
bool IsValidPosition(double x, double y, GridMap map) {
int ix = (int)(x / map.resolution);
int iy = (int)(y / map.resolution);
if (ix < 0 || ix >= map.width || iy < 0 || iy >= map.height)
return false;
return map.grid[iy][ix]; // 注意:y对应行索引
}
知识体系总览
下面这张图,是我自己总结的道路边界约束在路径规划中的位置。你看一眼就能明白整个逻辑链条:
这张图里,从上到下是数据流的方向。传感器拿到原始数据后,先提取边界,再转成数学表达,最后喂给规划算法。每一步都有坑,后面我们会一个一个填。
避坑指南:我踩过的三个坑
- 边界不连续:有一次在隧道里,GPS信号丢失,高精地图的边界直接断了一截。车差点往墙上开。后来我加了边界连续性检查,发现断点就降速并切换到视觉边界。
- 边界抖动:视觉车道线检测在颠簸路段会上下跳。如果直接把抖动边界喂给规划器,车就会像喝醉了一样左右晃。我的解决办法是加一个低通滤波器,时间常数0.3秒左右。
- 边界类型混淆:有一次系统把路沿误识别为车道线,结果规划出的路径贴着路沿走,非常危险。后来我强制要求:路沿和护栏的优先级永远高于车道线。
我的个人习惯:在工程代码里,我会用一个 BoundaryManager 类来统一管理所有边界数据。它负责融合多传感器信息、处理异常、输出统一的边界表达。这样规划模块只需要调用一个接口,不用关心底层是激光还是视觉。
好了,这一章我们搞清楚了道路边界是什么、为什么需要它、以及怎么用数学表达。下一章我们会深入讲如何从传感器数据中提取这些边界——那才是真正考验工程能力的地方。