拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于MATLAB的车流量检测:背景差分与虚拟检测线实现

简介Matlab车流量检测项目面向车辆检测初学者覆盖车流量检测常见流程适合新手及有一定经验的开发人员研究学习。压缩包共44个文件以m源码、png效果图和asv自动保存文件为主辅以bmp/jpg图像、avi视频及docx文档整体大小约18.72MB既可作为算法学习样例也能直接运行验证。目前已有573人学习下载具有一定参考价值。内容中提供多种检测思路包括背景差、前后帧差、增强对比度二值化、分割与跟踪脚本并配有原始图、中间结果和最终效果图方便对照观察每一步处理效果压缩包内还自带了测试图像序列和演示视频可直接运行验证。附带的相关说明文档和辅助文件可帮助理清算法原理调试与二次开发也较为便利适合作为课程设计或算法比对的基准工程。1. 车流量检测从哪入手背景差分、虚拟检测线与 MATLAB交通监控视频有一个天然优势机位固定、背景稳定、场景里只有车辆在动。车流量检测要解决的问题从来不是“认出这是哪款车”而是“把路上跑的目标数清楚”。用 MATLAB 做这件事走的是一条比深度学习目标检测轻得多的路线——先用背景建模把前景车辆分离出来再在画面上放一条虚拟检测线车辆压线就计数。整套流程不需要 GPU不依赖标注数据一台普通笔记本在 MATLAB 环境里就能跑出带检测框和计数的效果图。这类项目在课程设计和初期原型中非常常见之所以附上测试数据和效果图是为了让你先看到原始帧、中间掩码和输出计数的因果关系再拿着自己拍的视频换参数。如果你准备完成一份 MATLAB 图像处理大作业或者验证固定机位场景下的车流统计可行性这个方案是最容易跑通的起点。2. 车流量检测的核心原理背景建模与前景分割2.1 固定机位场景为什么优先选背景差分把车辆从视频帧中分离出来常见做法有帧间差分、背景差分和深度学习目标检测。深度学习方案在准确率上有优势但要准备标注样本、对齐模型版本对快速验证来说成本偏高。固定机位场景下背景差分是最划算的选择相机不动背景像素在时间轴上呈稳定分布车辆像素则是短暂且大面积的偏离。只要把这个“偏离”量化出来就得到了前景掩码。三种常用前景提取方法的对比如下方法是否需要训练对光照变化慢速车辆实现成本帧间差分否较弱容易漏检最低均值背景 阈值需要前若干帧弱可能被吸入背景低高斯混合模型 GMM需要前 20~50 帧较强适应较好中工具箱自带帧间差分的实现最简单但车辆在连续两帧间重叠区域较大输出掩码中间往往是空心的到计数阶段框容易断裂。均值背景的问题在背景更新方式粗糙车辆慢速通过时会被逐渐算进背景。GMM 是折中方案也是 MATLAB Computer Vision Toolbox 中 vision.ForegroundDetector 采用的做法。2.2 高斯混合模型与 vision.ForegroundDetector 的关系GMM 不负责跟踪它只回答“这个像素当前是否是前景”。算法为每个像素维护多个高斯分布每个分布表示该位置出现过的一种灰度状态例如路面、树影、光照渐变。新帧像素到达后先与已有分布匹配匹配则更新均值和方差不匹配则把权重最低的分布替换为新的单峰分布。最终判定前景取决于像素匹配的那个分布是否“足够不像背景”。MATLAB 的 vision.ForegroundDetector 把这一过程封装成了可重复调用的系统对象。构造时常用四个参数NumGaussians 控制每个像素维护多少个分布数值越大越能容忍树叶晃动这类动态纹理但内存占用会上升。NumTrainingFrames 是背景建模需要的初始帧数前 N 帧不会输出有效前景。MinimumBackgroundRatio 决定一个分布要占多大混合权重才被当作背景通常保持默认即可。LearningRate 控制背景更新速度这是后续调参中最常改动的一项。提示使用 vision.ForegroundDetector 需要安装 Computer Vision Toolbox安装 MATLAB 时勾选该工具箱即可。若没有这个工具箱可以先改用 2.3 节的帧间差分方案跑通流程但计数精度会明显下降。2.3 前景掩码为什么要做形态学后处理GMM 输出的前景掩码是逻辑矩阵直接使用会有两个问题噪点散落整个画面同一辆车的车体在掩码中存在空洞和锯齿。形态学后处理的目的是让“候选前景”变成“完整的车块”。常见顺序是先做开运算去除孤立噪点再做闭运算填充车体内部空洞最后用 bwareaopen 删除像素数少于阈值的碎片。如果只想验证整体流程也可以先用帧间差分搭一个基线。下面的代码按帧读取视频通过灰度差分离前景bg im2gray(read(vid, 1)); while hasFrame(vid) frame readFrame(vid); gray im2gray(frame); fg abs(double(gray) - double(bg)) 35; % 灰度差阈值 bg uint8(0.95 * double(bg) 0.05 * double(gray)); % 背景慢速更新 % fg 继续走 imclose、bwareaopen、regionprops 流程 end这段代码中阈值 35 决定灵敏度值调小噪点变多调大慢速车辆容易漏检。0.95 的保留系数决定了背景吸收当前帧信息的速度数值过大会把静止在路口的车快速融入背景。这个基线只能用于验证链路完整度真正做车流量统计还是建议回到 GMM 方案它在阴影鲁棒性和慢车处理上明显更好。3. 用 MATLAB 实现车辆检测主流程视频读取、背景建模与框选3.1 视频文件与帧序列两种数据入口测试数据通常以两种形式组织。第一种是单一视频文件用 VideoReader 读取第二种是按序号命名的图片帧例如 frame001.jpg 到 frame300.jpg可用 imageDatastore 管理。视频文件读取的推荐写法是配合 hasFrame 循环避免手动数帧号vid VideoReader(data/traffic.mp4); while hasFrame(vid) frame readFrame(vid); % 帧处理逻辑 endhasFrame 在还有下一帧时返回 true读完时返回 false天然适配长度不确定的视频。MP4 的 H.264 编码依赖系统解码器如果 readFrame 在 Linux 环境下报错优先把测试视频转成 Motion JPEG 编码的 AVI 再读不要为了解码器去改动项目代码。帧序列的读取方式略有不同。imageDatastore 默认按文件名字符串排序若文件名是 1.jpg、10.jpg、2.jpg会按字典序错乱。稳妥做法是自行构造文件列表并按数字升序排序或者在生成数据时就用 sprintf(%03d, n) 统一命名位宽。两种入口进入检测循环后后续代码完全一致。3.2 建立背景模型vision.ForegroundDetector 参数设置进入检测循环前先构造前景检测器。以 640×480 的测试视频为例初始化写法如下foregroundDetector vision.ForegroundDetector(... NumGaussians, 3, ... NumTrainingFrames, 20, ... MinimumBackgroundRatio, 0.7, ... LearningRate, 0.005);NumTrainingFrames 取 20表示前 20 帧只用于学习背景。如果测试视频本身很短例如只有 80 帧这个值要降到 10否则视频快结束了背景建模还没完成。LearningRate 取 0.005 时背景更新温和适合白天光照稳定、车流平稳的场景黄昏或夜间光照持续变化时我会调到 0.02。检测循环里直接调用对象即可传入彩色帧就行fgMask foregroundDetector(frame);返回的 fgMask 是逻辑矩阵背景位置为 false前景为 true。运行时可以通过 imshow(fgMask) 观察前几十帧输出确认检测器是否把路面阴影也判成了前景。3.3 形态学后处理与 regionprops 车辆框选得到前景掩码后先做后处理再提取每个车辆块的位置。这一段代码是整个检测环节里调参最集中的地方se1 strel(disk, 2); se2 strel(disk, 5); fgMask imopen(fgMask, se1); % 去除孤立噪点 fgMask imclose(fgMask, se2); % 填充车体内部空洞 fgMask bwareaopen(fgMask, 200); % 删除小于 200 像素的碎块 stats regionprops(fgMask, BoundingBox, Centroid, Area);开运算的结构元半径 se1 取 2能去掉孤立点但不影响车辆边缘。闭运算的 se2 取 5在 480p 下对填补车体空洞效果稳定换成 1080p 视频后se2 通常要放大到 8~10。bwareaopen 的 200 像素阈值针对 640×480 设置分辨率提高后应等比例放大到 800 以上否则远处的小噪块会产生大量假目标。regionprops 返回的是结构数组每个元素对应一个前景连通域。这里用到的三个属性各有用途属性返回值格式用途BoundingBox[x, y, w, h]画矩形框、计算车辆底部中点Centroid[x, y]帧间匹配、目标位置估计Area标量过滤面积过小的噪声块把检测框画到原帧上使用 Computer Vision Toolbox 的 insertShapefor i 1:numel(stats) frame insertShape(frame, Rectangle, stats(i).BoundingBox, ... Color, green, LineWidth, 2); end看到绿色框包住车身检测环节就闭环了。一个常见误用是直接拿 stats 数量当作车流量这个值在车辆静止时会被反复计入车辆并行时又可能只算出一个连通域。真正的车流量统计要引入虚拟检测线的概念这正是下一章的内容。4. 车流量计数实现底部中点、跨线判据与去重4.1 为什么“框与线相交”不能直接计数写车流量计数时最容易出现的逻辑是“检测框与检测线有重叠就计数”。这个做法存在两个致命问题。同一辆车跨线期间可能连续几十帧都能检测到计数值会明显虚高大车经过时检测框横跨检测线长度较大同一目标在相邻帧间的位置抖动也会触发重复计数。正确做法是记录目标的位置变化只在它真正从检测线一侧移动到另一侧时触发一次计数。实际项目中我采用的标准做法是用车辆检测框的底部中点作为参考点。摄像头通常略向下俯拍车辆底部与地面接触的位置最为稳定不会因为车身高低、车窗反光或车顶颜色变化而漂移。计数成立的条件是底部中点跨过检测线并且前后两帧能判定为同一个目标。4.2 底部中点加帧间距离匹配的计数代码以水平检测线为例假定车辆从画面下方驶向上方也就是底部中点的 y 坐标逐渐变小。检测线设置在画面高度 60% 处计数循环如下lineY round(vid.Height * 0.6); % 检测线位置 maxMatchDist 40; % 相邻帧允许的最大移动距离 prevBottom zeros(0, 2); % 上一帧所有底部中点坐标 vehicleCount 0; while hasFrame(vid) frame readFrame(vid); fgMask foregroundDetector(frame); fgMask bwareaopen(fgMask, 200); stats regionprops(fgMask, BoundingBox, Centroid, Area); currentBottom zeros(0, 2); for i 1:numel(stats) bbox stats(i).BoundingBox; bottomPt [bbox(1) bbox(3) / 2, bbox(2) bbox(4)]; % 底部中点 currentBottom(end 1, :) bottomPt; if bottomPt(2) lineY ~isempty(prevBottom) dists sqrt(sum((prevBottom - bottomPt).^2, 2)); [d, idx] min(dists); if d maxMatchDist prevBottom(idx, 2) lineY vehicleCount vehicleCount 1; end end frame insertShape(frame, Rectangle, bbox, ... Color, green, LineWidth, 2); frame insertMarker(frame, bottomPt, x, ... Color, red, Size, 8); end prevBottom currentBottom; frame insertShape(frame, Line, [1, lineY, size(frame, 2), lineY], ... Color, yellow, LineWidth, 2); frame insertText(frame, [10, 10], sprintf(Count: %d, vehicleCount), ... FontSize, 32, BoxColor, black, TextColor, white); imshow(frame); drawnow; end代码的关键是当前帧每个底部中点到上一帧所有底部中点的最近距离。d 小于 maxMatchDist 时视为同一辆车再比较上一帧位置是否在检测线另一侧。如果上一帧还在线下方、当前帧已经到线上方说明车辆刚完成跨线计数加一。currentBottom 在每一帧末尾整体替换 prevBottom这样不需要维护复杂的跟踪列表。核心参数需要根据实际视频调整参数典型值调整依据lineY画面高度 60%~70%选车辆行驶路径最长、目标最完整的位置maxMatchDist30~50 像素车速乘以帧间隔车速越快值越大bwareaopen 阈值200480p分辨率越高值越大LearningRate0.005光照变化大时调大慢车多时调小如果测试视频中车辆是横向行驶比如从右向左把水平线换成垂直线即可。判断条件从比较 y 坐标改为比较 x 坐标例如 bottomPt(1) lineX 且 prevBottom(idx, 1) lineX其他处理逻辑不变。4.3 用效果图验证计数可靠性每跑完一段视频效果图上会有绿色检测框、黄色检测线和左上角累计计数。验证计数是否可靠不能只看最终数字要看跨线瞬间的帧画面检测线附近是否出现红色 x 标记标记是否落在车底位置。我一般会抽取跨线前后的几帧单独保存一帧一帧确认底部中点没有落到路面阴影上。如果同一辆车被重复计数优先检查 maxMatchDist 是否过小。该值过小时同一辆车在相邻帧的底部中点距离超过阈值会被当成新目标再计一次。如果两辆并行车辆被计成一个则要回到形态学环节适当增大闭运算结构元半径让两车之间的连接缝隙保持填充或分离状态。要以判断为准不要直接套用默认参数。5. 测试数据适配、效果图输出与两处典型误检的调参顺序5.1 分辨率和帧率对参数迁移的影响拿到一份新的测试数据先看分辨率和帧率。分辨率决定 bwareaopen 的面积阈值帧率决定 maxMatchDist。若测试视频是 1920×1080而代码默认按 640×480 调参最小面积阈值应从 200 等比例提高到 900 左右。让阈值随画面总像素线性缩放是最省事的做法minArea 200 * (vid.Width * vid.Height) / (640 * 480);帧率方面vision.ForegroundDetector 的 NumTrainingFrames 按帧数计不感知时间。30fps 视频用 20 帧做背景建模大约覆盖 0.67 秒15fps 视频同样的 20 帧会覆盖 1.3 秒。处理不足半分钟的短测试视频时把 NumTrainingFrames 降到 10 会更合理。5.2 效果图输出原图、掩码与结果同屏保存效果图不只是报告素材也是调试工具。推荐输出“三段式”截图左边原始帧中间前景掩码右边叠加检测框、检测线和计数的结果帧。这样可以在同一张图里定位问题是出在前景分割还是计数逻辑。figure(Position, [100 100 1800 500]); subplot(1, 3, 1); imshow(frame); title(原始帧); subplot(1, 3, 2); imshow(fgMask); title(前景掩码); subplot(1, 3, 3); imshow(frameOut); title(sprintf(检测结果 %d 辆, vehicleCount)); exportgraphics(gcf, result_0050.png, Resolution, 150);exportgraphics 会按当前图形窗口尺寸导出高清图片适合直接放进报告。较老版本的 MATLAB 没有这个函数改用 print(gcf, -dpng, result_0050.png) 效果一样。检查前景掩码一栏时重点看车辆是否连成完整块、路面阴影是否入框。这两类问题在掩码阶段就能判断不需要等到计数结果出错再回头排查。5.3 两处典型误检的调参顺序阴影误检最常见。车辆阴影与路面对比度低、颜色偏暗GMM 单纯靠灰度无法把阴影与车体完全区分。处理顺序应当是先把检测线向画面近端移动让阴影和车体在跨线前完成连通域合并或分离如果无效再考虑对掩码做 HSV 空间的阴影处理用饱和度信息进一步剔除。不要一上来就调 GMM 参数阴影问题靠调整权重往往得不到明显改观。车体断裂是另一类高频问题表现为一辆车被画成两个框。优先检查 bwareaopen 阈值是否过大再检查 imclose 半径是否足够。把闭运算结构元从 disk(5) 调到 disk(8) 通常能解决车顶与车身灰度不一致造成的掩码空洞。LearningRate 留到这一步之后再碰它影响的是慢速车辆是否被背景吸收与车体断裂问题的相关性最低。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门