基于前视视觉的车道线检测(carla_lane_keeping)

本模块在 CARLA 仿真环境中,用前视相机图像检测车道线,输出车辆相对车道中心的 横向偏差、航向偏差与车道曲率。全部算法用纯 numpy 实现,不依赖 OpenCV、 不依赖深度学习,可在教学虚拟机的 ROS Noetic 环境中直接运行。

配套的车道保持控制见车道保持控制。

1. 为什么做这个模块

仓库中地面载具部分的视觉模块已有 carla_yolo_avoidance、yolov8_target_tracking、 carla_pilotnet_ros、carla_perception_control、carla_traffic_sign_recognition, 但它们的输出都是目标框、端到端转向量或交通标志类别——回答的是"前方有什么物体"。

显式的车道几何恢复是缺的:没有模块输出车辆相对车道中心的横向偏差、航向偏差 与车道曲率。已有视觉模块偏目标检测与端到端学习,缺的是把车道线本身当作几何结构 来拟合的那一层。

车道保持是自动驾驶最基础、也最能体现"感知 → 决策 → 控制"闭环的能力: 它必须先从图像中恢复出车道几何,再据此算出车辆偏离了多少,才能谈控制。 本模块负责前者,并输出下游可直接消费的物理量。

2. 处理流程

彩色图 (BGR)
  │
  ├─ 灰度化
  │
  ├─ 方向梯度阈值 ──┐
  │                 ├─► 二值车道像素掩码
  └─ 颜色阈值 ──────┘
  │
  ├─ 透视变换(梯形 → 矩形,得到鸟瞰图)
  │
  ├─ 底部直方图定位车道线起点
  │
  ├─ 连通域分组 + 多项式拟合
  │
  └─ 输出:左右车道线系数
             ├─► 横向偏差(米)
             ├─► 航向偏差(弧度)
             └─► 车道曲率半径(米)

2.1 方向梯度阈值

车道线在图像中近似竖直。对图像求 Sobel 梯度后,定义梯度方向角

angle = atan2(|gy|, |gx|)

这里有一个极易搞反的点:

  • 竖直边缘(车道线)的梯度几乎全在水平方向,|gy| ≈ 0,因此角度 接近 0°
  • 水平边缘(路面接缝、阴影边界)角度接近 90°

所以判据是保留 angle <= angle_max_deg,而不是相反。若按 90° 写判据, 整幅图会被过滤干净、一个车道像素都检不到。

梯度幅值阈值采用自适应策略:

实际阈值 = max(sobel_min, 该图像梯度分布的 sobel_auto_percentile 分位)

原因很实际:实测 CARLA 前视相机的路面纹理梯度中位数约 45、90 分位约 260, 而合成图像背景梯度几乎为 0。用同一个固定阈值处理两类图像必然顾此失彼—— 取 25 会在真实图上把 40% 的路面判成车道线(整块路面连成一个巨大连通域), 取 160 又会让低噪声图像几乎没有输出。按分位数自适应可以兼顾。

在真实 CARLA 图像上的实测效果:

阈值策略 二值掩码占比 最大连通域
固定 25(未标定) 42% 271205 像素(整块路面)
自适应(本模块) 1.6% ~ 4% 细长结构,宽高比 < 0.1

2.2 透视变换

用直接线性变换(DLT)自行求解单应矩阵,不依赖 OpenCV:

源梯形(图像中感兴趣的地面区域) → 目标矩形(鸟瞰图)

两个容易踩的坑,本模块都已处理:

  1. 源梯形底边必须贴到图像最底行(bottom_ratio = 1.0)。 横向偏差是相对"离车最近处"定义的;若底边抬高到 0.95h,算出来的其实是 约 3 m 前方的偏差,会引入与距离成正比的系统性误差(实测偏右 100 px 的场景误差达 35 px)。

  2. 目标矩形上下必须留边距。源梯形底边在图像底部,透视外推后该行以下的 近处像素会映射到目标图之外;若目标矩形下边直接顶到 height-1, 这些像素全部落在图像外被丢弃,导致近处车道(尤其偏在一侧的)整条消失。

2.3 车道线拟合:为什么用连通域而不是滑动窗口

