【Android车载学习笔记】第五天:汽车轨迹线绘制算法

车载AVM动态倒车轨迹线绘制算法

动态随动轨迹线是360全景环视(AVM)、倒车影像核心辅助功能,可实时根据方向盘转角预测车辆行驶包络线,降低剐蹭风险。

本文从底层阿克曼转向运动学理论切入,完整拆解轨迹点生成数学推导、坐标转换、图像渲染全流程;对比当下车载行业实时几何计算、查表预计算、高阶平滑规划三大主流实现方案;配套完整可运行仿真代码逐行解析,兼顾算法理论深度与工程落地实操,适合车载HMI、环视视觉开发人员直接发布参考。

一、行业背景与轨迹线分类

1.1 轨迹线功能价值

低速倒车、挪车场景下,驾驶员无法直观判断车轮与车身外侧行驶路径,动态轨迹线将车辆未来行驶轨迹以彩色线条叠加在摄像头画面上层,区分车轮轨迹、车身安全边界、距离标尺,是乘用车标配安全功能。

1.2 两类主流轨迹方案

  1. 静态固定轨迹(低端后视摄像头)
    无方向盘转角信号输入,轨迹弧线固定不变,仅作粗略参考,无实时性,已逐步淘汰。
  2. 动态随动轨迹(中高端AVM全景主流)
    CAN总线实时读取方向盘转角,毫秒级更新轨迹曲线,精准匹配车辆转向姿态,本文重点讲解该方案底层算法。

1.3 车载系统完整数据流

方向盘转角传感器 → 车机MCU/视觉SOC → 阿克曼几何计算世界轨迹点 → 相机IPM逆透视投影/鱼眼畸变矫正 → 图像像素坐标转换 → GPU叠加渲染线条输出屏幕。

二、底层核心:阿克曼转向运动学模型

2.1 模型基础定义(车辆固有参数)

参数 符号 说明 家用轿车典型值
轴距 LLL 前后轴中心点纵向距离 2.7m
轮距 WWW 左右后轮横向间距 1.8m
半轮距 W/2W/2W/2 后轴中点到单侧车轮距离 0.9m
前轮等效转角 δ\deltaδ 方向盘转角换算前轮倾斜角度,rad ±35°最大
瞬时转向半径 RRR 后轴中心绕ICR转弯半径 3.5~6m
ICR瞬时转向中心 OOO 四轮纯滚动共同圆心 左转(-R,0),右转(R,0)

2.2 转弯半径核心公式推导

车辆转弯时四轮绕同一瞬时圆心纯滚动,由直角三角几何关系得后轴中心转弯半径:

R=Ltan⁡δR = \frac{L}{\tan\delta}R=tanδL

  • 方向盘回正 δ≈0\delta≈0δ≈0,tan⁡δ→0\tan\delta→0tanδ→0,R→∞R→∞R→∞,车辆直行;
  • 方向盘打死 δ\deltaδ 最大,RRR 最小,转弯弧度最急。

2.3 统一坐标系约定(代码标准坐标系)

后轴中心点 为世界坐标系原点(0,0)(0,0)(0,0):

  1. +Y+Y+Y:车辆车头前进方向(倒车Y值减小);
  2. −X-X−X:车辆左侧,+X+X+X:车辆右侧;
  3. 地面所有轨迹点均基于该二维平面计算,无高度维度(仅考虑地面行驶)。

2.4 圆弧轨迹点生成数学模型(弧长极坐标法)

2.4.1 弧长与圆心角转换公式

车辆沿圆弧向前行驶地面距离sss(弧长),绕ICR圆心转过弧度角θ\thetaθ:

θ=sR\theta = \frac{s}{R}θ=Rs

相比传统"已知y解方程求x"的开根号方案,极坐标三角函数法无开方运算,嵌入式设备算力开销更低,点间距均匀、曲线顺滑,是车载行业标准采样方式。

