YOLO车辆检测与计数实战:1304张带标签图像训练及避坑指南
简介本资源为面向YOLO系列目标检测算法的车辆检测与计数数据集适合计算机视觉入门与进阶开发者、自动驾驶或智能交通方向的学生和研究人员使用可直接用于模型训练、验证与测试。压缩包共2000个文件约44.02MB包含1304张带标签图像标签同时提供YOLO格式的txt文件与VOC格式的xml文件分别存放于两个文件夹并附有data.yaml配置文件兼容yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流版本。数据集已划分完毕涵盖汽车、摩托车、公共汽车、卡车四类目标标注规范开箱即可投入训练。目前已有88人学习下载读者可借此快速搭建车辆检测与计数实验流程省去数据采集与标注成本将精力集中于模型调优与算法验证。1. 从 1304 张带标签图像说起车辆检测和计数到底难在哪拿到一个「yolo算法-车辆检测和计数数据集-1304张图像带标签」的压缩包很多人的第一反应是解压、找data.yaml、直接yolo train。但真正跑过车辆计数项目的人都知道检测框画得漂亮和计数结果对得上中间隔着一整套工程细节。1304 张图像、四类目标汽车、摩托车、公共汽车、卡车这个规模不算大却足够暴露小数据集训练 YOLO 的典型问题类别不均衡、密集遮挡、跨帧重复计数、置信度阈值一调计数就跳变。这个标题对应的场景非常具体城市道路或卡口的车辆检测输出不只是框还要按类别统计数量。它适合三类人——想用 YOLO 做第一个完整检测项目的入门者、手里有类似数据集需要快速验证的算法工程师、以及要把检测结果接到计数逻辑里的部署开发者。数据集本身是起点不是终点。真正决定成败的是标签格式对不对、类别映射稳不稳、计数逻辑放在检测之后还是跟踪之后。下面按「先立住原理、再动手复现、最后避坑」的顺序把这个方案拆开讲清楚。2. 车辆检测数据集与 YOLO 的匹配逻辑先搞懂再动手2.1 1304 张图像带标签够不够训练一个可用的车辆检测模型先说结论1304 张图像训练一个四类车辆检测模型在迁移学习的前提下是够用的但前提是标签质量过关、类别分布别太离谱。YOLO 系列从 v5 开始就依赖预训练权重做特征迁移你不需要从零学「什么是车轮」只需要让模型适配你的场景分布。常见做法是加载 COCO 预训练权重冻结主干前几层用较小学习率微调。判断数据集够不够看三个指标而不是看总数。第一每个类别的实例数汽车通常最多摩托车和卡车可能只有几十个少于 100 个实例的类别要警惕。第二图像分辨率分布如果卡口图和手机拍摄图混在一起短边差异过大会让imgsz参数很难选。第三标注框的宽高比分布公共汽车和卡车偏长条摩托车偏小如果标注框大量贴边或宽高为 0训练时损失会异常。我一般会先跑一个统计脚本把每类实例数、每张图目标数、框的宽高分布打出来再决定要不要做数据增强或类别重采样。这一步花十分钟能省掉后面几小时的无效训练。2.2 标签格式转换从 VOC/COCO 到 YOLO txt 的落地脚本数据集压缩包里常见两种标签VOC 的 XML 或 COCO 的 JSON。YOLO 训练需要的是每张图对应一个 txt每行class_id x_center y_center width height且全部归一化到 0~1。下面这个脚本处理 VOC 转 YOLO同时生成类别映射文件。import os import xml.etree.ElementTree as ET # 类别顺序必须和 data.yaml 里的 names 完全一致 CLASSES [car, motorcycle, bus, truck] CLASS_MAP {name: idx for idx, name in enumerate(CLASSES)} def convert_voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() # 用图片实际尺寸做归一化不要用 XML 里的 size 字段硬编码 img_name root.find(filename).text img_path os.path.join(img_dir, img_name) from PIL import Image with Image.open(img_path) as im: w, h im.size lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_MAP: continue # 忽略不在四类里的目标避免类别错位 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 裁剪越界框防止归一化后出现负值或大于 1 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) if xmax xmin or ymax ymin: continue xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{CLASS_MAP[cls_name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_file os.path.join(out_dir, os.path.splitext(xml_file)[0] .txt) with open(out_file, w) as f: f.write(\n.join(lines)) convert_voc_to_yolo(annotations_xml, images, labels)逻辑说明脚本先建立类别名到索引的映射这个映射必须和后续data.yaml的names顺序一致否则训练出来的类别会整体错位。归一化用的是图片实际尺寸不是 XML 里声明的尺寸因为有些数据集这两者不一致。越界框裁剪和零面积框过滤是必须的YOLO 对负坐标和零宽高框的容忍度很低容易在训练早期就出现 NaN 损失。参数说明CLASSES列表顺序决定class_id改顺序等于改标签含义xc/yc/bw/bh保留六位小数足够再高精度对训练无增益如果数据集是 COCO JSON把解析部分换成pycocotools读取bbox的[x, y, w, h]再转中心点格式即可。2.3 data.yaml 与训练配置四个必调参数标签转好后写data.yaml。这个文件看着简单但路径写错、类别数写错是新手最常见的翻车点。path: /workspace/vehicle_dataset train: images/train val: images/val nc: 4 names: [car, motorcycle, bus, truck]path是数据集根目录train和val是相对路径。nc必须等于names长度多一个少一个都会在训练启动时报错或静默错位。划分验证集时1304 张图建议按 8:2 分且要保证每个类别在验证集里都有出现否则验证指标会失真。训练命令以 YOLOv8 为例yolo detect train \ data/workspace/vehicle_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/vehicle \ nameexp1四个必调参数imgsz决定输入分辨率车辆目标偏小就上 640 或 768别盲目上 1280 显存吃不消batch受显存限制16 是 8G 显存的稳妥值lr0初始学习率微调预训练模型用 0.01 比默认 0.01 更稳若损失震荡就降到 0.001patience早停轮数小数据集容易过拟合20 轮没提升就停能省时间。model选 n 还是 s取决于你的部署硬件边缘设备优先 n。3. 车辆计数怎么做从检测框到数量统计的三种路径3.1 单帧计数置信度阈值和 NMS 的联动影响单帧计数是最简单的场景一张图或一帧视频统计每类框的数量。但直接数框会踩两个坑。第一置信度阈值conf设太低误检框混进来计数虚高设太高遮挡车辆漏检计数偏低。第二NMS 的iou阈值影响重叠框的合并密集车流里两辆车挨得近iou设太小会把其中一辆当重复框删掉。我一般先用验证集跑一组阈值扫描看conf从 0.2 到 0.6、iou从 0.4 到 0.7 的计数误差曲线选误差最小的组合。常见起点是conf0.35, iou0.5但这不是万能值必须用你自己的验证集校准。下面这段代码演示推理和按类计数from ultralytics import YOLO from collections import Counter model YOLO(runs/vehicle/exp1/weights/best.pt) names model.names # {0: car, 1: motorcycle, 2: bus, 3: truck} results model.predict(test.jpg, conf0.35, iou0.5, imgsz640) counter Counter() for r in results: for cls_id in r.boxes.cls.tolist(): counter[names[int(cls_id)]] 1 print(dict(counter))逻辑说明model.names直接来自训练时的类别映射避免手写类别名对不上。conf和iou在predict里传入和训练参数独立可以随时调。计数用Counter按类别累加输出如{car: 12, bus: 2}。参数说明conf是检测框保留的最低置信度低于它的框直接丢弃iou是 NMS 的重叠阈值两个框 IoU 超过它就只保留置信度高的那个。密集场景把iou提到 0.6~0.7稀疏场景 0.4~0.5 即可。3.2 视频计数为什么必须引入跟踪ByteTrack 怎么接视频里逐帧计数会重复统计同一辆车。一辆车在画面里出现 30 帧单帧计数就多算 29 次。解决办法是引入多目标跟踪给每辆车分配稳定 ID只在 ID 首次出现时计数。YOLOv8 生态里最省事的是 ByteTrackmodel.track直接支持。from ultralytics import YOLO model YOLO(runs/vehicle/exp1/weights/best.pt) seen_ids set() class_count {} # persistTrue 让跟踪器在帧之间保持状态 for result in model.track(sourcetraffic.mp4, conf0.35, iou0.5, trackerbytetrack.yaml, persistTrue, streamTrue): if result.boxes.id is None: continue ids result.boxes.id.int().tolist() clss result.boxes.cls.int().tolist() for track_id, cls_id in zip(ids, clss): if track_id not in seen_ids: seen_ids.add(track_id) name model.names[cls_id] class_count[name] class_count.get(name, 0) 1 print(class_count)逻辑说明persistTrue是关键它让跟踪器跨帧维护轨迹否则每帧都重新初始化ID 不稳定。seen_ids记录已经计过数的轨迹 ID新 ID 才累加。streamTrue逐帧返回避免一次性加载整个视频占内存。参数说明tracker指定跟踪配置文件ByteTrack 的默认参数对车辆场景基本够用如果车辆频繁被遮挡后重新出现ID 会切换导致重复计数这时需要调跟踪器的track_buffer或换 BoT-SORT。计数逻辑放在跟踪之后是视频计数的基本原则别在检测层直接数。3.3 计数结果校验用混淆矩阵和计数误差定位问题训练完看confusion_matrix.png能发现类别混淆比如卡车被识别成公共汽车。但计数场景还要额外做一件事拿一段有真值计数的视频对比模型输出和人工计数算每类的绝对误差和相对误差。如果汽车误差小于 5% 但摩托车误差超过 30%说明摩托车样本太少或太小需要针对性补充数据或调imgsz。混淆矩阵总和看起来不唯一是常见现象因为不同置信度阈值下矩阵会变别纠结单张图要看归一化后的矩阵和各类的误检方向。校验时固定conf和iou记录每次调整后的计数误差形成自己的调参记录。4. 避坑与排查小数据集训练车辆检测的五个血泪教训4.1 损失出现 NaN 或训练中途崩溃现象训练几个 epoch 后损失变成nan或直接报 CUDA 错误退出。原因通常是标签里有零宽高框、坐标越界或者学习率过高导致梯度爆炸。解决先用 2.2 的脚本过滤非法框确认所有坐标在 0~1 之间把lr0降到 0.001 再试如果用了混合精度关掉amp看是否恢复。BN 崩溃在小 batch 下也常见把batch提到 16 以上或改用bn冻结策略。4.2 验证集指标很好实际计数却差很多现象mAP 到 0.8但视频计数误差 20% 以上。原因多半是验证集和实际场景分布不一致或者计数逻辑没接跟踪。解决验证集要覆盖实际部署的视角、光照和密度视频计数必须用 3.2 的跟踪方案单帧计数只适合静态图。另外检查conf是否在验证和推理时一致很多人验证用默认 0.25推理用 0.5结果对不上。4.3 某类目标几乎检测不到现象汽车检测正常摩托车和卡车漏检严重。原因类别实例数太少模型偏向多数类。解决统计每类实例数对少于 200 的类别做过采样或复制粘贴增强训练时用cls损失权重调整或改用 focal loss 思路的配置imgsz提到 768 让小目标占更多像素。别指望单靠增加 epoch 解决类别不均衡。4.4 推理速度在边缘设备上不达标现象PC 上 30 FPS部署到 RK3588 或树莓派只剩 3 FPS。原因模型选太大或没做量化。解决边缘部署优先选yolov8n导出 ONNX 或 RKNN 时做 INT8 量化imgsz从 640 降到 416 能显著提速代价是小目标精度下降需要重新校准conf。V100 上跑大模型和边缘设备是两回事别用服务器指标预估端侧。4.5 计数结果随视频播放波动现象同一段视频跑两次计数差几个。原因跟踪器 ID 分配有随机性或conf在边界附近抖动。解决固定随机种子推理时设deterministicTrue对计数结果做滑动窗口平滑或要求轨迹连续出现 N 帧才计数。计数不是精确科学工程上接受小幅波动但要把波动控制在可解释范围内。5. 进阶技巧用计数误差反推模型短板并做定向优化训练指标和业务指标之间永远有 gap车辆计数项目里这个 gap 尤其明显。我的习惯是先把计数误差拆成三类漏检、误检、ID 切换。漏检看召回率误检看精确率ID 切换看跟踪器的track_buffer和匹配阈值。拆开之后优化方向就清楚了。如果漏检集中在远处小目标把imgsz从 640 提到 768同时用 SAHI 切片推理对大图分块检测再合并。如果误检集中在广告牌或阴影补充这类负样本或在推理后加一个基于框宽高比的过滤规则比如宽高比超过 5:1 的框大概率不是车。如果 ID 切换频繁把 ByteTrack 换成 BoT-SORT或调大track_buffer让轨迹在遮挡后保留更久。验证优化是否有效别只看 mAP要建一个小的计数基准集选 10 段短视频人工数出每类真值每次改动后跑一遍算误差。这个基准集不用大但要固定否则每次换测试集就没法比较。我一般会把这个基准集和对应的真值存成 CSV跑完推理自动对比省得每次手动数。还有一个容易被忽略的点类别映射的稳定性。如果你中途改了data.yaml里names的顺序之前训练的权重就废了必须重训。所以定类别顺序要慎重car、motorcycle、bus、truck 这个顺序一旦定了就别动。导出 ONNX 或 RKNN 时类别名可能丢失要在后处理代码里硬编码映射别依赖模型文件里的元数据。最后说一个部署习惯推理服务启动时先跑一张已知结果的测试图确认计数输出和预期一致再接入视频流。这个自检步骤能挡住大部分环境配置问题比如模型加载错、类别映射错、预处理尺寸错。我踩过最冤的一次坑是预处理用了 BGR 而训练用 RGB检测框全偏计数完全不可用排查了两小时才发现是通道顺序。希望帮到你。本文还有配套的精品资源点击获取