绝大多数车道线检测教程用滑动窗口。本模块最初也用,但在测试中发现了 它的一个固有缺陷:

滑动窗口要求「窗口宽度 > 单条车道线宽度」且「窗口宽度 < 两条车道线间距」。

这两个条件很容易同时不成立。实测:窗口取 ±100 px 时宽达 200 px, 而两条车道线在鸟瞰图中的间距可能只有约 91 px——每个窗口都同时框住两条线, 取均值后两条线都被拟合到画面正中,输出完全错误却不报任何错。

因此本模块改用连通域分组:鸟瞰图中每条车道线是一个细长连通域, 先做连通域标记,再按基点列把左右两侧分别取出、各自拟合多项式。 这样不存在尺度矛盾,且天然把两条线分开。

为防误检,还加了几何约束。这里有一个关键的设计选择:

判据用的是填充率(连通域像素数 / 其包围盒面积),而不是包围盒宽度。 原因是车道线在鸟瞰图中常常是倾斜的(车辆偏离车道中心、或相机存在偏航时), 此时它的包围盒会很宽——实测一条完全正常的倾斜车道线,包围盒宽度达 114 px, 但填充率只有 0.27。若按「包围盒宽度 < 90 px」判断,这条真实车道线会被整条 拒绝,导致车辆偏右场景下的横向偏差误差达 25 px。

填充率则对竖直与倾斜车道线都成立:车道线很细,填充率必然低;而被误阈值化的 路面是面状结构,包围盒内几乎被填满,填充率很高。此外还限制包围盒宽度、 总面积与纵向长度,共同排除零碎小块与整块路面。

若连通域因虚线车道线或局部遮挡而断裂,会自动退回滑动窗口作为兜底。

2.4 几何量解算

设左右车道线在求值行上的列坐标为 u_l、u_r,则

车道中心      u_center = (u_l + u_r) / 2
横向偏差      lateral = (u_vehicle - u_center) * meters_per_pixel_x

其中 u_vehicle 取鸟瞰图水平中心(相机装在车辆中心、光轴朝前时, 图像横向中心对应车辆前进方向)。正值表示车辆偏在车道中心右侧。

航向偏差由车道中心线在求值行的斜率换算;曲率半径由一阶、二阶导数给出:

R = (1 + (dx/dy)^2)^(3/2) / |d²x/dy²|        (直道为无穷大)

求值行必须选在有车道像素的行上。 目标矩形底边略高于图像底行,若固定用 height-1 求值,会在没有任何数据的 行上外推多项式,横向偏差出现十几到几十像素的系统误差。本模块从掩码 统计出实际有像素的最底行再求值。

3. 环境准备

3.1 依赖

依赖 版本要求 说明
ROS Noetic — 虚拟机镜像内已提供
Python 3.8(ROS Noetic 自带) 算法本身兼容 3.8 ~ 3.12
numpy ≥ 1.18 唯一的算法依赖
CARLA 0.9.13(与 carla_ros_bridge 一致) 仅"实车运行"需要
OpenCV — 不需要(算法为纯 numpy 实现)
深度学习框架 — 不需要

3.2 已验证环境

本模块在以下环境完成过实际运行与测试。请按行对应——不同项目的验证环境不同, 不要跨行推断:

验证项目 操作系统 Python 依赖
118 个单元测试 Windows 11 3.8.10(numpy 1.24.4) numpy
118 个单元测试 Windows 11 3.12.8(numpy 2.5.3) numpy
84 个单元测试(标定用例加入前) Ubuntu 20.04(ROS Noetic 教学镜像) 3.8.10 numpy
demo_lane_detection.py 演示 Ubuntu 20.04 3.8.10 numpy
demo_lane_detection.py 演示 Windows 11 3.12.8 numpy
mkdocs serve --livereload 网页验证 Ubuntu 20.04 3.8.10 见 3.5 节
真实 CARLA 图像验证 Windows 11 3.12.8 numpy + carla 0.9.16