2.4.2 左转/右转轨迹坐标方程
  1. 左转(δ>0\delta>0δ>0,ICR圆心O(−R,0)O(-R,0)O(−R,0))

    后轴中心轨迹:

    {xcenter=−R+R⋅cos⁡θycenter=R⋅sin⁡θ \begin{cases} x_{center} = -R + R\cdot\cos\theta \\ y_{center} = R\cdot\sin\theta \end{cases} {xcenter=−R+R⋅cosθycenter=R⋅sinθ

    左轮(内侧轮,转弯半径R−W/2R-W/2R−W/2):

    {xleft=−R+(R−W2)⋅cos⁡θyleft=(R−W2)⋅sin⁡θ \begin{cases} x_{left} = -R + (R-\tfrac{W}{2})\cdot\cos\theta \\ y_{left} = (R-\tfrac{W}{2})\cdot\sin\theta \end{cases} {xleft=−R+(R−2W)⋅cosθyleft=(R−2W)⋅sinθ

    右轮(外侧轮,转弯半径R+W/2R+W/2R+W/2):

    {xright=−R+(R+W2)⋅cos⁡θyright=(R+W2)⋅sin⁡θ \begin{cases} x_{right} = -R + (R+\tfrac{W}{2})\cdot\cos\theta \\ y_{right} = (R+\tfrac{W}{2})\cdot\sin\theta \end{cases} {xright=−R+(R+2W)⋅cosθyright=(R+2W)⋅sinθ

  2. 右转(δ<0\delta<0δ<0,ICR圆心O(R,0)O(R,0)O(R,0))

    后轴中心轨迹:

    {xcenter=R−R⋅cos⁡θycenter=R⋅sin⁡θ \begin{cases} x_{center} = R - R\cdot\cos\theta \\ y_{center} = R\cdot\sin\theta \end{cases} {xcenter=R−R⋅cosθycenter=R⋅sinθ

    左轮(外侧轮,转弯半径R+W/2R+W/2R+W/2)、右轮(内侧轮,转弯半径R−W/2R-W/2R−W/2)同步切换半径。

2.4.3 直行特殊分支处理

当∣δ∣<10−4|\delta|<10^{-4}∣δ∣<10−4(近似0转角),RRR无穷大,轨迹简化为垂直直线:

左轮固定x=−W/2x=-W/2x=−W/2,右轮固定x=W/2x=W/2x=W/2,仅Y轴随行驶距离递增,规避tan⁡0\tan0tan0除零报错。

三、轨迹绘制完整流水线(算法分步拆解)

步骤1:CAN信号输入与参数归一化

  1. 读取方向盘转角电压信号,标定换算前轮等效转角δ\deltaδ;
  2. 限制转角区间−max_angle,max_angle-max\\_angle,max\\_angle−max_angle,max_angle,过滤方向盘过度转动异常值;
  3. 载入车辆标定固有参数:轴距、轮距、最大轨迹距离(车前8米)、采样步长(0.05m,步长越小曲线越平滑)。

步骤2:世界坐标轨迹点批量生成

循环累加行驶弧长sss,逐点计算左轮、右轮、车身中心三组轨迹世界坐标(x,y)(x,y)(x,y),输出离散点列表。

行业优化点:步长统一0.05m,兼顾顺滑度与计算量;最大轨迹距离8m匹配摄像头有效视野,过滤远处无效点。

步骤3:世界坐标 → 图像像素坐标转换

地面米制坐标无法直接绘制屏幕,需要缩放+坐标平移映射:

{px=img_w2+x⋅scalepy=img_h−bottom_margin−y⋅scale \begin{cases} px = \frac{img\_w}{2} + x\cdot scale \\ py = img\_h - bottom\_margin - y\cdot scale \end{cases} {px=2img_w+x⋅scalepy=img_h−bottom_margin−y⋅scale

  • scalescalescale:比例尺(像素/米),控制画面放大缩小;
  • bottom_marginbottom\_marginbottom_margin:车尾底部留白,避免车身矩形贴画布底边;
  • 图像像素坐标系:画布左上角为原点,Y轴向下,因此地面+Y(车头)映射为图像-py。

步骤4:图像抗锯齿线条渲染

  1. 三组轨迹线分层绘制:中心灰色参考线、左轮绿色线、右轮红色安全边界线;
  2. 每隔固定采样点绘制横向连接线,模拟AVM距离标尺;
  3. 绘制车身矩形框、后轮起点标记圆,直观区分车辆当前位置;
  4. 启用LINE_AA抗锯齿,消除车载屏幕线条锯齿感。

步骤5:嵌入式后置处理(实车必备,仿真代码简化省略)

  1. 鱼眼畸变矫正:基于Brown-Conrady模型修正摄像头广角拉伸失真;
  2. IPM逆透视变换:将地面鸟瞰世界点映射到后视/环视摄像头原始图像;
  3. 转角低通滤波:平滑方向盘抖动,防止轨迹线高频闪烁;
  4. GPU纹理叠加:轨迹预生成纹理,与摄像头画面混合输出,降低CPU占用。

四、车载行业三大主流轨迹实现算法对比

4.1 方案一:实时阿克曼几何计算(本文仿真代码方案,主流中高端AVM)

原理

上电后根据实时方向盘转角,通过三角函数实时计算每帧轨迹点,无预存数据。

优势
  1. 适配任意车型轴距、轮距标定,参数可动态配置;
  2. 轨迹精度最高,左右转向无失真,包络线完全匹配真实车轮路径;
  3. 支持自定义轨迹长度、采样精度,适配不同摄像头视野。
劣势

每帧需要sin/cos浮点运算,低端8位MCU算力不足会产生延迟;

适用场景

10万以上车型360全景环视、安卓/QNX车载HMI视觉SOC。

4.2 方案二:转角分段查表法(低成本倒车影像专用)

原理

出厂前将前轮转角离散分段(如每2°一档),预计算对应轨迹点存储Flash表格;运行时读取当前转角,查表直接输出坐标,跳过实时三角函数计算。

优势

几乎无实时浮点运算,单片机即可驱动,成本极低;

劣势

转角档位离散,方向盘小幅转动时轨迹跳跃;车型标定参数修改需要重新烧录表格;

适用场景

低端燃油车单路后视倒车影像、低成本后装导航。

4.3 方案三:高阶平滑曲线规划(自动驾驶APA自动泊车、高速领航轨迹)

阿克曼圆弧仅适用于低速恒定转角场景,自动泊车、换道需要连续变曲率平滑轨迹,行业采用两类曲线:

  1. 五次多项式曲线
    约束起点/终点位置、速度、加速度连续,换道路径无顿挫,用于高速局部路径规划;
  2. 回旋曲线(Clothoid)/三阶贝塞尔曲线
    曲率线性平滑过渡,衔接直行与转弯圆弧,APA泊车多段拼接轨迹核心算法。
对比阿克曼圆弧

阿克曼:单一固定曲率圆弧,仅适配恒定方向盘角度 低速倒车;

多项式/贝塞尔:可变曲率,适配方向盘持续转动、动态避障场景,计算复杂度大幅提升。

算法方案 计算开销 轨迹平滑度 适用场景
实时阿克曼几何 中(浮点三角函数) 高(均匀采样) AVM动态倒车轨迹
转角查表法 极低(数组读取) 一般(档位跳变) 低端倒车影像
五次多项式/贝塞尔 高(方程组求解) 极高(连续曲率) 自动泊车、自动驾驶规划

五、完整仿真代码逐行解析(可直接运行出图)

python 复制代码
import cv2
import numpy as np
import math
import os

# ===================== 车辆标定固有参数(实车标定修改此处) =====================
wheel_base = 2.7    # 轴距 m
track_width = 1.8   # 轮距 m
max_angle = math.radians(35)  # 前轮最大转角35度

def calc_wheel_trajs(steer_angle, step=0.05, max_dist=8.0):
    """
    核心函数:基于阿克曼模型生成左轮、右轮、车身中心三组世界轨迹点
    :param steer_angle: 前轮等效转角,rad,正数左转、负数右转
    :param step: 轨迹采样步长,单位m,越小曲线越平滑
    :param max_dist: 轨迹向前最大绘制距离,单位m
    :return: left_pts左轮, right_pts右轮, center_pts车身中心 世界坐标列表
    """
    half_track = track_width / 2.0
    left_pts = []
    right_pts = []
    center_pts = []

    # 分支1:直行,转角接近0,规避tan(0)除零错误
    if abs(steer_angle) < 1e-4:
        s = 0.0
        while s <= max_dist:
            left_pts.append((-half_track, s))
            right_pts.append((half_track, s))
            center_pts.append((0.0, s))
            s += step
        return left_pts, right_pts, center_pts
    
    # 分支2:转弯行驶,计算转弯半径
    turn_left = steer_angle > 0
    radius = abs(wheel_base / math.tan(steer_angle))
    s = 0.0
    while s <= max_dist:
        theta = s / radius  # 弧长换算圆心角 核心公式
        if turn_left:
            # 左转:圆心(-R,0),左轮半径R-half_track,右轮R+half_track
            cx = -radius + radius * math.cos(theta)
            cy = radius * math.sin(theta)
            lx = -radius + (radius - half_track) * math.cos(theta)
            ly = (radius - half_track) * math.sin(theta)
            rx = -radius + (radius + half_track) * math.cos(theta)
            ry = (radius + half_track) * math.sin(theta)
        else:
            # 右转:圆心(R,0),左轮半径R+half_track,右轮R-half_track
            cx = radius - radius * math.cos(theta)
            cy = radius * math.sin(theta)
            lx = radius - (radius + half_track) * math.cos(theta)
            ly = (radius + half_track) * math.sin(theta)
            rx = radius - (radius - half_track) * math.cos(theta)
            ry = (radius - half_track) * math.sin(theta)
        center_pts.append((cx, cy))
        left_pts.append((lx, ly))
        right_pts.append((rx, ry))
        s += step
    return left_pts, right_pts, center_pts

def world_to_pixel(pt, img_w, img_h, scale, bottom_margin):
    """ 世界米坐标 转换 画布像素坐标 """
    x, y = pt
    px = int(img_w / 2 + x * scale)
    py = int(img_h - bottom_margin - y * scale)
    return [px, py]

def draw_polyline(img, pts, color, thickness=2):
    """ 绘制抗锯齿轨迹折线 """
    if len(pts) > 1:
        cv2.polylines(img, [np.array(pts, dtype=np.int32)], False, color, thickness, cv2.LINE_AA)

def draw_car_trajectory():
    # 画布配置
    img_w, img_h = 800, 600
    img = np.zeros((img_h, img_w, 3), dtype=np.uint8)
    scale = 55        # 比例尺:1米=55像素
    bottom_margin = 60 # 车尾底部留白像素
    steer = math.radians(20) # 测试:左转20度,负数改为右转

    # 1.生成世界轨迹点
    left_pts, right_pts, center_pts = calc_wheel_trajs(steer)
    # 2.批量转换像素坐标
    left_pix = [world_to_pixel(p, img_w, img_h, scale, bottom_margin) for p in left_pts]
    right_pix = [world_to_pixel(p, img_w, img_h, scale, bottom_margin) for p in right_pts]
    center_pix = [world_to_pixel(p, img_w, img_h, scale, bottom_margin) for p in center_pts]

    # 3.绘制三组轨迹线 BGR颜色通道
    draw_polyline(img, center_pix, (180, 180, 180), 1) # 车身中心 灰色细线
    draw_polyline(img, left_pix, (0, 255, 0), 2)       # 左轮 绿色
    draw_polyline(img, right_pix, (0, 0, 255), 2)      # 右轮外侧安全边界 红色

    # 绘制横向距离标尺连接线
    for i in range(0, min(len(left_pix), len(right_pix)), 20):
        cv2.line(img, tuple(left_pix[i]), tuple(right_pix[i]), (80, 80, 80), 1, cv2.LINE_AA)

    # 绘制车身矩形框
    car_w = int(track_width * scale)
    car_l = int(4.5 * scale) # 车长4.5米
    rear_cx, rear_cy = img_w // 2, img_h - bottom_margin
    cv2.rectangle(img, (rear_cx - car_w // 2, rear_cy - car_l),
                  (rear_cx + car_w // 2, rear_cy), (255, 255, 255), 2)
    cv2.line(img, (rear_cx, rear_cy), (rear_cx, rear_cy - car_l), (255, 255, 0), 1, cv2.LINE_AA)

    # 后轮起点标记圆点
    cv2.circle(img, tuple(left_pix[0]), 5, (0, 255, 0), -1, cv2.LINE_AA)
    cv2.circle(img, tuple(right_pix[0]), 5, (0, 0, 255), -1, cv2.LINE_AA)

    # 保存图片到脚本同级目录
    output_file = os.path.join(os.path.dirname(__file__), "test_car_trajectory.png")
    cv2.imwrite(output_file, img)
    print(f"轨迹仿真图已保存:{output_file}")

    # 环境变量DISPLAY_IMAGE=1时弹窗预览图片
    if os.environ.get("DISPLAY_IMAGE", "0") == "1":
        cv2.imshow("Car Trajectory Simulation", img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

if __name__ == "__main__":
    draw_car_trajectory()

代码运行效果说明

六、实车工程常见问题与优化方案

6.1 轨迹左右不对称、偏移失真

  • 根因:轴距/轮距标定参数错误;转角换算系数偏移;左右转向半径公式混用;
  • 优化:实车场地标定,测量真实轴距、轮距,单独校验左转/右转分支坐标方程。

6.2 轨迹线条持续闪烁抖动

  • 根因:CAN方向盘转角传感器噪声,每帧转角剧烈跳变;
  • 优化:一阶低通滤波平滑转角输入,限制单帧转角最大变化量。

6.3 嵌入式设备帧率低、画面卡顿

  1. 预计算三角函数表,替代实时sin/cos运算;
  2. 减少采样点步长,降低每帧绘制点数量;
  3. GPU离线生成轨迹纹理,图层叠加渲染,CPU仅传输坐标数据。

6.4 摄像头画面轨迹错位,不贴合地面

  • 根因:缺少鱼眼畸变矫正、未做IPM逆透视变换;
  • 优化:先矫正摄像头畸变,再将世界轨迹点通过透视矩阵映射到原始图像像素。

七、总结

动态倒车轨迹线核心依托阿克曼转向几何模型,通过弧长极坐标法均匀生成车轮行驶离散坐标,经坐标缩放映射、抗锯齿渲染输出辅助线条。

行业根据硬件算力、功能定位分为查表法、实时几何计算、高阶平滑曲线三类实现方案:低端后视影像采用查表法,360全景AVM使用实时阿克曼计算,自动泊车与自动驾驶采用五次多项式、贝塞尔变曲率曲线。