
简介这套matlab车流量检测项目源码由达摩老生整理校正面向计算机视觉初学者及有一定经验的开发者可用于车辆检测、背景建模、图像分割等场景的学习与二次开发。压缩包共44个文件主要包括15个.m源程序、14张png测试图片、若干bmp/jpg图像素材、一段avi测试视频及docx算法说明文档整体约21.96MB覆盖从图像预处理、背景差/帧差到分割检测结果输出的完整流程。目前已有267人学习下载。作者标注为亲测校正、百分百成功运行并承诺遇到问题可联系指导资源内置图片集与测试视频便于直观对照算法效果同时附有多个典型处理环节的脚本与结果图并保留从源码到输出图像的完整脉络适合毕业设计、课程实践或实际项目落地时参考借鉴。1. 车流量检测在 MATLAB 里怎么做先把“计数”和“识别”分开想拿到一批道路监控的测试图片直觉反应是“先识别出车”。这个思路不算错但会绕远路。车流量检测的真正难点从来不在“这一帧画面里有没有车”而在“同一辆车经过检测区时只被算了一次”。静态图片集本身没有时间轴所以第一步不是调识别模型而是把图片按拍摄顺序组织成可逐帧播放的图像序列有了时间轴才能谈背景建模、车辆框关联和跨线计数。下面按固定机位监控场景为前提讲一条不依赖深度学习的 MATLAB 路径图片集怎么整理、怎么用自适应前景分割把车从路面里抠出来、怎么设计一条虚拟检测线完成计数以及最后用测试图片逐帧核对时重点看哪几个参数。技术栈只用 MATLAB 基础环境和 Computer Vision Toolbox适合方案验证或毕业设计快速落地。2. 图片集的组织与预处理把测试图片变成可播放的图像序列2.1 为什么单张图片只能数车不能算流量单帧图片能回答“此刻路面有多少辆车”回答不了“这十分钟通过了多少辆”。车流量必须除以时间而“时间”来自图片集的先后顺序。监控抽帧图片通常叫frame_0001.jpg、frame_0002.jpg天然携带时间信息但这里有个坑MATLAB 的dir函数和imageDatastore都按字典序排列文件名frame_10.jpg会排在frame_2.jpg前面。如果直接逐帧读取算法在时间轴上会来回跳同一个目标一会儿出现在画面里一会儿又倒回去计数结果基本作废。这也是为什么标题里强调“包含图片集、测试图片”却不直接说视频图片集本身不是视频但车流量检测必须借时间轴完成。所以我一般会先做一件事把图片集的读取顺序固定下来并且让这个顺序可以被脚本随时验证。2.2 用 imageDatastore 读取图片集并强制按数值排序% 读取某个目录下所有 jpg 图片 imds imageDatastore(D:\traffic\frames\*.jpg, ... FileExtensions, .jpg); % imageDatastore 默认按文件名字典序排 % 这里把文件名里的数字编号单独提取出来做数值排序 rawFiles imds.Files; frameNo zeros(numel(rawFiles), 1); for i 1:numel(rawFiles) [~, name] fileparts(rawFiles{i}); numStr regexp(name, \d, match, once); % 取第一个连续数字串 frameNo(i) str2double(numStr); end [~, idx] sort(frameNo); imds.Files rawFiles(idx);这段代码的关键在regexp(name, \d, match, once)它提取文件名中第一段连续数字。比如frame_0234.jpg会得到234img_0001.jpg会得到1。sort默认升序返回的idx是重排索引直接赋回imds.Files即可。如果文件名里没有数字编号可以用dir返回的datenum按文件修改时间排序只是可读性差一些。顺带验证一下顺序对不对disp(imds.Files(1:5))看到前五帧的文件名是递增的再往下写算法。2.3 图片集、视频流和帧率三种数据组织方式的取舍有时候项目自带的是视频而不是图片集或者你把图片集拼成了视频再喂给跟踪器。三种组织方式各有适用场合数据组织方式适用场景优点代价imageDatastore 循环读图帧间隔不稳、只有几十张测试图片不占额外磁盘每帧可单独预处理要自己维护顺序和“上一帧”状态先拼成 mp4/avi 再读 VideoReader帧率稳定、想复用光流/跟踪器VideoReader 生态成熟帧率可控多一次编码参数调完还要重新拼dir 通配符 imread一次性验证脚本无依赖、代码最短边界条件和排序全要手写我一般倾向 imageDatastore因为车流量检测的前景分割和跨线计数都是按帧走的不需要视频编码格式参与。但如果后续要换用vision.KLT跟踪器或深度学习目标检测模型视频流接口反而更方便。图片集转视频用VideoWriter就好设置FrameRate为真实采集帧率比如 15每读一帧writeVideo一次注意先open最后close。2.4 预处理四件事灰度化、ROI、直方图均衡与缩放currentFrame imread(imds.Files{k}); % 转灰度车流量检测用不到颜色灰度图能减少光照变化带来的噪声 gray rgb2gray(currentFrame); % ROI 截取只保留车道区域把路牌、天空、对向车道排除掉 roi [120, 240, 600, 320]; % [x y w h]先手动在图上量一次 gray imcrop(gray, roi); % 直方图均衡弱光场景下提高对比度让车身和路面分得开 gray adapthisteq(gray, NumTiles, [8 8], ClipLimit, 0.02); % 分辨率超过 960 时缩一半后续 regionprops 会快 4 倍 if size(gray, 2) 960 gray imresize(gray, 0.5); endroi的四个值别拍脑袋定MATLAB 里运行imrect拖一个矩形框住车道getPosition取整后写回代码。ROI 越大干扰越多计算越慢ROI 太小会漏掉相邻车道流量统计偏差大。adapthisteq和histeq的区别在于它是分块自适应的不会把天空整片提亮对阴影较多的路面更友好。ClipLimit控制对比度增强强度默认 0.01 容易增强不足0.02 是我常用的起点。注意ROI 下边界要留出足够空间让车辆能完整出现在检测线附近而不是车头刚进入画面就被截断。3. 前景提取与车辆定位背景差分 形态学 连通域3.1 固定机位场景为什么先做前景分割而不是目标检测架在龙门架或路边杆上的摄像头背景基本不变变化的主要是路面上的车辆。这个前提让问题大幅简化先让模型学会“背景长什么样”再把当前帧和背景逐一对比不一样的像素就是前景也就是车辆。深度学习在 MATLAB 里也能做比如 Deep Learning Toolbox 里的 YOLOv4 目标检测但它需要先标注几百张车辆框还要处理小目标、遮挡和夜间泛化问题做方案验证的成本明显偏高。固定机位下背景差分是性价比最高的方案这也是交通监控项目里最经典的处理思路。背景差分真正难的不是“分离前景”而是“背景会慢慢变化”树影在动、光照在变、远处路面积水反光。所以背景模型必须能自适应更新不能只取第一帧当背景。3.2 帧差法、均值背景、高斯混合模型怎么选方法实现成本抗光照变化对慢车/静止车的表现说明帧差法低差会漏检目标静止后相邻两帧没有差异车直接消失均值背景法中中会吸收进背景需要维护背景图和更新率场景中不能有长时间停留车GMM 高斯混合模型中高较好较好对每个像素维护多个高斯分布能表达多模态背景帧差法可以作为入门练习abs(double(currGray) - double(prevGray)) 25就认为是前景。但它的问题是重影车辆移动后前后两个位置都会被标成前景而且车辆一旦刹车停下帧差结果就归零。均值背景的致命伤在堵车场景车停久了会被渐渐并入背景之后重新启动时又会被误判成新来车辆。GMM 对每个像素维护 5 个高斯分布能表达“路面虽然亮但也会被树叶阴影短暂遮住”这类情况所以我最终选了 GMM。3.3 用 vision.ForegroundDetector 做自适应背景建模MATLAB 的 Computer Vision Toolbox 直接提供了 GMM 前景检测器接口简单不用自己写更新公式。% 创建高斯混合前景检测器 fgDetector vision.ForegroundDetector(... NumGaussians, 5, ... % 每个像素用 5 个高斯分布 NumTrainingFrames, 50, ... % 前 50 帧只训练背景不输出前景 MinimumBackgroundRatio, 0.7, ... LearningRate, 0.005); % 训练阶段喂 50 帧灰度图让背景稳定下来 for i 1:50 frame imread(imds.Files{i}); frame imcrop(rgb2gray(frame), roi); fgDetector(frame); % 训练期间丢弃输出 end % 正式处理阶段逐帧输出二值前景掩码 mask fgDetector(imcrop(rgb2gray(currentFrame), roi));NumGaussians表示每个像素用几个高斯分布拟合背景5 是常见默认值越大越能覆盖复杂背景但也越耗时。NumTrainingFrames是训练帧数图片集只有一两百帧的话设置 20 更合理否则大量有效帧被消耗在训练阶段。MinimumBackgroundRatio是“一个高斯分布被认定为背景的最小概率”取值越大背景越保守慢车不容易被吸进背景但阴影也更容易被保留成前景。LearningRate是背景更新速度0.005 表示背景变化慢适合光线稳定的白天场景傍晚光线变化快的场景建议调到 0.02否则整个画面会逐渐被误判为前景。3.4 形态学清理与 regionprops 提取车辆框GMM 输出的mask是二值图里面既有车身也有大量孤立噪点。直接用bwconncomp找连通域会得到一堆碎片所以要先做形态学清理% 开运算先腐蚀后膨胀去掉小噪点和路面反光 mask imopen(mask, strel(disk, 3)); % 闭运算先膨胀后腐蚀把车窗玻璃造成的孔洞补平 mask imclose(mask, strel(disk, 5)); % 去掉与图像边缘粘连的大块防止路肩被算成车 mask imclearborder(mask); % 连通域分析提取车辆框和中心点 cc bwconncomp(mask, 8); stats regionprops(cc, BoundingBox, Centroid, Area); areas cat(1, stats.Area); validIdx find(areas 800 areas 20000); % 面积过滤 boxes cat(1, stats(validIdx).BoundingBox); % Mx4: [x y w h] centroids cat(1, stats(validIdx).Centroid); % Mx2: [x y]strel(disk, 3)的结构元素半径太小噪点去不干净太大会把相邻两辆车的连通域连成一片。初值给 3 之后用测试图片里最暗的一帧观察效果再调。面积阈值的设定和画面分辨率强相关一辆车在 640x360 的画面里大约占 8000 像素minArea取 800 正好是它的十分之一用来过滤小噪点maxArea取 20000 是为了防止阴影和花屏被当成车辆。如果直接看到“两辆车连成一个框”多半是imclose的半径太大或者minArea太小。3.5 调这套前景代码时最容易踩的三个坑第一车辆停住后 3 秒左右会被 GMM 吸进背景这是因为LearningRate仍在持续更新背景。真要统计红绿灯路口的排队车流就得给检测器加“暂停更新”逻辑将掩码中车辆占比超过阈值的帧跳过不训练。第二车身颜色与路面颜色接近时GMM 很容易把车身内部当成背景导致掩码出现“空洞”闭运算能补一部分但如果空洞贯穿整个车身区域会被切成两块一辆车变两辆。第三强烈阳光下车辆阴影会连到车底使二值图变成一个整体大块单纯调大minArea没有用建议转 HSV 空间用 V 分量判断阴影区域并单独剔除。4. 虚拟检测线与车流量计数跨线逻辑与去重机制4.1 检测线的位置和方向决定了计数的稳定性前景分割把“车在哪”解决了接下来要解决“车过了没有”。最稳定的做法是在画面里画一条虚拟检测线车辆中心点从线的一侧运动到另一侧时计一次数。检测线放在图像的下部而不是中部是因为越靠近摄像头车辆形变越稳定且小目标提前被过滤掉误检率低。以 ROI 高度为 320 像素为例lineY取 240 附近通常比较合适。多车道场景要做两件事一是按车道宽度把 ROI 竖直切分每个车道单独做连通域二是每条车道画不同的斜线或竖直线不要共用一个水平线。原因很简单并排行车的两辆车在 ROI 里距离很近连通域可能已经合并成一个大块中心点落在两车之间共用检测线时会产生大量跨线抖动。4.2 用最近邻匹配做帧间车辆关联计数之前先把当前帧检测到的车辆框和上一帧的车辆框对应起来。简单可靠的关联策略是最近邻匹配距离阈值按车辆在一帧间隔内的最大位移来定。% prevCentroids: 上一帧 Nx2 的车辆中心点 % currCentroids: 当前帧 Mx2 的车辆中心点 cost pdist2(prevCentroids, currCentroids); % NxM 欧氏距离矩阵 prevTracks repmat(struct(lastY, [], centroid, [], counted, false), ... size(prevCentroids, 1), 1); assignedCurr false(size(currCentroids, 1), 1); maxDist 80; % 一帧内车辆最多移动的像素数 for i 1:size(prevCentroids, 1) [minDist, j] min(cost(i, :)); if minDist maxDist ~assignedCurr(j) prevTracks(i).centroid currCentroids(j, :); prevTracks(i).curBox currBoxes(j, :); assignedCurr(j) true; % 每个当前目标只被匹配一次 end endpdist2默认算欧氏距离返回矩阵cost(i, j)表示上一帧第i个目标和当前帧第j个目标的距离。maxDist是核心参数固定机位且帧率 15 时小型车一帧内移动约 20~50 像素80 像素能容忍大部分形变和检测抖动。这个值取得太大会把紧邻的前后车错误关联取得太小轨迹容易断开车辆会被重复计数。4.3 跨线判定与防重复计数关联成功后判断中心点是否跨过检测线。车辆从上往下行驶时中心点的 y 坐标递增所以判定条件是“上一帧在线上面当前帧在线下面”lineY 240; % 检测线 y 坐标 tracks(i).lastY prevCentroids(i, 2); tracks(i).centroid currCentroids(j, :); % 滞后带上下各 3 像素防止中心点贴线抖动时反复计数 if tracks(i).lastY lineY - 3 tracks(i).centroid(2) lineY 3 if ~tracks(i).counted flowCount flowCount 1; tracks(i).counted true; end endcounted标志的作用是防止同一辆车在后续帧里被反复计数。配合滞后带用效果更稳波动幅度小于 3 像素时即使中心点短暂越过检测线也不会触发。帧率越高滞后带可以设得越小帧率低于 10 时要放宽到 5 像素以上因为车辆位移大crossing 帧可能直接被跳过。这里还要处理一种情况当前帧检测不到某辆车但下一帧它又出现了。如果不维护“丢失帧数”这辆车会被当成新目标再计一次。常见做法是给每个 track 增加lostFrames字段关联成功时清零失败时加一超过 5 帧才终止该 track。参数典型初值调大效果调小效果maxDist80 像素跨帧关联更容易但可能连接不同车辆轨迹容易断计数偏高lostFrames 上限5 帧容忍检测中断也容易把已驶出车辆拉回来快速车辆被二次计数滞后带宽度±3 像素抗抖动但快速车辆可能漏计计数敏感贴线抖动引发重复计数4.4 双向车流和虚拟线圈的扩展双向车道在画面上表现为两个方向相反的运动流判定条件只需增加一个方向分支if lastY lineY curY lineY downCount downCount 1; elseif lastY lineY curY lineY upCount upCount 1; end如果检测线附近有弯道车辆轨迹不是直线跨 y 坐标的判定会失效。这时可以把检测线升级成虚拟线圈用roipoly在画面上圈一个多边形区域判定“上一帧中心点在区域外当前帧中心点在区域内”就算压线。这本质上是把一维跨线判断扩展成二维包含判断抗弯道能力强得多。5. 测试图片验收可视化标注与参数回归5.1 用 insertObjectAnnotation 批量输出标注图写一版跑完测试图片后自动保存标注结果的脚本比在命令行里一行行看数字直观得多。annotated insertObjectAnnotation(currentFrame, rectangle, boxes, ... repmat({car}, size(boxes, 1), 1), ... Color, yellow, LineWidth, 2, TextBoxOpacity, 0.7); imwrite(annotated, fullfile(outDir, sprintf(annotated_%04d.jpg, k)));insertObjectAnnotation的第二个参数rectangle对应 boxes 的格式必须是 M x 4 的矩阵每行是[x, y, width, height]和regionprops输出的BoundingBox直接兼容。TextBoxOpacity设 0.7 是为了让标签文字不遮挡车身。运行时注意currentFrame用的是原始彩色帧而不是预处理后的灰度图。5.2 用 montage 拼图快速发现误检模式单张图看不出规律把所有测试图片的标注结果按时间顺序拼在同一个窗口里误检规律立刻暴露annotatedImds imageDatastore(outDir); montage(annotatedImds.Files, Size, [4 8]); title(全部测试帧标注结果检查漏检/重复框);montage 默认按文件名顺序排列由于 5.1 的输出文件名保留了帧序号拼图顺序就是时间顺序。看拼图时重点找两类问题一类是同一辆车在连续几帧里出现了多个框说明面积过滤或闭运算参数不对另一类是某个区域的树影反复被标成车说明MinimumBackgroundRatio设得偏低。5.3 数值化评估流量误差率和参数结构体光看图片还不够性能指标要落到数字上。对测试片段人工数一遍过车数和脚本输出的flowCount对比manualCount 42; % 人工统计测试视频中的真实过车数 errRate abs(flowCount - manualCount) / manualCount * 100; fprintf(流量误差率: %.1f%%\n, errRate);流量误差率控制在 5% 以内算合格。如果超了优先怀疑重复计数而不是漏检因为重复计数会把误差率直接放大一倍。这时候我会把所有可调参数收进一个params结构体写一个双层循环做参数扫描把每次的参数组合和误差率存进results表选出最优一组。打开输出目录挑一张车辆正好压在检测线上的帧双击放大看看标注框还在不在——这是所有调参结束后第一件该做的事。本文还有配套的精品资源点击获取