十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB车道线识别程序:从边缘检测到霍夫变换的完整实现

MATLAB车道线识别程序:从边缘检测到霍夫变换的完整实现 简介面向自动驾驶、智能交通及计算机视觉方向的学生和开发者该资源以MATLAB为平台整合了一套完整的车道线识别程序及多场景测试视频。压缩包包含5个文件一个lane_detect.m主程序脚本与四个avi格式道路测试视频总大小约184.63MB便于下载后直接运行并观察效果。当前已有770人学习浏览。程序覆盖了从视频帧读取、灰度化、直方图均衡化、高斯滤波到Canny边缘检测、霍夫变换直线检测的全流程并针对动态车道线引入滑动窗口和自适应模型策略在代码中可通过调整阈值、距离和角度参数优化检测结果。四个测试视频呈现不同路况有助于评估算法在光照、弯道和标记不清等条件下的鲁棒性是课程设计、毕业设计或自动驾驶算法入门的实用参考资料。1. 为什么车道线识别程序先用 MATLAB 把流程跑通很多做辅助驾驶的同学一上来就提深度学习 matlab 里的目标检测模型但在真实项目里车道线识别程序的第一版往往不是模型而是一段 MATLAB 图像处理脚本。原因很直接车道线是结构化特征边缘加直线检测在直道和标线清晰的场景里已经能稳定输出而且不需要训练样本和 GPU。配上一段车道线识别测试视频你就能在桌面上反复调参、看每一帧的中间结果比上车实测便宜得多。下面按实际程序写法把从边缘检测到霍夫变换、再到用测试视频验证的完整链路讲清楚。2. 车道线识别程序的图像处理链路从原图到直线方程2.1 灰度化、滤波和边缘检测先去掉路面纹理车道线识别第一步不是找线而是把输入帧变成只保留边界信息的二值图。摄像头拿到的彩色帧直接用会让后续计算量变大而且颜色受光照影响大所以先用rgb2gray转灰度图这是 MATLAB 图像处理里最常见的第一步。灰度化之后路面颗粒、油污和轮胎印都会变成高频噪声我用高斯滤波imgaussfilt先抹平一遍核大小通常取[5 5]Sigma取1.2~1.8。再往后就是 Canny 边缘检测edge函数里两个阈值[low high]的选择直接决定哪些边缘能留下来阈值设得太低路肩缝隙和阴影边缘全会被当成车道线设得太高虚线车道线会因为颜色太浅而断掉。我一般在直道测试视频上用[0.1 0.25]起步再根据结果微调。这里给出一段预处理核心代码它同时处理灰度化、滤波和边缘检测并把 ROI 之外的部分清掉。ROI 实际上是梯形区域对应路面靠近车头的那块视野。function edgeImg getLaneEdge(I, roiPts) % I: 单帧彩色或灰度图像 % roiPts: [4x2] 梯形四顶点顺序为左上、右上、右下、左下 if size(I, 3) 3 gray rgb2gray(I); else gray I; end blur imgaussfilt(gray, 1.5); edgeImg edge(blur, canny, [0.1 0.25]); [h, w] size(edgeImg); mask poly2mask(roiPts(:, 1), roiPts(:, 2), h, w); edgeImg edgeImg mask; end这段代码里poly2mask把梯形顶点变成和图像一样大的掩膜逐像素做“与”运算ROI 外的边缘点就直接归零。参数说明imgaussfilt的第二个参数是核大小第三个参数可以直接传标量当Sigmaedge的第三个参数是阈值向量如果要更少的边缘可以把高阈值改到0.3。这个函数只负责提取候选点真正判断哪条线是车道线在后面做。2.2 透视变换把前方路面投影成俯视图再识别在正视图里车道线在近处宽、远处窄且左右线向消失点汇聚直线检测在这种几何下虽然也能跑但聚类和曲率计算都不直观。常见做法是先做透视变换把前方路面区域映射成鸟瞰图让左右线变成近似的平行线。这里的数学关系是单应矩阵MATLAB 里用fitgeotrans根据至少四对点估算出来再用imwarp把原图投影过去。选点时我一般取原图中一个梯形区域底边稍窄于画面宽度顶边在消失点附近然后把它映射成俯视图里的矩形。变换之后的图像里车道线接近竖直方向角度特征更稳定。下面这段代码把上一节得到的边缘图变换成鸟瞰视角function bird toBirdView(edgeImg) [h, w] size(edgeImg); src [w*0.20 h; w*0.45 h*0.60; w*0.55 h*0.60; w*0.80 h]; dst [w*0.30 h; w*0.30 0; w*0.70 0; w*0.70 h]; tform fitgeotrans(src, dst, projective); bird imwarp(edgeImg, tform, OutputView, imref2d([h, w])); end这里的src是原图上的梯形顶点dst是目标俯视图里的矩形顶点。fitgeotrans的第三个参数projective表示允许任意四点单应变换比affine多两个自由度能纠正更多视角畸变。imwarp配合OutputView让输出尺寸和原图一致方便后续调试时逐像素对比。要注意的是src的四个点必须按对应顺序传前两个匹配后两个否则矩阵会产生很奇怪的扭曲。2.3 聚合短线基于角度把霍夫线分成左右两组经过预处理和透视变换车道线仍然可能是一些断续的边缘点而不是完整直线。这时用霍夫变换把它们聚合成候选线段。MATLAB 图像处理工具箱提供了hough、houghpeaks和houghlines三件套比直接调 OpenCV 的接口更透明中间能拿到累加器矩阵来调试。霍夫变换把每条边缘点映射到(rho, theta)参数空间rho是原点到直线的垂直距离theta是垂线与 x 轴的夹角。houghlines返回的每个结构体里有point1、point2和theta我们就可以按theta分左右。[H, T, R] hough(bird); peaks houghpeaks(H, 10, Threshold, ceil(0.3 * max(H(:)))); lines houghlines(bird, T, R, peaks, MinLength, 25, MaxGap, 15); if ~isempty(lines) leftLines lines([lines.theta] -70 [lines.theta] -20); rightLines lines([lines.theta] 20 [lines.theta] 70); else leftLines []; rightLines []; end在鸟瞰视角里左右线角度是稳定区间的左线大约在-70到-20度右线在20到70度。当然这个区间和摄像头安装角度有关后面要根据测试视频实际调整。houghpeaks的Threshold参数用max(H(:))的比例来控制代表只保留累加器里最强的那些峰值。过高的MinLength会把一些虚线段的短横线丢掉过低的MinLength又会把路沿石的碎边也当成候选这个参数是后面调优的关键。下表整理了这条链路上的核心函数和推荐参数后续排错时优先检查这些值是否和你的测试视频匹配。处理环节MATLAB 函数推荐参数说明灰度化rgb2gray-彩色帧必须先转单通道高斯滤波imgaussfilt核[5 5]Sigma1.5Sigma 越大越模糊线粗但边缘位移边缘检测edgeCanny[0.1 0.25]低阈值控制连续性ROI 掩膜poly2mask梯形顶点去掉天空和车头透视变换fitgeotrans/imwarpprojective需要至少 4 对点霍夫峰值houghpeaksThreshold 0.3 * max(H(:))太大会漏线太小会找很多杂线这一章是整套车道线识别程序的地基。预处理做得干净后面聚类和画线都会省很多精力反过来只要测试视频里出现一条你认为“眼睛看得见但程序找不到”的线问题大概率都出在 2.1 到 2.3 的某一个参数上而不是后面的逻辑。3. 实现可直接运行的车道线识别程序主循环3.1 最小框架VideoReader 帧处理 画线输出前一个章节的代码是函数片段真正要跑一段完整测试视频必须把它们组装进逐帧循环。我这里说的“程序”不是一个单函数而是包含读取、调用、显示三部分的最小可运行脚本。VideoReader用来读取车道线识别测试视频hasFrame和readFrame负责逐帧取出画面每一帧传入processFrame它返回的结果是已经叠加车道线的彩色图像最后用imshow或者vision.VideoPlayer展示。下面是完整主循环你可以先跑通再替换成自己的处理函数。这里我刻意保留了分层先调出直线再把直线拟合成左右车道线便于中间打断点看数据。% lane_main.m v VideoReader(lane_test.mp4); if ~hasFrame(v) error(测试视频为空检查文件路径); end player vision.VideoPlayer(Position, [100 100 960 540]); while hasFrame(v) frame readFrame(v); % 读取一帧 result processFrame(frame); % 检测并画线 player(result); % 显示在播放器窗口 endprocessFrame内部做的事是调用上一章的getLaneEdge拿边缘图、imwarp拿鸟瞰边缘然后在鸟瞰图上做霍夫检测再把得到的线段端点通过逆透视变换映射回原图最后用insertShape在原图上画出黄色标记。逆透视变换这一步很关键所有检测都是在俯视图坐标系完成的但视觉上必须回到原图坐标所以要先invert(tform)再做transformPointsInverse。如果漏掉这一步画出来的线会整体偏移一个角度很多新手把画线结果和路面标线对不上就卡在这里。processFrame的核心部分可以这样写function result processFrame(frame, roiPts, tform) edgeImg getLaneEdge(frame, roiPts); bird imwarp(edgeImg, tform, OutputView, imref2d(size(edgeImg))); [H, T, R] hough(bird); peaks houghpeaks(H, 6, Threshold, ceil(0.3 * max(H(:)))); lines houghlines(bird, T, R, peaks, MinLength, 25, MaxGap, 15); result frame; if isempty(lines) return; end invT invert(tform); for k 1:length(lines) p1 transformPointsInverse(invT, lines(k).point1); p2 transformPointsInverse(invT, lines(k).point2); result insertShape(result, Line, [p1 p2], ... Color, yellow, LineWidth, 4); end end这里的peaks数量从第 2 章的 10 个降到了 6 个因为视频中同一侧车道线可能被霍夫变换拆成多段峰值太多会让最终画面上叠加出好几条重影线。insertShape是 MATLAB 画图系里最方便的函数向量化的输入比用plot在循环里逐条画要快得多。另外要注意VideoReader在循环里逐帧读取时不要手动修改CurrentTime否则帧序会乱。3.2 车道线识别程序的八个核心参数与推荐值能跑起来不等于能稳定检测。为了让你在拿到新测试视频时有一个参照起点我整理了一份参数表。这份表不针对某个特殊视频而是基于常见的车载摄像头安装高度和中低分辨率视频。若你的测试视频是手机拍摄的视角更广摄像头畸变更大参数需要按下面的排查思路重调。参数位置推荐值调参方向Canny 低阈值getLaneEdge0.1虚线断开时降低Canny 高阈值getLaneEdge0.25阴影误检多时升高高斯 SigmagetLaneEdge1.5噪点多时升到 2.0ROI 梯形顶边getLaneEdge画面宽度 40%~55%摄像头俯角大时扩大ROI 梯形底边getLaneEdge画面宽度 10%~90%近处线形明显时可缩小霍夫最小长度houghlines25虚线识别不到时降低到 15霍夫最大间隙houghlines15虚线断裂严重时提高到 30左右角度区间聚类处-70~-20 / 20~70透视标定误差大时放宽到 ±15 度这张表没有列透视变换的目标点因为每段测试视频的拍摄角度都不一致必须动态标定。我的处理办法是把fitgeotrans的选点逻辑抽出来单独做成一个calib_lane.m脚本在视频第一帧用ginput选四个路面角点然后把坐标存到calib.mat后面的帧全部复用同一组坐标。这样每次换测试视频只需要调整一次标定点。3.3 用车道线识别测试视频验证程序是否正常测试视频是整套程序的验收基准。我不建议拿一段很顺滑的晴天直道视频从头跑到尾就宣布完成那样只会让你以为程序已经很稳定。一段合格的车道线识别测试视频至少应该包含直线虚线、直线实线、右转弯、左转弯、一段阴影和一处路桥接缝。把这些场景剪辑成 30 到 60 秒的短视频比从完整行车记录仪里抽帧方便得多。验证时不要只靠眼睛看播放器。用下面的脚本把每一帧的检测结果和原始帧抽出来存成对比图至少每个场景抽 10 帧人工数一下误检的数量。% extract_frames.m v VideoReader(lane_test.mp4); for i [1, 100, 250, 500, 800] % 手工挑可能的场景帧 frame readFrame(v, [i i]); % 只读指定帧 result processFrame(frame, roiPts, tform); imwrite([frame result], sprintf(check_%04d.png, i)); end这里的readFrame(v, [i i])是VideoReader的按区间读取方式索引区间两端相同时表示只读一帧。把原图和结果图横向拼起来存成 PNG放大看边缘位置是否贴住标线。如果发现某一帧里左右线频繁交叉或跳变不要急着继续调参先看是聚类角度区间写死了还是霍夫参数在弯道处失效。程序正常的表现是同一段直道上连续 100 帧画出的线在图像上的左右摆动不超过 10 个像素而不是每一帧都能完美贴合。提示readFrame在指定索引时会从文件头开始扫描所以不要在大循环里频繁调用否则读取速度会变慢。验证阶段适合只抽少量帧完整测试还是用while hasFrame的顺序读取。4. 车道线识别程序的典型坑光照、弯道与坏帧4.1 阴影和积水造成的假边缘怎么靠形态学开运算消掉测试视频一换到有树影或路面积水的路段Canny 边缘图里就会多出一大片不规则的边界。这些边界来自灰度的突然变化和车道线的几何特征很像尤其是阴影边缘是长条状时霍夫变换会把它们错认成直线。处理这类假边缘我一般不会先动 Canny 阈值而是对边缘图做形态学开运算先腐蚀再膨胀把细碎的短边缘和由噪声引起的孤立点消掉保留车道线这种有方向连续性的长边缘。MATLAB 里用imopen配合strel定义结构元素。se strel(line, 7, 0); % 0 度方向的线形结构元素 edgeClean imopen(edgeImg, se);代码里strel(line, 7, 0)创建一条长度为 7、角度为 0 的线性结构元素对边缘图做开运算。因为车道线在预处理后多是接近水平方向的短线在正视图里用水平结构元素可以优先保留水平走向的连续边缘。对于斜向的阴影可以再补一次 45 度方向的开运算然后合并两次结果。注意开运算不能消除大面积阴影的粗边缘那种情况必须用透视变换后的鸟瞰视角判断因为阴影边缘在鸟瞰图里不会和车道线保持平行。4.2 弯道场景的近似处理分段直线与透视区域收缩弯道是测试视频里最常见的失败来源。弯道半径小的时候车道线在图像里是一条明显的弧线而霍夫变换只能输出直线。勉强连成一条直线的话画出来的车道线会直接切进路肩。常见做法不是增加一个多项式拟合模型而是把 ROI 在纵向分成近、中、远三段每段独立做霍夫检测再用相邻段的交界点连接回归线。三段直线可以近似出大半径弯道小半径回头弯就别指望传统方式了直接切到模型方案更省事。实现分段时我会把透视图沿 y 轴切成三段每段的高度约为整体高度的三分之一每一段分别调用houghlines然后用最小二乘把三段端点连成折线。要注意每段之间的平行约束左右两线的斜率之差不能超过一个阈值否则说明某一侧检测到了路沿或者护栏。可以用下面这段逻辑过滤slopeL (left_y(end) - left_y(1)) / (left_x(end) - left_x(1)); slopeR (right_y(end) - right_y(1)) / (right_x(end) - right_x(1)); if abs(abs(slopeL) - abs(slopeR)) 0.4 % 两侧斜率差异过大丢弃该段用上一帧数据补 return; end这段代码并不是赛道级的曲率计算它只是防止某一侧检测结果突然跳到错误特征上。弯道里更实用的做法是给每一帧检测结果做时间上的平滑把上一帧的左右线端点作为当前帧的参考当前帧检测到的候选线若离参考线太远就判为误检。在实时系统里这种时域约束比空间上的直线拟合更能抵抗弯道中的抖动。下表是这套程序在测试视频里最常见的失效模式基本对应 4.1 和 4.2 的解决思路。失效现象可能原因对应处理检测线偏到路肩ROI 底边太宽缩小梯形底边虚线完全检测不到Canny 低阈值过高或MinLength过大调低到[0.05 0.15]MinLength降到 10左右线频繁交叉透视变换点没标好重新标定src和dst阴影处出现长线形态学开运算方向不对增加与阴影方向对应的结构元素4.3 用中间结果导出和帧号定位测试视频中的问题调参过程中最怕的就是“视频播到某一段突然画错了但回放时又找不到是哪一帧”。我处理这类问题的方法是给主循环加一个调试开关把每一帧的边缘图、鸟瞰图、霍夫候选线以及最终画线结果全部导出到当前目录下的debug文件夹文件名带帧号。出现问题时可以直接按帧号查图不用反复拖进度条。% debug_export.m if debugMode imwrite(edgeImg, sprintf(debug/edge_%06d.png, frameIdx)); imwrite(bird, sprintf(debug/bird_%06d.png, frameIdx)); imwrite(result, sprintf(debug/result_%06d.png, frameIdx)); endframeIdx是在主循环里用v.CurrentTime * v.FrameRate 1换算出来的帧号注意VideoReader的CurrentTime是以秒为单位的浮点数直接取整容易错一帧。这个调试开关不用的时候一定要关掉因为连续写 PNG 文件非常占磁盘一分钟 30 帧的视频 5 分钟就能写出一千多个文件。按文件大小清理也很简单只保留出问题前后各 30 帧然后对比边缘图和鸟瞰图就能看出是哪一步把线弄丢的。5. 从“能跑到”到“敢交给别人跑”的验证技巧这一章要讲三个具体技巧都围绕测试视频和程序回归。不是让你去写测试用例文档而是把验证动作固化到脚本里。第一个技巧是把整段测试视频的结果存成MAT文件作为回归基线。第一次调好参数后跑一遍视频把每一帧检测到的左右线端点存起来用save(baseline.mat, L, R)。以后改动任何参数再跑同一段测试视频和基线对比统计端点偏移超过阈值的帧数。这个方法能自动发现“改了一个参数导致弯道失效”的回归问题比肉眼盯屏幕可靠。第二个技巧是同时显示原图、边缘图和鸟瞰图三个画面。在 MATLAB 里可以用tiledlayout(1, 3)分别imshow原图、getLaneEdge输出的边缘图、toBirdView输出的鸟瞰图。这样调参时一眼就能看到哪一步丢了信息。我见过很多人只盯着最终画线图发现线不够长就往霍夫参数上改其实问题早在边缘图阶段就已经发生了。第三个技巧是给processFrame加一个断言左右线端点在经过逆透视映射回原图后必须始终位于 ROI 内部如果某帧检测出的点跑到roiPts之外说明这一帧取了垃圾特征程序应该输出一个警告而不要静默跳过。用 MATLAB 的assert会中断循环不方便长跑所以我更推荐用warning。if any(leftPt(:, 2) 0) || any(rightPt(:, 2) 0) warning(frame %d: 车道线跳出ROI判断为误检, frameIdx); continue; end这个continue会把当前帧的检测结果直接废掉用上一帧的车道线位置补上。在测试视频上连续跑完整个片段如果warning出现的比例超过 2%就说明参数还是偏激进需要把角度区间收窄或者把霍夫最小长度调大。这样你拿任何新视频给同事跑都能用同一套脚本快速给出“能不能用”的结论。本文还有配套的精品资源点击获取
返回列表