需要说明的三点:

  1. 单元测试在 Python 3.8.10 与 3.12.8 上都跑通了,结果一致 (均为 Ran 118 tests ... OK)。其中 3.8.10 正是 Ubuntu 20.04 的默认版本, 因此教学虚拟机上的兼容性有直接参照;但测试本身是在 Windows 上跑的, 没有在 Linux 上重跑。
  2. Ubuntu 侧跑过的是标定相关用例加入之前的 84 个测试;之后新增的用例 (相机模型、地面矩形、车道宽配对、航向符号、稳健重拟合、参数加载, 以及本轮补充的 8 个配置接线用例)只在 Windows 上验证过。
  3. 真实 CARLA 图像验证只在 Windows 上做过(Python 3.12.8 + carla 0.9.16)。 教学虚拟机侧没有做这项验证,原因是 CARLA 版本与桥接要求不一致、 且 RPC 只监听本机(详见 3.6 节)。

重要提示:算法与单元测试不依赖 CARLA、不依赖 ROS、不依赖 OpenCV, 因此可以在任何有 Python + numpy 的机器上直接复现(见 3.4 节)。

3.3 编译

# 为避免与教材章节中的同名包冲突,单独建立工作空间
mkdir -p ~/lane_ws/src
ln -s ~/ros2/src/ground/carla_lane_keeping ~/lane_ws/src/
cd ~/lane_ws
catkin_make
source devel/setup.bash

3.4 无需 CARLA / ROS 的验证方式(推荐给评审者)

这是最快确认本模块可用的方法,三分钟内可完成,不需要启动任何仿真器:

cd ~/ros2/src/ground/carla_lane_keeping

# 1) 单元测试:118 个测试,覆盖梯度、透视变换、拟合、几何量、ROS 节点、PID、参数接线
python3 -m unittest discover -s tests -v
#    预期结尾: Ran 118 tests ... OK

# 2) 端到端演示:生成合成图像 -> 检测 -> 输出量化指标并保存结果图
python3 tests/demo_lane_detection.py
#    预期: 3/3 个场景检出两侧车道线,最大测量误差 0.70 px(0.035 m)
#    结果图片保存在 demo_out/ 目录

# 3) 查看结果图(蓝=左车道线、红=右车道线、绿=中心线、黄十字=检出中心)
xdg-open demo_out/demo_1.png

numpy 是唯一前置依赖。若提示缺失:

pip3 install --user -i https://pypi.tuna.tsinghua.edu.cn/simple numpy

3.5 文档环境(mkdocs serve)的两处已知问题

若需要在本地预览文档网页,仓库现有的 requirements.txt 在 Python 3.8 环境(ROS Noetic 教学镜像的标配)下会遇到两个问题,这里记录解决办法:

问题一:mkdocs-bibtex 在 Python 3.8 上导入崩溃

现象:

TypeError: 'type' object is not subscriptable
  File ".../mkdocs_bibtex/citation.py", line 30, in Citation
    def from_markdown(cls, markdown: str) -> list[Citation]:

原因:新版本 mkdocs-bibtex 使用了 list[Citation] 这类内建泛型注解, 它在 Python 3.9+ 才可运行时求值。而 pip 在安装任何包时都会加载插件入口点, 因此这个崩溃会连带影响所有 pip install 操作。

解决办法:给该包的所有源文件加上惰性注解(不改动任何逻辑):

python3 - <<'PY'
import glob, os, ast
root = os.path.expanduser('~/.local/lib/python3.8/site-packages/mkdocs_bibtex')
for path in glob.glob(os.path.join(root, '**', '*.py'), recursive=True):
    src = open(path, encoding='utf-8').read()
    if 'from __future__ import annotations' in src:
        continue
    lines = src.splitlines(keepends=True)
    i = 0
    while i < len(lines) and (lines[i].startswith('#!') or 'coding' in lines[i][:40]):
        i += 1
    lines.insert(i, 'from __future__ import annotations\n')
    open(path, 'w', encoding='utf-8').write(''.join(lines))
    print('已修补', os.path.relpath(path, root))
PY

问题二:mkdocs-mermaid2-plugin 需要 Node.js,但 requirements.txt 未声明

现象:Error: cannot find module 'mermaid2',装好后变成 ModuleNotFoundError: No module named 'typing_extensions', 再之后是 Cannot find node executable。

