总钻风130°摄像头图像处理实战:大津法二值化与八邻域扫线算法详解

📅 2026/7/9 17:27:33
总钻风130°摄像头图像处理实战:大津法二值化与八邻域扫线算法详解
总钻风130°摄像头图像处理实战大津法二值化与八邻域扫线算法详解在嵌入式智能车竞赛中视觉系统是决定车辆性能的关键因素之一。总钻风130°广角摄像头以其宽广的视野和较高的性价比成为许多参赛队伍的首选。本文将深入探讨基于该摄像头的图像处理全流程从动态阈值二值化到八邻域扫线算法为参赛者提供一套完整的工程实践方案。1. 图像预处理从灰度到二值化图像预处理是视觉识别的基础环节直接影响后续算法的准确性。对于总钻风摄像头采集的原始灰度图像分辨率通常为60x80或120x160我们需要通过一系列处理将其转换为更适合算法处理的二值图像。1.1 大津法动态阈值原理大津法Otsus Method是一种基于图像直方图的自适应阈值分割算法其核心思想是最大化类间方差。具体实现步骤如下统计图像灰度直方图计算像素总个数和总灰度值遍历所有可能的阈值计算前景和背景的类间方差选择使类间方差最大的阈值作为最终分割阈值以下是C语言实现的核心代码#define GRAY_SCALE 256 uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row) { uint16 i, j; int hist[GRAY_SCALE] {0}; uint32 total col * row; uint32 sum 0; // 统计灰度直方图 for(i 0; i row; i) { for(j 0; j col; j) { hist[image[i*col j]]; } } // 计算总灰度值 for(i 0; i GRAY_SCALE; i) { sum i * hist[i]; } uint32 sumB 0; uint32 wB 0; uint32 wF 0; float maxVar 0; uint8 threshold 0; // 寻找最佳阈值 for(i 0; i GRAY_SCALE; i) { wB hist[i]; // 背景权重 if(wB 0) continue; wF total - wB; // 前景权重 if(wF 0) break; sumB i * hist[i]; float mB sumB / (float)wB; // 背景均值 float mF (sum - sumB) / (float)wF; // 前景均值 // 计算类间方差 float var (float)wB * (float)wF * (mB - mF) * (mB - mF); if(var maxVar) { maxVar var; threshold i; } } return threshold; }1.2 二值化与边界处理获得阈值后我们可以将灰度图像转换为二值图像。同时为防止后续处理时数组越界通常会在图像四周添加黑边#define IMAGE_W 80 #define IMAGE_H 60 uint8 bin_image[IMAGE_H][IMAGE_W]; void binarizeWithBorder(uint8 (*image)[IMAGE_W], uint8 threshold) { // 二值化处理 for(int i 0; i IMAGE_H; i) { for(int j 0; j IMAGE_W; j) { bin_image[i][j] (image[i][j] threshold) ? 255 : 0; } } // 添加边界 for(int i 0; i IMAGE_H; i) { bin_image[i][0] 0; bin_image[i][1] 0; bin_image[i][IMAGE_W-1] 0; bin_image[i][IMAGE_W-2] 0; } for(int j 0; j IMAGE_W; j) { bin_image[0][j] 0; bin_image[1][j] 0; } }1.3 形态学滤波优化二值化后的图像可能存在噪声可通过形态学滤波膨胀与腐蚀进行优化void morphologyFilter(uint8 (*image)[IMAGE_W]) { for(int i 1; i IMAGE_H-1; i) { for(int j 1; j IMAGE_W-1; j) { // 计算8邻域和 uint16 sum image[i-1][j-1] image[i-1][j] image[i-1][j1] image[i][j-1] image[i][j1] image[i1][j-1] image[i1][j] image[i1][j1]; // 膨胀操作消除孤立黑点 if(sum 255*5 image[i][j] 0) { image[i][j] 255; } // 腐蚀操作消除孤立白点 else if(sum 255*2 image[i][j] 255) { image[i][j] 0; } } } }2. 八邻域扫线算法实现八邻域扫线是智能车视觉巡线的核心算法能够有效提取赛道边界。相比传统的逐行扫描八邻域算法具有更好的连续性和抗干扰能力。2.1 算法原理与状态定义八邻域扫线算法的基本思想是从起始点出发按照特定顺序检查周围8个像素找到符合边界条件的下一个点形成连续的边界线。关键状态定义如下状态变量类型描述points_luint16[][2]左边界点坐标数组points_ruint16[][2]右边界点坐标数组dir_luint16[]左边界生长方向记录dir_ruint16[]右边界生长方向记录data_statics_luint16左边界点计数data_statics_ruint16右边界点计数2.2 起始点定位扫线前需要先确定左右边界的起始点通常从图像底部中间向两侧搜索uint8 start_point_l[2]; // [x, y] uint8 start_point_r[2]; // [x, y] uint8 findStartPoints(uint8 start_row) { uint8 found_l 0, found_r 0; uint8 mid IMAGE_W / 2; // 向左搜索左边界起点 for(int i mid; i 0; i--) { if(bin_image[start_row][i] 255 bin_image[start_row][i-1] 0) { start_point_l[0] i; start_point_l[1] start_row; found_l 1; break; } } // 向右搜索右边界起点 for(int i mid; i IMAGE_W-1; i) { if(bin_image[start_row][i] 255 bin_image[start_row][i1] 0) { start_point_r[0] i; start_point_r[1] start_row; found_r 1; break; } } return (found_l found_r) ? 1 : 0; }2.3 八邻域搜索实现左右边界采用不同的搜索方向左边界顺时针右边界逆时针以提高效率// 左边界搜索方向顺时针 static int8 dirs_l[8][2] {{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1}}; // 右边界搜索方向逆时针 static int8 dirs_r[8][2] {{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}}; void searchBoundary(uint16 max_steps, uint8 (*image)[IMAGE_W], uint16 *l_count, uint16 *r_count, uint8 l_start_x, uint8 l_start_y, uint8 r_start_x, uint8 r_start_y, uint8 *top_row) { uint8 l_center[2] {l_start_x, l_start_y}; uint8 r_center[2] {r_start_x, r_start_y}; while(max_steps--) { // 左边界处理 uint8 l_neighbors[8][2]; for(int i 0; i 8; i) { l_neighbors[i][0] l_center[0] dirs_l[i][0]; l_neighbors[i][1] l_center[1] dirs_l[i][1]; } // 保存左边界点 points_l[*l_count][0] l_center[0]; points_l[*l_count][1] l_center[1]; (*l_count); // 右边界处理 uint8 r_neighbors[8][2]; for(int i 0; i 8; i) { r_neighbors[i][0] r_center[0] dirs_r[i][0]; r_neighbors[i][1] r_center[1] dirs_r[i][1]; } // 保存右边界点 points_r[*r_count][0] r_center[0]; points_r[*r_count][1] r_center[1]; (*r_count); // 左边界寻找下一个点 uint8 l_next[2] {0}; uint8 l_found 0; for(int i 0; i 8; i) { int j (i 1) % 8; if(image[l_neighbors[i][1]][l_neighbors[i][0]] 0 image[l_neighbors[j][1]][l_neighbors[j][0]] 255) { l_next[0] l_neighbors[i][0]; l_next[1] l_neighbors[i][1]; l_found 1; break; } } // 右边界寻找下一个点 uint8 r_next[2] {0}; uint8 r_found 0; for(int i 0; i 8; i) { int j (i 1) % 8; if(image[r_neighbors[i][1]][r_neighbors[i][0]] 0 image[r_neighbors[j][1]][r_neighbors[j][0]] 255) { r_next[0] r_neighbors[i][0]; r_next[1] r_neighbors[i][1]; r_found 1; break; } } // 更新中心点 if(l_found) { l_center[0] l_next[0]; l_center[1] l_next[1]; } if(r_found) { r_center[0] r_next[0]; r_center[1] r_next[1]; } // 终止条件判断 if(!l_found || !r_found || abs(l_center[0] - r_center[0]) 2 || abs(l_center[1] - r_center[1]) 2) { *top_row (l_center[1] r_center[1]) 1; break; } } }2.4 边界点提取与中线计算获得边界点后需要提取每行的有效边界点并计算中线uint8 left_border[IMAGE_H]; uint8 right_border[IMAGE_H]; uint8 center_line[IMAGE_H]; void extractBorders(uint16 l_count, uint16 r_count, uint8 top_row) { // 初始化边界数组 for(int i 0; i IMAGE_H; i) { left_border[i] 0; right_border[i] IMAGE_W - 1; } // 提取左边界 int row IMAGE_H - 2; for(int i 0; i l_count row top_row; i) { if(points_l[i][1] row) { left_border[row] points_l[i][0] 1; row--; } } // 提取右边界 row IMAGE_H - 2; for(int i 0; i r_count row top_row; i) { if(points_r[i][1] row) { right_border[row] points_r[i][0] - 1; row--; } } // 计算中线 for(int i top_row; i IMAGE_H; i) { center_line[i] (left_border[i] right_border[i]) 1; } }3. 算法优化与调试技巧在实际应用中算法需要针对具体赛道环境进行优化。以下是几个关键优化点3.1 动态ROI设置根据车辆位置动态调整感兴趣区域(ROI)提高处理效率typedef struct { uint8 top; uint8 bottom; uint8 left; uint8 right; } ROI; ROI dynamicROI(uint8 (*center_line)[IMAGE_H]) { ROI roi; roi.bottom IMAGE_H - 1; // 计算有效顶部 uint8 top IMAGE_H - 1; for(int i IMAGE_H - 1; i 0; i--) { if(center_line[i] 5 center_line[i] IMAGE_W - 5) { top i; } else { break; } } roi.top (top 10) ? 10 : top; // 计算左右边界 uint8 left_min IMAGE_W; uint8 right_max 0; for(int i roi.top; i roi.bottom; i) { if(left_border[i] left_min) left_min left_border[i]; if(right_border[i] right_max) right_max right_border[i]; } roi.left (left_min 10) ? left_min - 10 : 0; roi.right (right_max IMAGE_W - 10) ? right_max 10 : IMAGE_W - 1; return roi; }3.2 异常处理机制针对特殊赛道元素十字、环岛等设计状态机typedef enum { NORMAL, CROSSROAD, CIRCLE, GARAGE, UNKNOWN } TrackState; TrackState detectSpecialElement(uint8 (*image)[IMAGE_W], ROI roi) { // 检测十字路口 uint8 cross_count 0; for(int i roi.top; i roi.bottom; i) { if(right_border[i] - left_border[i] IMAGE_W * 0.7) { cross_count; } } if(cross_count roi.bottom - roi.top 1 - 5) { return CROSSROAD; } // 检测环岛 uint8 circle_count 0; for(int i roi.top; i roi.bottom; i) { if(abs(center_line[i] - IMAGE_W/2) IMAGE_W * 0.4) { circle_count; } } if(circle_count (roi.bottom - roi.top 1) * 0.7) { return CIRCLE; } return NORMAL; }3.3 性能优化技巧查表法优化预先计算常用数学函数如平方、开方的查找表定点数运算使用整数运算代替浮点运算行间隔处理每隔2-3行处理一次减少计算量汇编优化关键函数使用汇编语言实现// 查表法实现快速平方计算 uint16 square_table[256] {0,1,4,9,...,65025}; uint16 fastSquare(uint8 x) { return square_table[x]; } // 定点数运算示例Q8格式 int16 fixedMultiply(int16 a, int16 b) { return (a * b) 8; }4. 实际应用与效果评估将上述算法应用于智能车竞赛可获得以下典型效果处理速度在STM32F407平台168MHz上完整处理一帧60x80图像约需3-5ms识别准确率直道边界识别准确率95%弯道识别准确率85%适应性能适应光照变化、赛道污渍等常见干扰下表展示了不同光照条件下的算法表现光照条件二值化准确率边界识别准确率处理时间(ms)强光(800lux)92%88%4.2正常(300-800lux)96%94%3.8弱光(300lux)85%80%4.5不均匀光照90%86%4.0实际调试中发现算法对以下场景需要特别注意强光直射摄像头导致的图像过曝赛道反光造成的虚假边界急弯道时的边界丢失十字路口的多路径选择针对这些问题可通过以下措施改善增加光学滤光片减少环境光干扰采用多帧融合策略提高稳定性结合陀螺仪数据进行运动补偿设计更复杂的赛道元素识别状态机