
“21届走马观碑车模运行视频未加入视觉”这个标题很多人看到后的第一反应可能是既然项目叫“走马观碑”视觉识别才是核心没加视觉是不是意味着只是个半成品我的看法恰恰相反。在智能车和竞赛车模这类项目里“未加入视觉”不是一个缺项而是一个被太多团队跳过去、最后又回来补课的里程碑。车模和普通的小车 demo 有本质区别。它更接近一个真实系统电池供电所以电压会掉机械结构存在摩擦电机有死区编码器有噪声处理器控制周期不完全是固定间隔。只要有一环没稳住就算摄像头已经识别出赛道和标牌车也早就冲出去了。很多团队连续几周调视觉仍然跑不出成绩不是因为识别不够准而是底层运动控制没有给视觉提供一个稳定平台。这种情况在智能车竞赛、电子设计竞赛以及机器人毕设里反复出现。所以这篇文章我想借“21届走马观碑车模运行视频未加入视觉”这个案例把无视觉阶段到底解决了什么问题、需要哪些硬件和软件基础、运动控制代码怎么写、运行结果怎么验证、常见坑有哪些完整拆解一遍。无论你是在做竞赛车模、课程设计还是独立机器人项目这套“先把车开稳再让车看懂路”的开发顺序都值得长期参考。1. 项目名里最容易被低估的四个字“走马观碑”这个项目名取得很有意思。字面意思是行走的马经过碑文时快速浏览对应到车模项目上可以理解为小车在运动中识别路侧标志物或文字。目标是运动与视觉的结合但从视频标题来看当前版本只完成了前一半让车先跑起来而且跑得稳。这个开发节奏是值得赞赏的。很多人一接到车模项目就迫不及待地安装摄像头、跑深度学习模型、做目标检测结果车一动就翻或者识别到了目标但车根本来不及反应。原因不复杂视觉感知解决的是“看到什么”运动控制解决的是“车稳不稳”两者是串行关系不是并行关系。视觉识别需要稳定的画面需要相机随车体运动时不会因为抖动产生模糊需要底盘能在规定时间内完成转向和刹车。这些前提条件没有满足视觉算法再快也没有用。从“未加入视觉”这个阶段要交付的结果来看至少包含这几项能力电机的 PWM 驱动可靠正反转切换无异常。编码器能准确测量车轮转速数据不丢脉冲。速度环可以稳定输出直线不跑偏急停不甩尾。转向机构响应灵敏舵机或差速转向有明确的中位。车模支持串口或遥控方式下发指令方便后续扩展。这些能力全部打通之后视觉模块才是一个“可插入”的增量。否则所谓视觉版就是在不稳定底板上叠一个更不稳定的模块。所以在分析这个视频时真正值得看的东西不是“现在能跑多快”而是“它是否建立了一条可以继续接入视觉的稳定基线”。本节可以给出一个明确结论“未加入视觉”不是这个项目的短板而是它最扎实的起点。后续加视觉如果效果不好回滚到这个版本也是一种有效的调试手段。没有这个基线后续所有调优都缺乏参照物。2. 无视觉版车模的系统拆解与运行逻辑车模和普通桌面小车最大的不同是它必须按照“输入→控制→执行→反馈→修正”的闭环模型工作。很多初学者写小车代码习惯写成开环向前走 1 秒停止转弯。这种方式在无负载、平整桌面上勉强能跑一旦放到真实赛道或复杂地面结果完全不可控。因此车模项目从第一天起就应该按闭环系统设计。从硬件角度看一个典型的无视觉版竞赛车模由以下几部分构成。注意这里不写死具体型号不同学校、不同比赛的方案差异很大但功能模块基本一致。模块作用说明车模底盘承载所有设备决定机械特性常见两驱、四驱也有转向舵机加后驱结构驱动电机提供前进动力常见直流减速电机带编码器接口电机驱动板将控制信号转为电机电压常见 H 桥驱动需要共地编码器测量车轮转速用于速度闭环是运动控制核心反馈转向机构改变行驶方向舵机或差速转向两种常见方案主控板运行控制算法STM32、Arduino、MSP430 等均可遥控接收机接收上位控制指令也可以使用串口或预置轨迹替代电池及电源管理提供动力与稳定电压常见航模电池注意电压跌落从软件角度看无视觉版车模的程序并不复杂但它必须有一个清晰的主循环结构。最忌讳的是把所有代码都堆在loop或者while(1)里面导致某一帧执行时间过长控制周期抖动。一个合理的软件数据流大概是这样的遥控或串口指令先进入“指令解析模块”解析后的目标速度与目标转向角度被送入控制算法。速度环根据编码器反馈计算 PWM 输出转向控制根据舵机角度映射输出对应脉宽。底层还要有一个监控模块负责检测电池电压、编码器异常、通信超时并在异常时执行安全停车。无视觉版虽然不处理图像但依然可以做很多事。最简单的是遥控模式适合测试硬件和控制参数进阶一点是预置轨迹模式让车按照固定的速度曲线和转向曲线跑再进阶可以做基本的直线保持、循线如果是电磁或红外甚至可以利用编码器实现简单的里程计估算行驶距离。这些都不需要摄像头却能为视觉版提供非常宝贵的运动学模型数据。这一节的关键在于理解车模是一个实时闭环系统代码结构必须为主循环的确定性服务。先把这个意识建立起来后面写成什么样都不会太偏。3. 开发环境与工具链准备在实际开始写代码之前环境准备决定了后面调试效率。如果你的项目还在早期请先花半天时间把工具链理顺不要上来就插电跑车。首先是集成开发环境。不同主控有不同选择STM32 系列常用 Keil 或 STM32CubeIDEArduino 系列用 Arduino IDE 或 PlatformIOMSP430 用 CCS。工具的选择不是最重要的重要的是你能完成编译、烧录、调试三步闭环。建议在拿到板子后第一个任务是点亮板载 LED确认工具链没问题而不是直接写 PID。其次是串口调试工具。车模调试几乎离不开串口。常见 Windows 下推荐 Vofa、SerialPlot或者直接用 Python 写一个简单的串口绘图脚本。串口的作用有两个一是打印内部状态包括目标速度、当前速度、PID 输出、控制周期二是远程下发指令。这里建议统一使用 115200 波特率数据结构做到简单可解析方便后续做上位机。然后是电源与测量设备。车模调试期间强烈建议使用稳压电源供电而不是只靠电池因为稳压电源可以限制电流防止 PID 参数爆错导致电机堵转时烧毁驱动。万用表用于检查电压、通断、共地状态。对于电机 PWM 波形、编码器相位等信号如果条件允许用逻辑分析仪或示波器看一眼很多怪问题立刻就有答案。最后是机械检查工具。车模跑不直很多时候不是算法问题而是前后轮不平行、左右轴承阻力不一致、轮胎磨损不均匀。在写任何控制代码之前手动推一下车检查四个轮子是否顺畅悬空抬起车用手转动电机轴感受传动阻力是否均匀。这类机械问题如果没有在前期排除后面 PID 参数无论怎么调都会很奇怪。环境准备的整体思路可以归结为一句话先保证“可观察、可控制、可限制”然后再开始写运动控制代码。可观察指有串口或显示屏能看到内部变量可控制指能通过指令让电机启动和停止可限制指供电和输出都有保护机制。4. 核心运动控制实现主循环、电机驱动与速度 PID这一节是整篇文章的重点。我们以 Arduino 风格代码为例讲解无视觉版车模最核心的运动控制实现。之所以用 Arduino 风格是因为它便于展示整体逻辑且在实际项目中经常作为快速原型工具。如果你使用的是 STM32 或其他 MCU逻辑完全一致只是底层寄存器或 HAL 库函数不同。4.1 主循环设计车模主循环不应该是一个大 while 套所有功能而应该是一个固定时间片调度器。常见的做法是10ms 执行一次舵机控制20ms 执行一次速度环50ms 上传一次串口数据200ms 检查一次电压。这样每个任务都有确定的时间资源不会互相干扰。// 文件SmartCar_NoVision.ino // 说明无视觉版本车模运动控制示例使用 Arduino 语法封装。 // 实际项目请根据主控型号、电机驱动板、编码器相数进行适配。 #include Encoder.h // 电机输出引脚 const uint8_t PIN_PWM 5; const uint8_t PIN_IN1 7; const uint8_t PIN_IN2 8; // 编码器引脚 const uint8_t PIN_ENC_A 2; const uint8_t PIN_ENC_B 3; // 控制周期 const unsigned long intervalMs 20; unsigned long lastControlMs 0; // 目标速度与当前速度编码器每秒脉冲数 float targetSpeed 0.0f; float currentSpeed 0.0f; // PID 参数与中间变量 float Kp 1.2f; float Ki 0.08f; float Kd 0.00f; float pidError 0.0f; float pidIntegral 0.0f; // 编码器对象 Encoder myEncoder(PIN_ENC_A, PIN_ENC_B); void setup() { pinMode(PIN_PWM, OUTPUT); pinMode(PIN_IN1, OUTPUT); pinMode(PIN_IN2, OUTPUT); Serial.begin(115200); Serial.println(SmartCar NoVision Start); motorStop(); targetSpeed 800.0f; // 请按实际赛道调整 } void loop() { // 串口指令解析例如输入 t500 表示设置目标速度为 500 if (Serial.available() 0) { String cmd Serial.readStringUntil(\n); cmd.trim(); if (cmd.startsWith(t)) { float value cmd.substring(1).toFloat(); targetSpeed value; pidIntegral 0.0f; Serial.print(Set target speed: ); Serial.println(targetSpeed); } } // 固定周期执行速度环 if (millis() - lastControlMs intervalMs) { runSpeedControl(); lastControlMs millis(); } } void runSpeedControl() { long count myEncoder.read(); myEncoder.write(0); // 将 20ms 内的脉冲数换算为每秒脉冲数 currentSpeed count * (1000.0f / intervalMs); float error targetSpeed - currentSpeed; pidIntegral error; pidIntegral constrain(pidIntegral, -1000.0f, 1000.0f); float output Kp * error Ki * pidIntegral Kd * (error - pidError); pidError error; output constrain(output, -255.0f, 255.0f); setMotorOutput((int)output); // 串口输出调试数据 Serial.print(target); Serial.print(targetSpeed); Serial.print( current); Serial.print(currentSpeed); Serial.print( output); Serial.println((int)output); } void setMotorOutput(int speed) { if (speed 0) { digitalWrite(PIN_IN1, HIGH); digitalWrite(PIN_IN2, LOW); analogWrite(PIN_PWM, speed); } else { digitalWrite(PIN_IN1, LOW); digitalWrite(PIN_IN2, HIGH); analogWrite(PIN_PWM, -speed); } } void motorStop() { digitalWrite(PIN_IN1, LOW); digitalWrite(PIN_IN2, LOW); analogWrite(PIN_PWM, 0); }这段代码的核心逻辑是三部分串口指令解析、固定周期速度环、电机方向与 PWM 输出。初学者最容易忽略的是intervalMs的确定性。代码里通过millis() - lastControlMs intervalMs实现非阻塞延时每次进入速度环后重置时间戳保证主循环不会被delay卡死。代码中使用了 Arduino Encoder 库它依赖外部中断读取编码器脉冲。如果你用的是 STM32通常会配置一个定时器为编码器模式让硬件直接完成正交解码这种方式更可靠不占用 CPU 中断。无论如何编码器读出的“脉冲数/控制周期”必须换算成统一单位否则 PID 参数会随着控制周期变化而失去意义。4.2 电机驱动PWM 与正反转setMotorOutput函数解决了电机正反转和调速问题。直流电机驱动板通常有 IN1、IN2、PWM 三个关键输入。IN1 和 IN2 决定方向PWM 决定占空比。同一时刻 IN1 和 IN2 不应该同时为高否则进入刹车状态。这里的实现是speed 为正时 IN1 高、IN2 低speed 为负时反过来speed 为 0 时两个引脚都拉低电机滑行。这段代码里真正容易踩坑的地方是 PWM 频率。不同驱动板对 PWM 频率要求不同有的支持几 kHz有的在 20kHz 左右更安静。如果 PWM 频率太低电机会有可听见的啸叫或抖动速度环会表现为往复振荡频率太高驱动板 MOS 管开关损耗增大发热严重。Arduino 默认的analogWrite频率约 490Hz对部分电机驱动板可用但对直流减速电机来说偏低。实际项目中建议查阅驱动芯片手册设置合适频率。4.3 编码器测速与速度换算编码器是整个反馈闭环的“眼睛”。光电编码器和霍尔编码器都是常用方案输出 A、B 两路相位相差 90 度的方波。通过判断 A、B 相先后顺序可以同时得到速度和方向。这里有一个关键点编码器的数据必须在每个控制周期内完整读取并清零计数避免累计误差。在示例代码中20ms 内读取一次计数并写回 0。那么currentSpeed count * (1000.0f / intervalMs)计算的就是每秒脉冲数。如果你的编码器是每圈多少脉冲电机减速比是多少还可以进一步换算成实际轮速m/s但在控制环内部统一为脉冲数并不影响 PID 工作。初学者经常遇到的编码器问题是丢脉冲线束太长、接触不良、未共地、或者引脚中断被其他高优先级任务阻塞。排查方法很简单用手慢慢转动电机轮观察串口打印的编码器值是否平滑递增或递减。如果数值跳变或方向时正时负先查接线和共地不要急着调 PID。4.4 PID 速度环调参逻辑示例中使用了位置式 PID 的离散形式。Kp * error是比例项负责快速消除当前偏差Ki * pidIntegral是积分项负责消除长期稳态误差Kd * (error - pidError)是微分项负责抑制超调。车模速度环通常可以先用 PD 控制只有当稳态误差明显时再引入积分。调参顺序建议固定为三步第一步先设 Ki 和 Kd 为 0只保留 Kp。把从 0 加到目标速度的响应打出来观察有没有稳态误差、超调量多大、响应时间多长。如果 Kp 太小车加速慢如果 Kp 太大车会明显抖动甚至发出嗡嗡声。第二步加入 Kd主要看超调是否减小。第三步加入 Ki消除稳态误差。整个过程每改一个参数只改一个变量记录曲线不要同时改两三个参数。这里真正容易踩坑的地方是积分饱和。示例中增加了pidIntegral constrain(pidIntegral, -1000.0f, 1000.0f)就是把积分项限制在一定范围内。如果没有限幅车被障碍卡住时误差持续累加积分项涨到极大值等障碍消失后车会猛冲出去。这也是很多车模“一解锁就飞出去”的原因之一。5. 转向控制舵机中位与无视觉循道基础速度闭环负责让车按目标速度行驶转向控制则决定车往哪里走。无视觉版虽然没有图像识别但转向控制必须有否则只能直走。在竞赛车模中常见的转向方案有两种舵机转向和差速转向。前者适合后驱或四驱模型车后者适合麦克纳姆轮或普通两轮差速底盘。舵机转向的核心是 PWM 脉宽控制。一个标准舵机通常接收周期约 20ms、脉宽在 1000us 到 2000us 之间的信号中间值 1500us 对应中位。车模跑不直的最常见原因不是算法而是舵机中位没有校准。如果硬件安装时舵机臂没有在中位对齐即使控制代码输出 1500us 的脉宽车轮仍然是偏的。#include Servo.h Servo steerServo; // 舵机中位对应的脉宽微秒 int midUs 1500; int steerAngle 0; // -45 到 45表示转向角度 void setup() { steerServo.attach(9); steerServo.writeMicroseconds(midUs); delay(500); } void loop() { // 简单映射转向角度增量对应脉宽增量 int pulse midUs steerAngle * 5; pulse constrain(pulse, 1000, 2000); steerServo.writeMicroseconds(pulse); delay(20); }这段代码展示了舵机中位设置与角度映射的思路。每个舵机的中位可能不同脉宽增量与实际角度的比例也可能不同因此实际项目中必须做一次标定把车抬起分别写入 1000us、1500us、2000us用角度尺或肉眼记录前轮角度再建立映射表。无视觉版如果要实现自动直线或简单循线还可以结合 IMU。陀螺仪测量车体的航向角速度积分后得到航向角通过一个 PD 控制器输出舵机修正量。这里要提醒的是直接积分会产生漂移且底盘振动会严重污染原始陀螺仪数据。不要指望一个未滤波的 MPU6050 原始数据能直接用于航向保持。更稳的做法是先用互补滤波或姿态解算库获得相对干净的姿态角再把姿态角输入转向环。6. 运行视频怎么录、怎么看、怎么验证回到项目标题里“运行视频”这个关键词。很多人录视频只是为了证明“我的车跑了”但这种素材对调试几乎没有帮助。真正有价值的运行视频应该像实验记录一样有参照、有指标、可以回放对比。特别是“未加入视觉”这个版本它存在的意义是作为后续版本对比的基线所以录制规范直接影响后续调试效率。首先机位设置。至少需要一个固定机位和一个跟随机位。固定机位放在赛道的起点或直线段旁边保证能看到车的完整运行路径跟随机位用于近距离捕捉轮胎、舵机、电机声音等细节。如果条件允许可以在车顶固定一个小摄像头记录实际第一视角画面这对标识视觉算法很有用但无视觉阶段不是必须。其次赛道参考线。地面上的赛道边缘、瓷砖缝、卷尺都可以作为参考线。视频里有了参考线就可以从画面中判断车辆是否跑偏。如果你只是在一个空旷地录车偏了十厘米也看不出问题这会让 PID 调参失去依据。更专业的做法是在赛道旁放几把直尺从不同角度拍出偏移量。第三叠加遥测数据。用 OBS 一类软件可以将串口打印的目标速度、当前速度、PID 输出等数据实时叠加到视频画面上。这样观众和调试者都能看到“车跑得快”和“PID 输出在震荡”之间的因果关系。比如画面里车在直线加速但串口数据里 current 已经超过 target那就说明 Kp 或 Kd 有问题目标加速变成了来回振荡。验证运动控制质量不能只看“能跑”。至少要检查几个指标直线段稳态误差是否小于 5%从 0 加速到目标速度的响应时间是否在 0.3 秒以内超调量是否小于 10%刹车时车头是否会明显偏转。这些指标在无视觉阶段都达标后再进入视觉阶段会更稳。为了更直观地观察 PID 响应可以在电脑上运行一个简单的 Python 脚本读取串口数据并绘制实时曲线。这样判断收敛过程比看数字方便得多。# 文件plot_speed.py # 用于读取车模串口数据并绘制目标速度与当前速度曲线 import serial import matplotlib.pyplot as plt ser serial.Serial(COM3, 115200, timeout1) fig, ax plt.subplots() times [] targets [] currents [] start_time 0 plt.ion() while True: line ser.readline().decode(utf-8, errorsignore).strip() if line.startswith(target): try: parts line.split() target float(parts[0].split()[1]) current float(parts[1].split()[1]) if start_time 0: start_time __import__(time).time() times.append(__import__(time).time() - start_time) targets.append(target) currents.append(current) ax.clear() ax.plot(times, targets, labeltarget) ax.plot(times, currents, labelcurrent) ax.set_xlabel(time (s)) ax.set_ylabel(speed (pulse/s)) ax.legend() plt.pause(0.01) except ValueError: pass # 运行后在图中观察目标速度与当前速度的跟随效果这个脚本的核心思路是解析串口输出中的targetxxx currentxxx outputxxx格式然后实时绘图。运行前需要修改COM3为实际串口号并保证车模已经通电且波特率一致。如果从曲线中看到当前速度围绕目标速度不断震荡说明 PID 参数过于激进如果很长时间才到达目标值说明 Kp 太小。7. 常见问题与排查思路无视觉版车模虽然代码量不大但在实际运行中的问题却不少。下面这张表总结了最常见的几类现象、原因和排查方向建议收藏到本地调试时逐项对照。问题现象可能原因排查方式解决方案电机完全不动驱动板未使能、供电不足、PWM引脚配置错误测量电机两端电压查看使能引脚电平拉高使能引脚检查电源线和共地电机抖动或啸叫PWM频率过低、PID参数过激、编码器信号异常用示波器查看PWM波形打印编码器原始值调整PWM频率降低Kp检查编码器接线只有一个轮子转H桥共地问题、另一侧PWM引脚损坏、接触不良手动设置该轮PWM固定值观察是否响应重新插拔线束确认共地更换引脚或驱动板起步猛冲上电瞬间PWM引脚为高、积分饱和、目标速度设置过大检查上电复位后的引脚状态查看PID输出初始化时先把PWM置0做积分限幅直线跑不直舵机中位不准、左右机械阻力不同、车轮安装偏悬空打正方向用手推车测试滑动阻力重新校准舵机中位调整机械结构编码器数值乱跳线束接触不良、未共地、使用了冲突的中断引脚手转轮子观察串口打印值变化检查接线确认共地更换中断引脚串口输出乱码波特率不匹配、地线没接、代码位数不一致用示波器观察TX引脚波形与波特率统一波特率确认串口共地PID输出饱和仍追不上目标编码器损坏、电池电压过低、目标速度超出能力打印编码器值与电池电压更换电池或减速比更大的电机运行一段时间后失控电池电压跌落、驱动过温、看门狗未喂记录日志观察失控前最后状态增加电压监测开启看门狗设置过温保护每一条问题背后都建议先怀疑机械和接线再怀疑代码。原因是代码问题重复出现率高容易被发现而机械和接线问题往往只在特定振动条件下出现非常隐蔽。比如“运行一段时间后失控”这个案例。很多初学者第一反应是 PID 参数不够好但最终发现是电池电压从满电 8.4V 掉到 6V 以下电机驱动已经进入欠压保护状态。此时不是算法问题是电源系统设计问题。增加电池电压监测一旦低于阈值就减速并声光报警可以有效避免这种情况。8. 工程建议与最佳实践无视觉版虽小但工程规范不能少。以下几条是长期调车经验的沉淀适用于竞赛车模、毕设机器人以及任何嵌入式运动控制项目。第一条是上电安全。车模上电瞬间非常容易跑飞。正确顺序是先开串口助手并确认能收到数据再打开车模电源代码初始化时所有 PWM 引脚要先置 0后使能驱动。很多驱动板在上电瞬间如果没有外部拉低MCU 复位期间的引脚是高阻态此时驱动板可能误判为高电平导致车轮猛转。一个硬件上的做法是给使能引脚加下拉电阻软件上则要确保初始化顺序正确。第二条是参数集中管理。Kp、Ki、Kd、目标速度、舵机中位这些参数不要散落在程序各个位置。应该在代码顶部集中定义或者放在独立的头文件里。参数改用宏或常量后调参时只改一处还能避免版本混乱。更规范的做法是把参数放在配置文件中通过串口指令修改重启后生效。这样你不用每次调参都重新编译烧录省下大量时间。第三条是日志先行。每次运行都应该有日志哪怕只是输出到串口也要让关键状态可追溯。日志至少包括时间戳、目标速度、当前速度、PID输出、舵机脉宽、电池电压。不要等到出了问题才想到加日志。如果程序崩溃或跑飞第一件事就是看日志中最后一个正常帧是什么问题范围一下子就能缩小。第四条是机械与电子分开排查。调 PID 之前先确认机械是“干净”的。手动推动车观察四个轮子是否都正常转动听听有没有异常摩擦声。把车悬空用手转动一侧车轮另一侧是否反向自由转动。机械卡涩会造成编码器读数正常但实际轮速偏低导致 PID 输出越来越大电机持续过流。这类问题很难通过调参解决。第五条是安全保护。在运动控制里必须有一个“失控保护”机制。常见做法是串口超时保护如果连续 500ms 没有收到新的遥控或串口指令主控自动执行刹车。加速度限制和输出限幅也属于保护机制。无视觉版看起来简单但如果缺少保护测试过程中撞到人或设备的风险会明显增加。第六条是录视频、传代码、存调参记录。具体来说每次调参后保留一份视频和对应的参数表文件名用“日期_赛道_目标速度_Kp_Ki_Kd”这种格式。后期加入视觉后如果发现视觉算法表现异常可以对比同赛道无视觉版本的运行效果判断是视觉处理问题还是底层控制退化问题。没有这种记录调试就会变成盲人摸象。9. 下一步加入视觉前必须准备好的四件事实现并验证完无视觉版本后项目就要走向视觉增强了。这里必须提醒不要直接拿起摄像头就接代码。从无视觉到视觉版之间还有四件准备工作必须完成否则视觉模块会成为下一个问题的根源。第一件事是运动学标定。至少要知道车模的最大速度、最小转弯半径、舵机打角与转弯半径的关系、加速和刹车的极限。这些数据可以通过无视觉版测得并将它们写成配置参数。视觉算法指导车辆行驶时需要这些上限值来规划速度曲线否则视觉系统给出一个超出物理能力的指令车仍然会冲出赛道。第二件事是接口预留。摄像头模块、图像处理板比如 OpenMV、树莓派或K210和主控之间建议在硬件上预留串口、I2C 或 SPI 接口并定义好通信协议。最简单的方式是视觉板只发送“目标角速度”或“目标方向”给主控主控保留原有的速度环和转向环代码。也就是说无视觉版本的底层运动控制代码应该是视觉版本的复用基础而不是被重写。第三件事是时间同步与帧率评估。如果视觉处理帧率只有 15fps而速度环是 50Hz那么每两到三帧车就走过一段不小的距离。必须评估视觉延迟对控制系统的影响并设计前馈补偿或预测算法。这一步没有做好视觉识别的结果即使完全正确控制效果也会很差。第四件事是自动回滚机制。加入视觉后一旦检测到视觉数据长时间不可用、置信度严重下降控制模式应自动切回无视觉的安全模式比如直接减速停车或回到遥控模式。这个机制会极大降低实车调试的风险。完成这四件事视觉才不是“挂在车上的摄像头”而是真正能辅助车辆决策的模块。这里的顺序和前面整个文章的逻辑是一致的先稳定运动再增强感知。控制是基础感知是增量。10. 最后的话“21届走马观碑车模运行视频未加入视觉”这个标题虽然简短但它其实揭示了智能车开发中最容易被忽视、也最值得记录的一个阶段。无视觉版本没有漂亮的识别画面没有复杂的算法但它把一个工程项目分成了可以单独验证、单独调试、单独回滚的层次。这种分层能力恰恰是很多之后做视觉项目的人最缺的。如果你正在做车模项目先把无视觉版本扎实跑通把 PID 调稳把串口日志打通把机械问题排查干净从视频里建立一条可重复的测试基线。然后再把摄像头接上去你会发现视觉调试的难度会低很多。任何高级功能都要建立在“车真的能稳下来”这个前提上。希望这篇文章对正在调车、准备加视觉的你有帮助。跑起来然后慢一点把每个环节记录清楚比跑得快更能决定项目的终点。