原因:该插件依赖 beautifulsoup4(它又依赖 typing_extensions), 并在渲染时调用外部 node 进程,而 requirements.txt 里没有体现这些依赖。

解决办法:

sudo apt install -y nodejs npm
pip3 install --user -i https://pypi.tuna.tsinghua.edu.cn/simple \
    beautifulsoup4 typing_extensions soupsieve
pip3 install --user --no-deps -i https://pypi.tuna.tsinghua.edu.cn/simple \
    mkdocs-mermaid2-plugin

建议向仓库反馈:在 requirements.txt 中补充 Node.js 说明,并为 mkdocs-bibtex 固定一个兼容 Python 3.8 的版本(例如 mkdocs-bibtex<2.13)。

3.6 实车运行的限制说明

若要与 CARLA 实车联调,需要注意:

  1. CARLA 版本必须与桥接一致:仓库内 carla_ros_bridge 声明要求 CARLA 0.9.13(其 CARLA_VERSION 文件值为 0.9.13, README 亦写明 "This version requires CARLA 0.9.13")。 使用 0.9.16 会因 API 差异导致连接异常。
  2. CARLA 的 RPC 默认只监听本机(127.0.0.1:2000 / 41451), 虚拟机不能直接连宿主机的 CARLA。需按 设置并连接到 Carla 模拟器 的方式通过 carla_ros_bridge 转发图像话题。
  3. CARLA 0.9.16 在中文 Windows 下偶发字符串导出缺陷: world.get_map()、client.get_trafficmanager() 与车道侵入传感器 可能抛 UnicodeDecodeError,导致拿不到标准生成点。 本次实跑(2026-10-29)该接口已恢复正常,可以正常取到 155 个生成点; 若再次遇到,重启 CARLA 服务器通常即恢复,否则改用 0.9.13。

4. 运行

只想快速验证能不能跑? 请直接看 3.4 节——不需要 CARLA、不需要 ROS, 三分钟内即可复现全部结果。

4.1 前置条件

需要一个正在运行、且有车辆的 CARLA 环境,且能提供前视相机图像。 两种方式任选:

  • 方式一(推荐):先启动 CARLA 与桥接,图像话题为 /carla/ego_vehicle/rgb_front/image 参见设置并连接到 Carla 模拟器

  • 方式二:用本模块自带的独立采集脚本(不需要 ROS 桥接), 直接连接 CARLA 的 Python API 抓图并跑检测,适合快速验证算法

该方式依赖版本与桥接一致的 CARLA(0.9.13),限制见 3.6 节。

4.2 启动检测

# 方式一:一键启动(推荐)
roslaunch carla_lane_keeping main.launch

# 如果图像话题不同,用参数覆盖
roslaunch carla_lane_keeping main.launch image_topic:=/camera/image_raw

# 方式二:直接运行入口脚本
rosrun carla_lane_keeping main.py --mode detect

# 方式三:只启动检测节点
rosrun carla_lane_keeping lane_detector.py

4.3 查看结果

# 横向偏差(米,正 = 车辆偏右)
rostopic echo /lane/lateral_offset

# 航向偏差(弧度)
rostopic echo /lane/heading_error

# 是否偏离车道
rostopic echo /lane/departure

# 置信度
rostopic echo /lane/confidence

在 RViz 中可视化鸟瞰掩码:

rosrun rviz rviz
# Add -> Image -> Topic: /lane/mask

还可以用 rqt_image_view 查看实时画面。

5. 话题接口

方向 话题 类型 说明
订阅 /carla/ego_vehicle/rgb_front/image sensor_msgs/Image 前视彩色图(bgr8 / rgb8 / bgra8 / rgba8 / mono8 均支持)
发布 lane/lateral_offset std_msgs/Float32 横向偏差(米),正 = 车辆偏右
发布 lane/heading_error std_msgs/Float32 航向偏差(弧度)
发布 lane/curvature std_msgs/Float32 车道曲率半径(米),直道发 0
发布 lane/confidence std_msgs/Float32 置信度 0~1
发布 lane/departure std_msgs/Bool 横向偏差是否超过阈值
发布 lane/lane_width std_msgs/Float32 本帧实测车道宽(米),标定自检用
发布 lane/mask sensor_msgs/Image 鸟瞰二值掩码(调试,默认 5 帧发一次)

