本期核心灰度图像怎么变成车能用的信息二值化、噪声滤波、左右边线提取、跳跃触发识别、视觉偏航修正——一帧 120×188 的图像如何驱动偏航控制。1. 先想清楚摄像头能给我们什么摄像头在车上的角色是**“眼睛”——提供 GPS 给不了的局部、高精度**信息GPS 精度是米级赛道线是厘米级——循线必须靠视觉跳跃台阶的位置也要靠视觉提前发现。但摄像头给的是一帧灰度图像不是线在哪。中间要经过一条完整的图像处理流水线原始灰度 → 二值化 → 噪声滤波 → 逐行边线扫描 → 中线提取 → 偏差→偏航修正这套流水线在main.c里由三个函数接力完成if(camera_driver_frame_ready()){constuint8_t*rawcamera_driver_get_image();image_binarize(image_bin,(constuint8_t(*)[188])raw,car.img_threshold);/* 二值化 */image_noise_filter(image_bin);/* 去噪 */track_detect_process(car,image_bin);/* 提取边线 */camera_driver_clear_flag();}三个函数一帧图各干一件事。每帧图像只在主循环处理不阻塞 1ms 控制中断——实时性由调度保证第1期讲过。2. 第一步二值化 —— 把灰度变成黑白原始图像每个像素是 0~255 的灰度。循线场景下我们只关心是白还是黑白 赛道黑 背景。于是设定一个阈值voidimage_binarize(uint8_tout[120][188],constuint8_tin[120][188],intthreshold){for(inti0;iCAMERA_H;i){for(intj0;jCAMERA_W;j){out[i][j](in[i][j]threshold)?IMG_WHITE:IMG_BLACK;}}}就这么简单 阈值 → 白否则 → 黑。全图遍历一遍O(H×W)。阈值本身就是个可调参数car.img_threshold存进 Flash、可以在菜单里调、开机从 Flash 读回第4期讲过。二值化的意义把 256 级灰度的浮云变成是/否的布尔判断后面所有算法瞬间变简单。3. 第二步噪声滤波 —— 3×3 邻域投票摄像头画质不完美图像里有椒盐噪声孤立的黑白点。用 3×3 邻域投票滤掉voidimage_noise_filter(uint8_timg[120][188]){for(inti1;iCAMERA_H-1;i){for(intj1;jCAMERA_W-1;j){/* 统计 8 邻域 自身的白色点数 */uint8_twc0;wc(img[i-1][j-1]IMG_WHITE);.../* 8 邻域 */wc(img[i][j]IMG_WHITE);if(img[i][j]IMG_WHITEwc2)img[i][j]IMG_BLACK;/* 孤立白点→黑 */elseif(img[i][j]IMG_BLACKwc6)img[i][j]IMG_WHITE;/* 孤立黑点→白 */}}}思路看邻居投票。一个白点周围全是黑的说明它是噪声翻成黑反过来黑点周围一片白翻成白。这是最朴素的滤波O(H×W×8)简单可靠。真正复杂的场景才需要中值/均值/形态学循线这种对比度高的图投票法够了。4. 第三步逐行边线扫描 —— 从图像里抠出赛道边界这是视觉模块的核心。思路是从下往上逐行扫描每行在左右方向上找黑白跳变点intstart_xCAMERA_W/2;for(intiCAMERA_H-1;i0;i--){/* 从图像底部往上扫 */intraw_left0,raw_rightCAMERA_W-1;if(img[i][start_x]IMG_BLACK)start_xCAMERA_W/2;for(intjstart_x;j0;j--)/* 向左找第一个黑点 */if(img[i][j]IMG_BLACK){raw_leftj;break;}for(intjstart_x;jCAMERA_W;j)/* 向右找第一个黑点 */if(img[i][j]IMG_BLACK){raw_rightj;break;}/* 宽度约束上一行知道赛道多宽这行偏差过大就矫正 */intpws-right_line[i1]-s-left_line[i1];if(rji5lji5)raw_rightraw_leftpw;/* 右边界缺失→按宽度补 */...s-left_line[i]raw_left;s-right_line[i]raw_right;s-center_line[i](raw_leftraw_right)/2;start_xs-center_line[i];/* 下一行从本行中线附近开始搜 */}三个关键工程细节从下往上图像底部离车最近、最可靠先扫出底部中线作为上面行的起始搜索点宽度约束如果这一行只找到一个边就用上一行的赛道宽度补出另一边——防止断线导致边线突变逐行推进每行用上一行的中线作为起点往左右找路径连续性让扫描又快又稳。这套从底部往上、宽度约束、连续推进的扫描策略是所有循线算法的基础套路。你的项目可以照抄这套骨架换成别的摄像头、别的赛道也一样。5. 第四步中线偏差 → 视觉偏航修正拿到每行的左右边线就能算中线。中线偏离图像中心多少就是车偏了多少/* 取有效区段剔除底部遮挡/顶部噪声 */for(intiCAMERA_H-1-IGNORE_BOTTOM_ROWS;i30;i--){if((right_line[i]-left_line[i])MIN_VALID_WIDTH){if(valid_bottom-1)valid_bottomi;valid_topi;}}if(valid_bottom!-1(valid_bottom-valid_top)10){/* 取约 40% 高度处的中线做误差 */intlarvalid_top(int)((valid_bottom-valid_top)*0.4f);floatavg5行中线平均;/* 抗噪 */floaterroravg-(CAMERA_W/2);/* 中线 vs 图像中心 *//* PD 修正量叠加到目标偏航 */s-vision_yaw_correction-(KP_YAW_VISION*errorKD_YAW_VISION*(error-last_err));last_errerror;}else{s-vision_yaw_correction0.0f;/* 看不到线修正归零 */}这里的精髓只用 40% 高度、5 行平均不用整幅图像取代表性的一行做误差——用局部信息代表全局省算力还抗噪PD 修正比例项快速跟线微分项抑制左右摆看不线就归零摄像头丢线时进入跳跃/出界修正量为 0不会把车带偏。6. 视觉和 GPS 怎么配合修正量叠加关键问题视觉循线和 GPS 路径跟踪会不会打架答案在balance.c的目标偏航合成里——视觉修正量是叠加上去的floatcur_yaws-yaws-gps_yaw_bias;floattargetnormalize_deg(s-target_yaws-vision_yaw_correction);floatyaw_errnormalize_deg(cur_yaw-target);PID_Calc(s-pid_yaw,yaw_err,0);最终目标偏航 GPS路径的目标偏航 视觉偏航修正量设计思路GPS 管宏观方向走哪条路视觉管微观纠偏贴哪条线。视觉修正量是一个小量叠加在 GPS 目标上做局部微调两者不冲突。这就是多传感器融合最简单的形态“主从叠加”。主源定方向从源做修正。不用卡尔曼也能让两个传感器协同工作。7. 视觉的另一个任务跳跃触发识别摄像头不只是循线还负责在跳跃任务里识别台阶/障碍触发跳跃第3期讲过跳跃状态机。voidtrack_detect_process(CarState_t*s,constuint8_timg[120][188]){if(s-task_flag1){if(s-jump_cnt0||s-jump_cnt2){/* 在图像中下方找白块台阶/路障底部 */for(intiCAMERA_H-5;i0;i--){...统计中线附近白色像素数...if(wcMIN_STEP_WIDTH){step_bottom_yi;break;}}}elseif(s-jump_cnt1){/* 找黑块跃过障碍后的路面 */...}/* 触发条件台阶高度到达阈值行或累计行驶距离到阈值 */if(s-jump_cnt0step_bottom_ys-jump_trigger_row_1)s-jump_flag1;elseif(s-jump_cnt1s-all_distances-jump_distance_1)s-jump_flag1;elseif(s-jump_cnt2s-all_distances-jump_distance_2)s-jump_flag1;}...}触发跳跃的组合判据视觉判据台阶的行位置越往下越近到达jump_trigger_row阈值里程判据累计行驶距离all_distance到达jump_distance阈值用于后续跳跃的等间距触发。视觉 里程双判据互为冗余、互相兜底——一个判据丢失另一个还能触发。这是传感器冗余的简单实践。8. 完整视觉流水线回顾原始灰度帧 │ 二值化阈值可调 ▼ 黑白图像 │ 3×3 投票去噪 ▼ 干净二值图 │ 从底部向上逐行扫描宽度约束 ▼ 左右边线 → 中线 │ ├──▶ 视觉偏航修正 ──叠加──▶ 目标偏航贴线 └──▶ 台阶检测 ──▶ jump_flag跳跃触发每一步都是图进图出/图进数出的纯函数可以单独调试、单独可视化。9. 本期小结二值化阈值切分灰度变黑白投票滤波3×3 邻域去椒盐噪声边线扫描从底部往上、宽度约束、连续推进中线偏差 → PD 修正40% 高度 5 行平均丢线归零视觉 GPS 叠加GPS 定方向视觉做微调视觉 里程双判据跳跃触发互为冗余。10. 下期预告算法闭环基本齐了最后一期讲人机交互与调参经验LoRa 遥控的摇杆映射、按键/菜单系统怎么设计以及整套系统在实车调试时踩过的坑和心得。欢迎关注持续连载中。