ARTICLE DETAIL

资讯详情

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

MATLAB车流量检测:背景差法、虚拟线与计数去重实战

MATLAB车流量检测:背景差法、虚拟线与计数去重实战 简介面向车辆视频场景的 MATLAB 车流量检测项目源码以帧差、背景差分、增强对比度二值化等经典图像处理算法为核心覆盖背景建模、目标分割、车辆检测与跟踪演示的完整流程适合新手和有一定经验的开发者快速掌握视觉算法在交通场景中的实际落地方法。资源共 44 个文件以 .m 源码与 .asv 备份为主另有多张 png/bmp/jpg 检测与分割效果图、一个 avi 测试视频、rar/zip 归档压缩包及说明文档整体 18.72MB目录分类清晰便于按脚本、图像、文档模块查阅。已有 573 人学习下载。包内提供可直接运行的检测脚本、原始图像/背景图/分割结果等中间可视化素材并附算法说明文档所有源码均经亲测校正可减少调试成本按处理顺序理解车流量检测全流程并在此基础上进行参数调整与二次开发。1. 车流量检测的MATLAB实现路径从背景建模到计数闭环把路口监控视频丢给MATLAB做一个车流量检测项目要输出能交差的统计数字90%的精力其实不在“检测”而在“计数去重”。同一辆车在连续30帧里反复出现如果不对目标做关联或触发抑制统计结果会膨胀到实际车流的十几倍。MATLAB在这个题目上仍然值得选因为Computer Vision Toolbox里的背景建模、连通域分析和形态学处理都不需要标注样本参数调整全程可见适合做信号灯配时研究、拥堵预警原型以及需要提交测试数据和效果图的图像处理课程设计。这个标题背后的需求也就很清晰了拿到一段MP4跑出一条车流量曲线再用效果图和统计图表证明结果可信。2. 车流量检测的核心原理为什么背景差法比深度模型更先落地2.1 帧差法、背景差法和光流法在车流量检测里的取舍车流量检测的视频场景有两个明显特征摄像头固定背景几乎不变。这决定了先做运动分割、再对目标计数的传统路线往往比训练目标检测网络更稳。常见的候选方案有三种帧差法、背景差法、光流法它们在MATLAB里都有现成实现但代价完全不同。帧差法用相邻两帧相减运动区域直接暴露。实现只要absdiff加阈值但车辆表面颜色接近路面时中间会出现空洞慢速行驶时前后帧重叠又会产生“双影”。光流法能提供运动方向和速度对车流统计很理想然而在纯CPU环境下一个1080p帧的稠密光流计算要几百毫秒很难实时。背景差法在固定摄像头场景下最合适。先用前若干帧建立背景模型再用当前帧减去背景凡是偏离背景模型的像素都标记为前景。经典实现是混合高斯模型GMM车辆在红灯前停车几十秒也能保持在前景里不会像帧差法那样目标一停就融入背景。三者的关系用一张表能看明白方法计算量慢车/停车表现实现难度适用场景帧差法低停车即消失低快速路、车辆不停留背景差法GMM中稳定保留中路口、城市道路、收费站光流法高持续检测但噪声大高行人轨迹、车速估算在做车流量检测项目时我会直接锁定背景差法把光流留给后续测速扩展而不是一开始就铺开。深度学习目标检测也是可选路线但在这个标题下传统方案不需要训练数据和模型文件半天就能跑出测试数据和效果图更适合作为第一版交付。2.2 车流量检测的任务拆解检测框、跨线事件与计数车流量检测不能只做“画面里有没有车”最终交付的是一段时间内的车辆数。这个目标可以拆成三层前景分割、目标定位、跨线事件判定。前景分割回答“哪些像素是运动物体”目标定位把像素聚成一个个包围盒跨线事件则决定“这辆车什么时候算通过”。跨线事件是最容易写错的环节。如果每帧检测到车就加一结果会严重偏大如果等目标离开画面再加一又会有大量车还没驶出视野时下一辆车已经进来。工程上首选方案是虚拟检测线在画面固定位置画一条线只有当车辆质心从线的一侧跨到另一侧时计数才触发一次。这样设计的好处是把检测与计数解耦。检测算法偶尔丢一两帧没关系只要车辆在跨线的关键帧被识别事件就不会丢同一辆车反复出现的问题则通过冷却帧数来抑制。理解了这个拆解后面调参数才有依据否则只会盲目改面积阈值。2.3 MATLAB中车流量检测依赖的工具箱与最小代码MATLAB做这件事依赖两个工具箱Computer Vision Toolbox提供视频读写、前景检测器、Blob分析Image Processing Toolbox提供形态学滤波、孔洞填充和连通域过滤。两者在图像处理类任务里几乎是标配。R2017b以上版本都能运行下面这段最小代码。% 构建视频读取对象、前景检测器、Blob分析器 vidReader vision.VideoFileReader(traffic_test.mp4); fgDetector vision.ForegroundDetector(... NumGaussians, 5, ... % 高斯分量数量 NumTrainingFrames, 50, ... % 前50帧用于背景建模 MinimumBackgroundRatio, 0.7); % 背景占比阈值 blob vision.BlobAnalysis(MinimumBlobArea, 150); % 对第一帧执行一次验证整条链路 frame step(vidReader); fgMask step(fgDetector, frame); [area, centroid, bbox] step(blob, fgMask);这里NumGaussians控制背景模型能接受多少种灰度变化树影和路面亮度扰动明显时调到6以上但调到7之后计算速度会明显下降。MinimumBlobArea是过滤小噪点的面积下限。把这三个对象接好车流量检测的入口就打开了。3. 用MATLAB跑通车流量检测的最小实现与参数表3.1 工程文件划分与测试数据准备车流量检测脚本很容易写成一个几十行的main.m但实际调参时你会发现每次改参数都要翻找变量很不方便。我一般会拆成三个文件main_vehicle_count.m负责主循环detect_foreground.m封装前景提取和后处理crossing_count.m封装跨线计数。测试数据放在data/目录效果图输出到results/目录。这样换一条视频只需要改两个参数视频文件名和检测线高度。测试数据可以自己拍也可以找公开的交通监控片段。无论来源尽量包含三种情况白天顺畅车流、红灯排队起步、有树影或路面反光的路段。“包含测试数据和效果图”的项目标题意味着验证闭环本身也是交付物所以我建议在代码开头就把输入输出路径统一写好videoFile fullfile(data, traffic_test_01.mp4); resultVideo fullfile(results, traffic_result.avi); lineY 360; % 检测线位置取画面高度60%处3.2 前景提取、形态学后处理与Blob分析基于matlab图像处理的前景提取流程大体固定读取当前帧喂给前景检测器得到二值掩码然后用开运算去掉小亮点闭运算连接车身上的断裂填充孔洞最后用面积过滤去掉噪点。核心清理就是几行代码fg step(fgDetector, frame); fg imopen(fg, strel(square, 3)); % 先去孤立亮点 fg imclose(fg, strel(square, 9)); % 再连断裂区域 fg bwareaopen(fg, 150); % 去掉小于150像素的块strel(square, 3)表示3x3方形结构元尺寸越大去噪越狠但也容易把两辆靠近的车连在一起。bwareaopen的面积阈值要和后面BlobAnalysis的MinimumBlobArea保持一致否则会出现检测器认为有目标、后处理又把它删掉的矛盾。处理完的掩码交给vision.BlobAnalysis一次得到面积、质心和外接矩形三个数据。把流程封装进主循环并且把检测框和检测线画到原图上方便肉眼核对while ~isEOF(vidReader) frame step(vidReader); fg step(fgDetector, frame); fg imopen(fg, strel(square, 3)); fg imclose(fg, strel(square, 9)); fg bwareaopen(fg, 150); [area, centroids, bbox] step(blob, fg); % 把检测框画到原图上便于核对 frame insertShape(frame, Rectangle, bbox, Color, yellow); frame insertShape(frame, Line, [0 lineY size(frame,2) lineY], ... Color, red, LineWidth, 2); end3.3 用虚拟检测线完成车流量计数的核心逻辑计数逻辑基于一个朴素事实车辆在通过检测线时其质心的y坐标会从线的一侧换到另一侧。取检测线附近的目标质心保留上一帧的质心y坐标如果上一帧在线下、当前帧在线上就认为有一次跨线事件。这一步先不考虑跨车关联用冷却帧数防止同一辆车被连续计数lastCY []; % 上一帧检测线附近的质心y坐标 cooldown 0; totalCount 0; while ~isEOF(vidReader) frame step(vidReader); fg step(fgDetector, frame); fg imopen(fg, strel(square, 3)); fg imclose(fg, strel(square, 9)); fg bwareaopen(fg, 150); [area, centroids, bbox] step(blob, fg); % 只取检测线上下30像素内的目标 nearLine abs(centroids(:,2) - lineY) 30; if any(nearLine) [~, idx] min(abs(centroids(nearLine,2) - lineY)); cNow centroids(nearLine,:); cNow cNow(idx,:); % 上一帧在线下当前帧在线上 跨线事件 if ~isempty(lastCY) lastCY lineY cNow(2) lineY cooldown 0 totalCount totalCount 1; cooldown 15; % 冷却15帧防止重复计数 end lastCY cNow(2); else lastCY []; end if cooldown 0 cooldown cooldown - 1; end endabs(centroids(:,2) - lineY) 30把搜索范围限制在检测线上下30像素内避免远处车辆的质心干扰判断。lastCY只取离检测线最近的质心这在单车道、车流量不是很大的条件下足够用。冷却15帧意味着一次触发之后至少0.5秒按30fps算内不会再次计数排队车辆刚好能区分开。3.4 车流量检测参数参考表与常见误用下面这张表对应上面代码里的每个可调项。调参顺序从高往低先把前景检测器调准再改形态学最后才动计数逻辑。参数建议范围影响调参信号NumGaussians3~6对背景灰度变化的容忍度背景常有扰动就调大NumTrainingFrames30~80背景收敛速度视频前段出现大量目标就调小MinimumBackgroundRatio0.5~0.9前景判定灵敏度背景误检多就调高MinimumBlobArea / bwareaopen100~400最小车辆投影面积小目标丢失调小噪点调大MaximumBlobArea10000~30000过滤面积异常大的合并块多车粘连时调小cooldown10~25帧计数去重的抑制时间重复计数调大漏计调小常见的误用是把MinimumBlobArea调得很小去追求不漏检结果是行人、飞鸟和树叶阴影全被当成车。另一个常见误用是冷却帧设太短把一辆车左右车窗的反射当成两辆车。每次改动只动一个参数跑完整个视频再看累计曲线不要盯着单帧效果调参。4. 车流量检测进阶多车道关联、IOU跟踪与阴影处理4.1 多车道场景下的检测线设计与方向计数单车道方案在多车道路口会立刻失效。不同车道的车同时接近检测线只取最近质心的做法会把多个目标混成一次跨线事件计数严重偏低。常见做法是给每个车道单独维护一组计数状态把画面按车道分成若干矩形区域只在该区域内做跨线判断laneX [100 250 400 550]; % 每条车道的x起始坐标 laneWidth 120; for li 1:length(laneX) inLane centroids(:,1) laneX(li) ... centroids(:,1) laneX(li) laneWidth ... abs(centroids(:,2) - lineY) 30; if any(inLane) c centroids(inLane,:); % 取该车道离检测线最近的目标 [~, idx] min(abs(c(:,2) - lineY)); % 这里复用上一节的跨线判断与冷却逻辑 end end每条车道独立冷却车道间的重复计数被天然隔离。如果路口有双向车流就再设一条反向检测线车辆从线的一侧到另一侧计一次反向再计一次两个方向的累计数分开输出。车道区域的x坐标必须在标定阶段一次调准否则两条车道边界重叠时一辆跨线变道的车会被两个车道同时计数。4.2 用bboxOverlapRatio做跨帧关联避免重复计数冷却帧只是一个妥协方案真正稳的做法是给检测框做跨帧关联。MATLAB里bboxOverlapRatio计算两个检测框的交并比assignDetectionsToTracks按交并比给上一帧轨迹和当前帧检测框做匹配两帧间匹配上的目标就认为是同一辆车。% 维护轨迹trackBoxes保存每个目标的最近检测框 costOfNonAssignment 0.3; [assignments, ~, unassignedDetections] ... assignDetectionsToTracks(trackBoxes, bbox, costOfNonAssignment); for i 1:size(assignments, 1) trackIdx assignments(i, 1); detIdx assignments(i, 2); trackBoxes(trackIdx, :) bbox(detIdx, :); % 用新检测框更新轨迹 lastSeen(trackIdx) frameIdx; end这里的核心参数是costOfNonAssignment 0.3交并比低于0.3时视为不是同一辆车值越小越容易把同一辆车拆成两条轨迹。用IOU匹配的好处是无需调卡尔曼滤波器的过程噪声和测量噪声对车流量统计这种低速、短遮挡场景精度完全够用。等轨迹稳定后再把每条轨迹的质心历史数据交给跨线判断计数就不会受多目标重叠影响。4.3 阴影、夜间与粘连目标的处理经验白天场景精度损失的主要来源是阴影。汽车阴影的亮度通常比路面低但和车身一样“运动”会不断进入检测线范围。常见做法是把RGB转到YCbCr用Cb分量的差异作为阴影掩码再从前景里减掉foregroundYCbCr rgb2ycbcr(frame); shadowMask fg (foregroundYCbCr(:,:,3) ... 0.6 * backgroundCb(:,:,3)); fg(shadowMask) 0;其中backgroundCb是背景模型里的Cb分量可以在训练阶段对前几十帧求均值得到。0.6这个系数按场景亮度调整数值越小阴影去除越保守、也越容易误伤深色车身。夜间则是另一个极端车身偏暗前景检测器经常把车头大灯和高光路面分离开来形成两个不连续的小块。我会先判断整帧平均亮度若明显低于日间水平就把MinimumBlobArea降到80同时把形态学闭运算的结构元加大到11x11让两个车灯区域连成一个整体。深色车和夜间车漏检是传统方法的通病这类场景更适合换成轻量目标检测网络而不是继续堆传统图像处理参数。不同场景的调整对照如下场景主要表现调整方式白天阴影检测线附近出现额外Blob计数偏高Cb分量阴影抑制适度加大开运算结构元夜间车灯分裂、车身丢失降低MinimumBlobArea加大闭运算结构元排队停车多车粘连成一个Blob缩小MaximumBlobArea必要时按宽度切分5. 车流量检测测试数据与效果图的验证方法5.1 给测试数据建立真值人工计数脚本车流量检测项目必须有真值否则报告里的准确率都是自说自话。写一个人工计数脚本读视频每帧按空格键记录一次“通过检测线”的事件把记录写到groundTruth.mat里字段就两列帧号和该帧触发的事件累加数。每段测试视频人工跑一遍得到一个gtCum累计曲线。gtCum load(groundTruth.mat).gtCum; % 真值累计曲线 algCum cumsum(countsPerFrame); % 算法累计曲线 plot((1:length(gtCum)) ./ fps, gtCum, k, ... (1:length(algCum)) ./ fps, algCum, r--); legend(真值, 算法);如果两条曲线形状一致、只是尾部差几辆车说明单帧检测没问题问题在计数去重。如果某一段开始斜率不同就把那一帧附近的原图和掩码存出来人工核对。5.2 输出效果图与结果视频效果图不是随手截一张图而是要有三种固定输出带检测框的视频帧、前景掩码帧、计数曲线图。前两种用insertShape加框后再imwrite保存曲线图用plot生成后saveas导出png。输出视频用vision.VideoFileWriter帧率必须和输入一致否则后续人工校队时对应不上。outVideo vision.VideoFileWriter(resultVideo, FrameRate, fps); frame insertShape(frame, Rectangle, bbox, Color, yellow); frame insertText(frame, [10 10], sprintf(Count: %d, totalCount), ... FontSize, 28, TextColor, red); step(outVideo, frame);5.3 评价计数误差的快速定位技巧最后提供一个排查技巧把countsPerFrame绘制成柱状图看哪一帧计数发生突变。正常行驶的路段计数应该是稀疏而单峰的如果某帧突然多出3辆车优先怀疑该帧前景掩码是不是把多辆排队车粘连成一个大Blob如果计数间隔均匀但总数偏小优先怀疑冷却帧数偏大导致慢速车被吞。先看帧变化量最大的时间点再回放那10帧原图和对应掩码九成问题都能一眼定位。本文还有配套的精品资源点击获取
返回列表