lane/lane_width 值得单独说一句:它是相机标定是否正确的直接指标。 这条通路里米/像素完全由相机高度与俯角决定,因此车道宽的测量值应与真实 车道宽(本仓库测试地图为 3.5 m)吻合。在 48 张真实 CARLA 帧(覆盖多个生成点 与扰动过程)上实测均值 3.80 m、中位数 3.87 m、范围 3.07~3.97 m;若它系统性 偏大 20%,说明 camera_height_m 填小了,此时横向偏差也会同比例失真——这比 "看横向偏差像不像"要可靠得多。

未检出时不发布横向偏差。 这一点很重要:如果感知失效时发布 0,下游控制器会误以为"车辆正好居中"而 继续直行,这在真实车辆上是危险的。本模块在未稳定检出时只更新置信度, 由控制器依据置信度与超时来决定停止输出。

6. 参数说明

完整参数见 config/lane_params.yaml,参数名与代码字段一一对应。 几个关键参数的标定方法:

标定模式(传入 CameraModel)下真正需要填的是相机安装参数, 鸟瞰图的「米/像素」由它们算出来,不需要手工量。lane_params.yaml 里 对应的 ROS 参数带 camera_ 前缀,use_calibration: true 时生效:

ROS 参数 代码字段 含义 怎么得到
use_calibration — 是否启用标定模式 真机保持 true;跑合成图可设 false
camera_height_m mount_height_m 相机光心离地高度 卷尺量,默认 1.70
camera_pitch_deg pitch_deg 光轴俯角(向下为正) 量相机安装角,默认 8.0;CARLA 里 pitch=-8
camera_hfov_deg hfov_deg 水平视场角 相机规格,默认 90.0
d_near_m / d_far_m 同名 鸟瞰图覆盖的前向距离区间 默认 3.5~15 m,受视场限制(见 7.2 节)
bev_half_width_m 同名 鸟瞰图横向半宽 默认 3.2 m,近端再宽就超出 45° 半视场
lane_width_m / lane_width_tol_m 同名 车道宽先验与容差 默认 3.5 m ± 0.6 m
hist_band_m 同名 统计直方图用的距离段 默认 [5.0, 13.0],两端各避噪声
max_stale_frames 同名 检出失败时沿用上一帧的最大帧数 默认 10(20 Hz 下 0.5 s)

camera_height_m、camera_pitch_deg 也可以在 launch 里临时覆盖,不必改文件:

roslaunch carla_lane_keeping main.launch camera_height:=2.10 camera_pitch:=12.0

其余影响鲁棒性的参数:

参数 含义 调整方向
white_min 白漆的三通道最小值阈值 漏检白线调小,误检路面调大(默认 160)
sobel_auto_percentile 梯度自适应阈值分位数 越大越严格
angle_max_deg 允许的最大梯度方向角 弯道多则调大
min_lane_pixels 单侧最少车道像素数 随分辨率与线宽调整
refine_tol_px 稳健重拟合的取点半宽 线宽、噪声大时调大

比例模式(不传 CameraModel,仅用于合成图像与单元测试)仍按 bottom_half_width_ratio 等比例参数圈梯形,此时横向偏差没有物理意义, 必须手工给出 meters_per_pixel_x(量出鸟瞰图中左右线间距再除以真实车道宽)。

7. 实测结果

7.1 合成图像(真值已知)

合成图像中车道线位置是解析给定的,因此可以直接算出测量误差:

场景 车道中心测量误差
车辆位于车道正中 −0.5 px(−0.025 m)
车辆偏右 100 px(车道线在鸟瞰图中倾斜) +0.7 px(+0.035 m)
叠加高斯噪声 σ=12 −0.5 px(−0.026 m)

误差均在 0.1 m 以内。第二个场景是倾斜车道线,早期版本因几何判据设计缺陷 在此处误差达 25 px,修正后降到 0.7 px。

合成场景检测结果

对应上表第一行(车辆位于车道正中):蓝线拟合左车道线,红线拟合右车道线, 绿色竖线是拟合出的车道中心,黄色十字是车辆位置。

鸟瞰二值掩码

