
简介面向目标检测入门与实战的YOLO系列汽车计数数据集压缩包适合需要训练车辆、卡车、公交车、自行车、拖拉机等类别检测模型的开发者。数据集已经划分好训练集、验证集与测试集并附带配置文件data.yaml可直接用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流目标检测算法。标注文件同时提供两种格式YOLO格式的txt文件每行记录一个目标包括类别索引、归一化后的中心点坐标与宽高坐标值均相对于图像尺寸VOC格式的xml文件则采用标准对象标注结构便于其他框架或平台读取与转换。压缩包共2000个文件以xml标注文件为主整体约184.13MB目录结构清晰可帮助使用者省去数据整理和格式转换的环节直接进入模型训练与验证阶段。已有57人学习浏览对于想快速上手汽车场景目标检测的初学者和研究者来说是一份可直接使用的现成数据集。1. 拿到这份车辆计数数据集你离一个能数车流的YOLO模型还差什么路口车流统计、停车场饱和度监测、园区出入口管理这类需求听起来简单实际落地时卡住的往往不是算法而是一份干净的带标签数据集。3870张图像、五个类别卡车、公交车、汽车、自行车、拖拉机覆盖了城市道路和混合交通场景这是做车辆计数项目一个很实在的起点。压缩包解出来里面有图像、有对应的txt标注文件按YOLO格式组织好目录就能用YOLOv8直接开训。这篇笔记就按拿到zip之后的真实操作顺序来写先检查标注质量再转目录结构、写配置文件、跑训练最后把模型输出变成“每小时通过多少辆车”的计数结果并把最容易翻车的几个坑提前摆出来。2. 先拆开zip看家底标注格式、类别分布与目录约定2.1 解开压缩包先确认这三种文件是否齐全拿到zip后第一件事不是急着训练而是确认压缩包里的东西完整、能对上。标准做法是先解压到工作目录再核对文件数量。Linux下用unzipWindows下用解压工具都行注意中文文件名乱码问题——如果你的zip是在Windows下压的解压到Linux服务器上很容易出现文件名乱码这个问题后面专门讲。一个合格的YOLO Detection数据集压缩包解压后至少要有三类东西文件类型扩展名作用图像文件.jpg / .png模型看到的原始画面注意尺寸和通道标注文件.txt每张图对应的目标框和类别编号类别名文件classes.txt / data.yaml类别编号到类别名的映射# 解压并查看结构 unzip yolo车辆计数数据集.zip -d ./vehicle_count/ cd ./vehicle_count/ find . -name *.txt | wc -l # 标注文件数量 find . -name *.jpg | wc -l # 图像数量应等于3870解压后先用wc -l确认图像和标注文件数量一致。3870张图像对应3870个txt文件是理想情况但实际下载的数据集经常出现个别图像没有标注文件这种脏数据会在训练时直接报错。我会先写一个脚本把“有图无标”和“有标无图”的文件遍历出来宁可花五分钟做数据体检也不要等训练到一半才被FileNotFoundError打断。2.2 读一行标注文件从txt坐标到真实边界框YOLO格式的txt每行表示一个目标框五列数字分别是类别编号从0开始、归一化中心点x、中心点y、归一化宽w、归一化高h。全部数值都在0到1之间是相对图像宽高的比例不是像素值。这个细节很多人栽过跟头——想手工检查标注对不对时拿像素坐标去比对结果怎么都对不上。# 随便看一张图的标注内容 cat 000001.txt # 输出示例 # 0 0.451562 0.493511 0.210938 0.261024 # 3 0.825129 0.722613 0.145682 0.183520 # 4 0.613601 0.810244 0.082731 0.140571第一条标注类别0假设是car中心点位于图像宽度的45.2%、高度的49.4%框宽占图像宽度21.1%框高占26.1%。换算回像素坐标的方式是拿这些比例分别乘图像的宽和高。检查标注质量时最直接的办法是写脚本把边界框画回图上肉眼扫一遍比任何统计指标都直观。具体做法是用OpenCV读图按上面公式把归一化坐标还原成像素坐标然后画矩形并标上类别名。2.3 类别分布不均是第一个要处理的隐患五个类别——卡车、公交车、汽车、自行车、拖拉机——的样本量绝对不平均。真实路况下汽车占大头拖拉机可能是少数派。这份数据集标称3870张但拖拉机可能只有几百个实例。训练之前必须先统计每个类别的实例数不均衡太严重时单纯的YOLO训练会让拖拉机这个类别直接“学不会”表现为recall极低、AP几乎为零。import os from collections import Counter label_dir ./vehicle_count/labels counter Counter() for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r) as fp: for line in fp.readlines(): cls int(line.strip().split()[0]) counter[cls] 1 print(counter) # 示例输出Counter({0: 4102, 1: 1288, 2: 3824, 3: 2030, 4: 516})跑完统计如果某个类别的实例数不足另一个类别的十分之一就要考虑用类权重、过采样或者干脆调整标签策略。我做车辆计数项目时遇到过拖拉机只有五百多个实例的情况最后是靠调整data.yaml里的类别顺序把少样本类别放在前面配合class_weight参数才把AP拉起来。这个后面在第5章的避坑部分再展开。2.4 标注质量的快速体检空标注、越界框、异常宽高比训练前还有一道体检工序值得做检查有没有空txt文件、有没有坐标越界、有没有宽高为0的非法框这些脏标注会让训练loss突然跳到NaN。import os invalid 0 for f in os.listdir(label_dir): path os.path.join(label_dir, f) if os.path.getsize(path) 0: print(空标注:, f) invalid 1 continue with open(path, r) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: print(列数错误:, f, line.strip()) invalid 1 else: _, cx, cy, w, h map(float, parts) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): print(越界框:, f, line.strip()) invalid 1 print(问题文件数:, invalid)空标注文件会让YOLOv8在计算损失时出现异常越界框则会影响锚框匹配的质量。跑完这一步把有问题的文件单独放到一个bad_files目录里先不删等训练结束后再补标或丢弃。3. 把3870张图喂给YOLOv8目录结构、data.yaml与训练参数3.1 按YOLO的规矩摆目录少走弯路YOLOv8的数据目录约定很明确images和labels两个兄弟目录各自下面再分train和val或test。同一个文件名的图像和txt必须分别在两侧对应位置例如images/train/000001.jpg对应labels/train/000001.txt。这个结构不能乱data.yaml里就是靠路径去定位的。我一般会把3870张图按8:2划分成训练集和验证集也就是约3096张训练、774张验证。划分之前先shuffle防止同一个地点、同一条路的图像全部挤在训练集里验证集失去意义。python -c import os, random, shutil os.makedirs(vehicle_count/images/train, exist_okTrue) os.makedirs(vehicle_count/images/val, exist_okTrue) os.makedirs(vehicle_count/labels/train, exist_okTrue) os.makedirs(vehicle_count/labels/val, exist_okTrue) imgs sorted(os.listdir(vehicle_count/images)) random.seed(42) random.shuffle(imgs) split int(len(imgs) * 0.8) for i in imgs: sub train if i in imgs[:split] else val name os.path.splitext(i)[0] shutil.copy(fvehicle_count/images/{i}, fvehicle_count/images/{sub}/{i}) shutil.copy(fvehicle_count/labels/{name}.txt, fvehicle_count/labels/{sub}/{name}.txt) 划分完扫描一遍两个子目录确认没有缺文件。这里的random.seed(42)很重要重复执行结果稳定后面调参数重新训练时对比公平。3.2 写data.yaml三个字段最容易写错data.yaml是所有训练的入口文件三个字段path数据集根目录、train训练图片子目录、val验证图片子目录以及names类别名列表。容易出问题的点有两个路径写成相对路径还是绝对路径以及names的顺序必须和标注文件里的类别编号严格对齐。# vehicle_count/data.yaml path: ./vehicle_count train: images/train val: images/val names: 0: car 1: truck 2: bus 3: bicycle 4: tractor注意names列表的索引就是标注txt里每行第一个数字。这个数据集标题给的类别顺序是“卡车、公交车、汽车、自行车、拖拉机”但实际标注文件里编号未必按这个顺序一定要打开几个txt看一眼类别0到底是什么再如实写names。一旦names和标注对应错位训练出来的模型预测时就会张冠李戴——车标成了自行车。3.3 训练命令从yolov8s起步先把pipeline跑通第一次训练不要直接上最大模型。我建议用yolov8s.pt作为预训练权重它在精度和速度之间最平衡显存占用也友好。机器显存不够时再降到yolov8n.pt但n的mAP会比s低两三个点。pip install ultralytics yolo detect train \ datavehicle_count/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ project./runs \ namevehicle_count_v1命令里的关键参数说明epochs100轮是起步线。3870张图不算多50轮可能过拟合150轮收益递减。我通常会先跑100轮看val曲线有没有收敛。imgsz训练输入分辨率640是默认值。如果你的应用场景是路口的远距离监控车辆在画面里偏小建议提到768甚至960代价是训练时间增加。batch根据显存调整。16G显存用16没问题8G显存降到8。batch越小梯度估计噪声越大训练越不稳。device0代表第一张显卡CPU训练建议直接放弃3870张图用CPU跑100轮可能要两三天。训练跑起来后实时盯着终端打出的box_loss、cls_loss、dfl_loss。正常情况三项loss曲线都在下降如果box_loss降一阵子后反弹优先怀疑学习率太大或数据里有脏标注。3.4 理解YOLOv8的损失函数调参不再靠猜YOLOv8的损失由三部分组成边界框回归损失CIoU、分类损失BCE、分布聚焦损失DFL。这三项在ultralytics的损失函数里是线性加权组合的权重默认值分别为box7.5、cls0.5、dfl1.5。训练日志里打印的box_loss是所有样本的平均不是最终的总损失所以看它的趋势比看绝对数值更重要。分类损失用的是BCE而不是交叉熵允许一个目标同时有多个类别的高置信度这对“公交也是卡车”这种语义重叠类别其实更宽容。如果训练集里卡车和公交车长得像BCE的独立性假设反而能缓解误分类。理解了这一点就不会在cls_loss居高不下时盲目加大cls权重——先检查类别标注是否自洽再动权重。3.5 训练中断续训断点续跑的命令与时机的选择训练到一半机器重启或显存溢出是常见事故不要从头再来。YOLOv8会把最新的权重、优化器状态、学习率调度器的进度自动存到runs/vehicle_count_v1/weights/last.pt。续训时指定resumeTrue即可yolo detect train resumeTrue model./runs/vehicle_count_v1/weights/last.pt注意resumeTrue时不要同时给epochs否则会覆盖原来还没跑完的轮数计数。续训后先观察两三轮loss曲线有没有继续下降如果出现断崖式回升可能是学习率状态恢复失败这种情况我一般老实从头跑。4. 从检测框到汽车计数计数逻辑与工程实现4.1 视频帧的车辆计数别把每一帧都当独立世界模型训练完输出的是每张图上的边界框。要变成“车辆计数”需要定义清楚是统计画面里同时出现的车辆数静态计数还是统计一个时间段内通过路口的车辆数流量计数。前者简单模型输出多少个框就数多少个后者必须做跨帧去重不然同一辆车在视频里出现30帧就会被数30次。跨帧去重的常见做法是加一个目标跟踪器比如ByteTrack或BoT-SORT。YOLOv8自带yolo track命令封装了跟踪器核心思路是给每个目标分配一个track_id同一辆车在多帧之间保持同一个ID。最终计数时统计不同ID的数量或者按具体业务统计进入某个区域的ID数量。from ultralytics import YOLO model YOLO(runs/vehicle_count_v1/weights/best.pt) results model.track( sourcetraffic_video.mp4, trackerbytetrack.yaml, imgsz640, persistTrue, streamTrue ) vehicle_ids set() for r in results: if r.boxes is None or r.boxes.id is None: continue for box, track_id in zip(r.boxes.xyxy, r.boxes.id): cls int(r.boxes.cls) if cls in [0, 1, 2, 4]: # car, truck, bus, tractor vehicle_ids.add(int(track_id)) print(视频中出现的车辆总数:, len(vehicle_ids))这里的关键细节是persistTrue告诉跟踪器跨帧保留之前帧的跟踪状态否则每一帧都重新分配ID去重就失效了。bytetrack.yaml是YOLOv8自带的跟踪配置在ultralytics/cfg/trackers/目录下可以用文本编辑器打开查看它的参数其中track_thresh0.5控制跟踪匹配的门槛检测置信度低于这个值的目标不会进入跟踪流程调低它漏检少但ID切换会变多调高则相反。4.2 区域计数只数进入某个区域的车辆停车场出入口、厂区道闸这类场景通常只关心“从这条线驶过的车”。区域计数的做法是设定一个进入区域用多边形或矩形框定每辆车第一次进入这个区域时计数加一离开时不加。跟纯计数相比区域计数天然地对遮挡更鲁棒——没进入区域的车即使检测断断续续也不会被计入。实现区域交并比判断时可以用shapely库的box.intersects(region)来判定检测框是否与区域相交。要注意的坑是判断区域进入的边界框锚点位置选择——用中心点还是用底边中点。车辆检测框的底边中点刚好落在车轮接地位置比中心点更能反映真实位置用中心点会造成提前半秒计数密集车流下误差被放大。from shapely.geometry import box, Polygon roi Polygon([(0.2, 0.3), (0.8, 0.3), (0.8, 0.8), (0.2, 0.8)]) for r in results: if r.boxes is None or r.boxes.id is None: continue for box_xyxy, track_id in zip(r.boxes.xyxy, r.boxes.id): x1, y1, x2, y2 box_xyxy.tolist() center_x (x1 x2) / 2 bottom_y y2 point Point(center_x, bottom_y) if roi.contains(point) and track_id not in counted_ids: counted_ids.add(track_id) total_count 14.3 单帧检测计数与实时性取舍如果你的需求是“大屏上实时显示当前画面里有多少辆车”不关心去重那直接用静态检测模型对每一帧做推理即可不用启用跟踪器省掉一大块计算开销。测试时先看单帧推理耗时在1080p视频上yolov8s在V100上大概10毫秒级在边缘设备NVIDIA Jetson Orin上约30毫秒基本能满足25帧/秒的实时性。实时性不够时有两个调优方向一是model.predict时加halfTrue用FP16半精度推理速度提升接近一倍显存占用减半mAP损失可以忽略二是把输入分辨率从640降到480速度可以再翻倍但小目标检测率会明显下滑这个分寸要根据你的摄像头安装高度和车辆在画面中的尺度来权衡。5. 车辆计数模型常见问题排查与避坑5.1 zip解压后中文文件名乱码标签路径全断现象在Windows下压缩的zip传到Linux服务器用unzip解压图像文件名变成乱码txt文件内容却正常训练时找不到图像路径直接报错。 原因Windows默认用GBK编码文件名Linux的unzip默认用UTF-8解码中文文件名就成了乱码。 解决Windows下压缩时用7-Zip选择“UTF-8”压缩选项或者Linux侧用unzip -O GBK指定编码解压。已经解压出乱码的用convmv批量转换sudo apt install convmv convmv -f GBK -t UTF-8 --notest -r ./vehicle_count/5.2 YOLO混淆矩阵总和不是1是不是训练出了问题现象训练结束后打印的混淆矩阵每一行加起来不是1有的行只有0.8多看起来“不完整”。 原因YOLOv8的混淆矩阵按行做了归一化每个真实类别召回率总和为1但显示精度只保留了两位小数背景类background_fp也被算进去了。行和列都包含背景一列四舍五入后总和不恰好等于1是正常的。另外未参与验证的类别可能完全被归入背景导致某一行数值偏低。 解决不用纠结总和是否为1看对角线数值和背景类误报率。重点关注“公交被识别成卡车”“拖拉机被识别成汽车”这类成对混淆如果这类错误挤占了整体精度的1%以上考虑合并同类或给易混类别增加样本。5.3 车道远处的小目标全部漏检现象模型在近处的车辆上表现很好但画面远处、大概只有二三十像素宽的车几乎全部漏检。 原因Mosaic数据增强在随机裁剪拼接时经常把小尺寸的车辆裁掉一半甚至完全切掉模型训练时就没见过多少“完整的小目标”。另外imgsz640在缩小整张图时远处的车可能只有十几个像素的有效特征。 解决训练时把imgsz提到960并关闭或弱化Mosaic增强# ultralytics的cfg里修改, 或训练命令加参数 mosaic: 0.5 # 50%概率使用mosaic, 原默认1.0 imgsz: 960如果目标实在太小还有一种做法是把原图切成四块分别训练相当于等效2倍分辨率训练。但推理时也要对应切图拼接检测结果复杂度上一个台阶先试imgsz960多数场景能救回来。5.4 训练时显存不足CUDA out of memory现象batch16, imgsz640训练到第二个epoch直接OOM终端报CUDA out of memory第一次跑的人会紧张。 原因YOLOv8的训练显存占用比推理高得多——反向传播需要保存中间激活张量8G显存的卡跑yolov8s时batch16确实吃紧。Mosaic增强的拼图尺寸是原始图的两倍也推高了显存峰值。 解决先降到batch8再看显存占用是否降到80%以下。还不够就把imgsz降到512或者换yolov8n。注意别把batch降得太低低于4——BN层的统计会不稳定训练容易发散如果batch必须用2或4给训练命令加batch2的同时建议关掉Mosaic增强并在ultralytics的配置里检查是否对BN做了同步。V10016G显存的标配是yolov8s batch32 imgsz640这是比较舒服的组合。5.5 训练loss正常但预测几乎不框出车现象训练曲线好看val的mAP也不差但部署到实际视频里90%的车辆检测不出来偶尔框住一个还是错的。 原因数据集和真实场景分布差异典型的是训练图像来自白天、晴天、固定角度监控测试却拿夜间/雨天/逆行车道的视频来跑。还有一种情况是摄像头俯视角不同——数据集多是高位监控视角你的摄像头装在车头高度车型特征完全不同。 解决先收集一小段目标场景的视频几十帧就够标注后做微调用yolo detect train加载已有权重继续训练20到30轮。这是最有效的补救手段比换模型、调阈值都管用。如果新场景只有夜间考虑用图像增强工具先做一个简单的亮度拉伸把图像整体调亮后再推理能解一部分问题但不如微调彻底。6. 验证计数模型该看哪几个数字mAP之外还验证什么模型训练结束后很多人只看终端打印的mAP50这个数字代表框和类别都正确匹配的交并比阈值0.5下的平均精度但它不能告诉你计数业务是否可靠。我习惯输出三类验证结果mAP指标、混淆矩阵、计数误差率。mAP50-95这个指标比mAP50更严苛它从0.5到0.95每隔0.05算一次然后取平均对边界框的位置精度非常敏感。做计数场景时mAP50高于0.85足够用但如果你发现mAP50-95低于0.5说明框的位置时好时坏在密集车流下会造成同一辆车ID抖动——这一帧框住车头、下一帧框住车身去重逻辑容易把一辆车数成两辆。我在流量计数项目里会另外统计一个“计数误差率”拿着模型跑一段人工数过车辆的2分钟视频比较模型计数的总数和人工计数的差值。模型输出310辆、人工数出312辆误差率0.6%这个数字才能决定项目能不能交付。mAP反映模型能力的上限计数误差反映业务下限两者要分开看。最后分享一个调优技巧验证集里手动挑50张“困难样本”——车辆重叠遮挡、光照剧烈变化、雨天反光的场景单独建一个hard_val目录每次训练完成后都跑一遍这个困难验证集。因为常规验证集的分数会被好样本拉高困难集的mAP才真实反映部署环境的边际情况。如果困难集mAP低于0.5主模型指标再漂亮也要再调一轮数据增强参数或补样本。这是我踩过多次坑之后养成的习惯每次模型交付前必须过这一关。希望帮到你。本文还有配套的精品资源点击获取