同一帧经过透视变换与方向梯度筛选后的鸟瞰二值掩码。两条车道线被分离成互不连通的 细长区域,后续在这一步上做连通域分组与多项式拟合。

7.2 真实 CARLA 前视图像

真实图像和合成图像之间隔着一整类问题。早期版本在合成图上误差 0.035 m, 但接到 CARLA 前视相机上完全不能用——一帧里报出的横向偏差能到 13~33 m, 而车其实好端端在车道中间。把这一帧拆开看,原因有三层:

层 现象 根因
视野 梯形只覆盖车前 1.7~6.4 m、横向 ±2 m 用手工比例(0.42/0.58)圈区域,与地面距离没有确定关系
掩码 二值掩码占全图 18.15% 颜色阈值 white_min=190 把天空、白墙一起算了进去;梯度通道只占 0.47%
配对 车道宽报出 0.92 m / 8.84 m 只要求「两峰分开 40 px」,于是把双黄线的两条线、或相邻车道的线配成了左右边界

对应的三处修改:

  1. 相机标定取代手工梯形(CameraModel + ground_region_homography)。 由针孔投影把「地面矩形 d ∈ [3.5, 15] m、y ∈ [-3.2, 3.2] m」的四角 算成像素,再求单应矩阵。鸟瞰图从此有确定的标度:0.008 m/像素(横向)。 区域上界不能再远:FOV 90°、离地 1.7 m、俯角 8° 时地平线在第 244 行, 车前 15 m 已经到第 289 行,再远竖向分辨率就退化到 1 m/像素以上。
  2. 白色阈值降到 160。实测白漆的三通道最小值是 170~189,沥青路面是 128~135,取 160 能干净分开;原来的 190 会把真实白线整条漏掉(合成图上 白漆是 255,所以这个问题一直没暴露)。
  3. 按车道宽先验配对(find_lane_pair)。鸟瞰图是地面正视图,左右边界的 列距恒等于车道宽,于是直接穷举 |Δ − 3.5 m| ≤ 0.6 m 的所有列对, 取两侧都够强、且中心最靠近车辆中心线的那一对。双黄线(间距 0.25 m) 和相邻车道(间距 7 m)都自动被排除。

验证方法:用地图的标准生成点放车,这些点位于车道中心,真值 e_y ≈ 0。 取 10 个标准生成点(#5/#15/…/#95),每点 20 帧,共 200 帧:

指标 数值
9 个路段生成点 每点 19 / 20 通过合理性检查(95%),仅首帧未出图时缺一帧
#95(路口) 0 / 20,该点正对斑马线与双黄线,视野内没有可配对的车道边界
实测车道宽 3.45 ~ 3.75 m(9 个路段点均值,真实车道约 3.5 m)
|e_y| 路段点均值在 -0.34 ~ +0.32 m 之间(标准生成点仍可能有半米级的横向偏移)

「通过合理性检查」指车道宽落在 1.5~8.0 m 且两侧拟合都存在;口径由 tools/run_on_carla.py 统一给出,逐帧可复现。

真实前视帧的检测结果

左:真实前视帧上叠加拟合结果,绿线为左车道线、蓝线为右车道线;该帧报出 e_y = -0.015 m、车道宽 3.93 m、置信度 1.00。右:同一帧的鸟瞰二值掩码, 黄线是车辆所在列——两条车道边界对称地落在它两侧,这正是"车在车道中间"的 视觉形态,也是本模块判定偏差的依据。

实测车道宽这一项可以做独立交叉验证:把左双黄线与右侧白线在图像里的位置 逐行反投影到地面,得到的横向间距是 3.72 m,与检测器给出的 3.71 m 只差 1 cm。两条完全不同的计算路径得到同一个数,说明标定与拟合是自洽的。

误差的 90 分位偏大主要来自两处:每个位置的第 1 帧(车辆刚生成,悬架还在 下沉),以及一段 4.3 m 宽的宽车道——那里车辆本身就不在几何中心。

本节替换了早期基于俯视相机的验证。俯视视角下车道线又粗又规整, 测出来好看,但它不代表车载前视相机的真实难度;现在这套结果是在与 实际部署一致的前视视角下得到的。检测进入闭环后与地图真值的逐帧对比 见 车道保持控制 第 9 节「闭环实跑验证」。

7.3 正确性校验

本模块的自动化测试覆盖了从像素到物理量的完整链路,共 118 个测试:

  • 图形变换:Sobel 梯度、方向阈值、颜色阈值
  • 单应矩阵:四点映射精度、透视变换往返一致性、越界处理
  • 车道像素:直方图定基点、连通域拟合、填充率约束、像素不足时的拒绝
  • 几何量:与解析曲率公式对照、横向偏差符号、外推保护
  • 端到端:合成图像往返验证、噪声鲁棒性、倾斜车道线不被误杀、 只检出一侧时的行为
  • 相机标定:投影↔反投影互逆、地平线位置、地面矩形四角映射、标度换算、 区域越界时拒绝构造
  • 车道宽先验配对:能挑出间距等于车道宽的列对、拒绝双黄线那种窄间距、 中心先验在两组候选中生效
  • 航向符号:车道向远处右偏时 e_psi 必须为负、左偏时为正,且幅值与 atan(du/dv · mx/my) 一致
  • 稳健重拟合:混入 120 px 外的杂散像素后,残差仍能收敛到 3 px 以内
  • ROS 节点:图像编解码(含行填充)、参数加载(含 rosparam 二元组还原)、 相机内参加载与投影互逆、PID 限幅与符号约定
  • 配置接线:run_on_carla.py 取车道宽度用的是检测器算好的米制值而非 dataclass 默认的 0.05 m/px、main.launch 把控制参数载入控制器私有命名空间、 检测段与控制段两份 yaml 不重名(防止再退化成只载一次)

7.4 如何复现以上结果

结果 复现命令 预期输出
7.1 合成图像精度 python3 tests/demo_lane_detection.py 3/3 场景检出,最大误差 0.70 px
7.3 单元测试 python3 -m unittest discover -s tests -v Ran 118 tests ... OK
结果图片 同上(演示脚本会自动生成) demo_out/demo_1.png 等

两条命令都只需要 numpy,不需要 CARLA、ROS 或 OpenCV, 可在任意 Linux / Windows 机器上复现。详细步骤见 3.4 节。

8. 常见问题

Q: 一直提示"未稳定检出车道线"

按顺序排查:

  1. 确认相机图像正常:rostopic echo /carla/ego_vehicle/rgb_front/image/height
  2. 用 rqt_image_view 看画面,确认确实拍到车道标线—— 这是最常见的原因:车辆停在广场/停车场而不是车道上
  3. 检查相机俯仰角:太低会只看到天空与远处,太高会看到大量车头
  4. 调小 sobel_auto_percentile 与 sobel_min 提高灵敏度

Q: 横向偏差的数值明显偏大或偏小

先看 lane/lane_width。标定模式下的比例尺完全由相机参数决定,若车道宽测得 3.9 m 而实际 3.5 m(偏大 11%),横向偏差也会同比偏大 11%,此时该调的是 camera_height_m 与 camera_pitch_deg,而不是别的阈值。只有把 use_calibration 关掉退回比例模式时,才需要手工标定 meters_per_pixel_x。

Q: 横向偏差符号相反

本模块的符号由地面坐标系定义:y 轴向右为正,因此"正 = 车辆偏右"。 相机若正装(朝车头方向),符号不会反;若控制器收到偏差后打错方向, 改控制器的 invert_steering 参数即可,不必动感知。

Q: 曲率半径一直是很大的值

直道时曲率半径理论上是无穷大,本模块发 0 表示直道。若数值在几千到几万米 之间波动,属于正常的数值噪声(多项式二阶导很小)。

9. 后续改进方向

  • 时序滤波:检测器已有"检出失败沿用上一帧"的保持机制,但每帧仍是独立 重检测;可加入卡尔曼滤波,把曲率也纳入状态量
  • 车道线跟踪:用上一帧的拟合曲线直接约束本帧的搜索带,进一步提速并 提升遮挡场景的连续性
  • 多车道识别:当前只识别左右两条,可扩展为识别全部车道
  • 在线自标定:用车道宽先验反解相机高度与俯角,容忍安装误差

参考文献