ARTICLE DETAIL

资讯详情

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

YOLO车辆检测与计数实战:从数据集校验到跟踪去重全流程

YOLO车辆检测与计数实战:从数据集校验到跟踪去重全流程 简介这是一套面向yolo系列算法的车辆检测与计数数据集包含1304张带标注图像覆盖汽车、摩托车、公共汽车、卡车四类目标适合目标检测算法训练、验证与测试。压缩包内共2000个文件其中1089个xml文件为voc格式标注911个txt文件为yolo格式标注两种标签分别存放配合data.yaml配置文件可直接用于yolov5、yolov7、yolov8、yolov9、yolov10及yolo11等主流模型训练。数据集已预先划分好训练集和验证集无需额外整理yolo标注以归一化中心坐标和宽高表示voc标注则便于在LabelImg等工具中查看修改兼顾不同使用习惯。资源包整体大小仅44.02MB体量精简但类别均衡适合入门练习与快速迭代。目前已有88人学习下载对需要车辆检测计数场景的开发者而言是一份开箱即用的高质量数据基础。1. 直接用现成车辆数据集训练 YOLO比你自己打标快十倍很多人拿到yolo算法-车辆检测和计数数据集-1304张图像带标签-汽车-摩托车-公共汽车-卡车.zip这种资源的第一反应是图太少了才1304张能用我直接说结论能用而且这类数据集在真实项目里出现的频率远比你想的高。1304张图对应的是四个固定类别汽车、摩托车、公共汽车、卡车类别跨度足够大但不算碎作为交通流量统计、停车位管理、高速公路监控这类场景的起步模型完全够用。真正让你翻车的往往不是图少而是你没把数据校验、标签清洗、类不均衡这三件事做好就直接开训。这套数据集的价值在于它把YOLO训练里最耗时的两个环节——图像采集和标注——替你做了。你拿到手的是已经整理好的图像和YOLO格式标签剩下要做的就是把目录归位、写yaml配置、跑训练、调置信度和NMS参数最后接一个带跟踪的计数逻辑。本文全程按YOLOv8的流程来讲YOLOv5和v11在命令上基本一致只有少量参数名差异对照着改就能跑通。2. 先拆数据集结构和标签格式训练前必须做的一次性体检2.1 四类目标的标签体系与YOLO格式校验YOLO格式的标签不是Pascal VOC那种XML也不是COCO那种JSON而是每个图像对应一个同名txt文件。每一行对应一个目标框格式是固定的五列class_id x_center y_center width height。注意这里全部是归一化坐标x_center和y_center用框中心点除以图像宽高width和height用框宽高除以图像宽高取值范围在0到1之间。一旦出现大于1的坐标说明标注时图像尺寸和训练时不一致典型翻车点。我一般拿到数据集后的第一件事是写个快速校验脚本检查标签文件和图片文件是否一一对应、有没有空标签、有没有坐标越界而不是急着配环境。import os from pathlib import Path img_dir Path(images) label_dir Path(labels) class_names [car, motorcycle, bus, truck] # 按数据集给定顺序 img_files list(img_dir.glob(*.jpg)) list(img_dir.glob(*.png)) label_files list(label_dir.glob(*.txt)) print(f图像数量: {len(img_files)}, 标签数量: {len(label_files)}) # 1. 检查文件对应关系 missing 0 for img in img_files: label label_dir / (img.stem .txt) if not label.exists(): missing 1 print(f[缺失标签] {img.name}) print(f缺标签文件数: {missing}) # 2. 检查标签内容是否合法 bad_lines 0 for label in label_files: with open(label, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_lines 1 print(f[格式错误] {label.name}: {line.strip()}) continue cls int(parts[0]) x, y, w, h map(float, parts[1:]) if cls len(class_names): bad_lines 1 print(f[类别越界] {label.name}: class{cls}) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_lines 1 print(f[坐标越界] {label.name}: {line.strip()}) print(f非法标签行数: {bad_lines})上面这段代码做的事很朴素先确认图片和标签数量对得上再逐行检查标签的五列格式、类别ID是否在四类范围内、坐标是否落在归一化区间。任何一个检查点报错都值得停下来处理因为YOLO训练时遇到越界坐标会直接报错或产生极其离谱的loss曲线。在热门模型仓库里跑训练之前做这步体检能把后面调试的时间省掉一大半。这里还有个容易忽略的点标签里的类别顺序必须和后面yaml配置文件里的names顺序完全一致一旦错位会出现模型把卡车识别成汽车但标签显示正确的诡异现象因为模型只学数字ID不学语义。2.2 目录归一化和数据集划分的固定套路YOLO训练标准目录结构有两种流派一种是把所有图片放一个文件夹、所有标签放一个文件夹靠train/val的txt列表文件区分另一种是train和val各自带images和labels子目录。YOLOv8官方推荐第二种结构清晰且不用维护列表文件但我个人更建议直接按YOLOv8的默认约定来组织这样后续用Ultralytics的API训练时不用写任何自定义数据集加载逻辑。目录结构如下dataset/ ├── images/ │ ├── train/ # 约1040张 │ └── val/ # 约260张 ├── labels/ │ ├── train/ │ └── val/ └── data.yaml将原目录按8:2划分train/val代码逻辑如下import random import shutil from pathlib import Path random.seed(42) src_imgs Path(images) src_labels Path(labels) dst Path(dataset) for split in [train, val]: (dst / images / split).mkdir(parentsTrue, exist_okTrue) (dst / labels / split).mkdir(parentsTrue, exist_okTrue) all_imgs sorted(src_imgs.glob(*.jpg)) sorted(src_imgs.glob(*.png)) random.shuffle(all_imgs) val_count int(len(all_imgs) * 0.2) for i, img in enumerate(all_imgs): split val if i val_count else train label src_labels / (img.stem .txt) shutil.copy(img, dst / images / split / img.name) shutil.copy(label, dst / labels / split / label.name) print(ftrain图像: {len(all_imgs) - val_count}, val图像: {val_count})注意这段代码做了三件事固定随机种子保证每次划分结果一致按8:2比例划分而不是用默认的9:1因为车辆检测场景里验证集太小会导致mAP波动大看不出真实效果用copy而不是move保留原始数据做备份。数据划分看着简单但有个坑——如果图像之间是连续视频帧截取的直接随机划分会让同一个车辆在train和val里同时出现导致验证指标虚高。如果你发现数据集文件名是连续的帧号最好先按视频片段分组再划分但这种按截帧方式构建的车辆数据集本身就可能存在这个问题建议训练和评估时多关注类别级别的AP而不是只看整体mAP。2.3 data.yaml配置和类别不平衡的初步应对# data.yaml path: /absolute/path/to/dataset train: images/train val: images/val names: 0: car 1: motorcycle 2: bus 3: truck写yaml时最容易犯的错误是path用了相对路径。YOLOv8在训练时会基于当前工作目录解析path你换了终端或换了IDE路径就漂了。建议直接写绝对路径或者把path那行改成数据集根目录的绝对路径train和val用相对路径这样最稳。另外提一句类别不平衡车辆数据集里car的样本量几乎一定是碾压motorcycle的这会导致摩托车这一类的AP明显低于汽车——当这类数据集标注不平衡时YOLO训练会自动做类别采样但效果有限首先是检查各类别的目标框数量。如果差距超过10倍优先考虑给摩托车类做简单的Mosaic增强或加大训练轮数让模型多见到小众类别的样本。3. 训练配置与实操从环境搭建到loss曲线判读3.1 训练环境安装和显存预估训练YOLOv8的首要问题是环境。很多人卡在PyTorch和CUDA版本不匹配上面。常见做法是先用Anaconda建一个干净的虚拟环境然后通过PyTorch官网给出的与当前CUDA版本匹配的安装命令来装而不是自己随手pip install torch。如果机器没有独显纯CPU也能跑但训练速度会让你怀疑人生——1304张图、640分辨率、30个epochCPU跑一次大概要几小时到十几小时不等有GPU哪怕是6GB显存的GTX 1660或RTX 3060会舒服非常多几十分钟到两小时就能完成一轮完整训练。装好的环境里需要几个核心组件Python 3.9以上、PyTorch 2.x、Ultralytics库。Ultralytics的安装是pip一条命令的事它会自动拉取依赖。为了确保基础环境可用我建议先跑一个最小推理脚本验证CUDA和模型加载正常yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg如果这条命令能正常输出检测结果说明环境没问题。然后把你自己的数据集路径填好进入训练阶段。这里说一句选模型规模的逻辑yolov8n是nano版只有约300万参数速度快但精度一般yolov8s约1100万参数是平衡之选1304张图的数据量撑不起yolov8m以上规模的模型强上大模型的结果就是过拟合——训练集上loss很低验证集mAP反而下降。我的建议是先用yolov8s起步或者从yolov8n开始快速验证效果。3.2 训练命令与关键参数逐个拆解训练YOLOv8数据集对显卡的适配优化比较成熟大部分超参数直接用默认值就能得到合理结果。我一般这样跑yolo detect train \ data/path/to/dataset/data.yaml \ modelyolov8s.pt \ epochs60 \ imgsz640 \ batch16 \ lr00.01 \ patience15 \ device0 \ projectvehicle_counter \ nameexp1逐项解释参数modelyolov8s.pt表示在ImageNet预训练权重基础上做迁移学习这对小数据集至关重要epochs60是训练总轮数车辆目标不算难60轮够收敛imgsz640保持默认因为标签归一化不依赖尺寸但模型训练时的输入分辨率会影响小目标检测——如果你发现在画面远处的小车总是漏检可以调到800代价是训练和推理变慢batch16在6GB显存下YOLOv8s刚好能放得下不够就降到8或4patience15表示连续15轮验证集mAP不涨就提前停止这是防止过拟合和节省时间的兜底device0是单卡训练没有GPU就改成devicecpu。训练过程中需要盯的东西不是loss曲线本身而是验证集的mAP50和mAP50-95这两个指标在每轮结束后打印出来的数值。mAP50是指IoU阈值为0.5时的平均精度车辆检测场景下经验值mAP50能达到0.85以上说明模型可用0.9以上说明效果很好了。如果你的mAP50一直在0.5以下徘徊先别调参回看数据体检那一步有没有漏掉标签错位或坐标越界的问题。3.3 损失函数观察和过拟合的早期信号YOLOv8的loss由三部分组成box_loss边界框回归损失、cls_loss分类损失、dfl_loss分布焦点损失负责边界框的精细调整。YOLOv8的损失函数设计得比较稳健数据没问题时三个loss会同步下降。你最需要警惕的信号是train loss一路下降但val loss先降后升这就是过拟合的典型模式——明确了模型把训练图片的背景和噪声当成了特征。发生这种情况的处理方式不是马上换模型或加数据而是先看是不是训练轮数太多因为patience没有触发说明验证集指标还在缓慢上升然后考虑增强手段把Mosaic关闭或降低概率过拟合严重时Mosaic反而让模型学不到稳定的特征。Ultralytics里调整增强参数的方式是在训练命令中加上参数覆盖比如关闭Mosaicyolo detect train \ data/path/to/dataset/data.yaml \ modelyolov8s.pt \ epochs60 \ batch16 \ mosaic0.5mosaic0.5表示一半概率使用Mosaic增强默认是1.0即全部使用。小数据集1000多张在Mosaic全开的情况下模型会看到大量拼接出来的假图训练初期收敛快后期反而限制精度降到0.5左右通常更稳。另外还有个实用技巧——把训练过程中保存的last.pt和best.pt都留着过拟合时best.pt往往在中间轮数比final.pt靠谱得多。4. 推理检测与计数方案从单帧结果到跨帧去重4.1 单张图像的检测结果提取训练完成后模型文件在project/name/weights/目录下best.pt就是要用的那个。先用它对单张测试图片做推理检查检测框是否正确落在车辆上from ultralytics import YOLO model YOLO(runs/vehicle_counter/exp1/weights/best.pt) results model.predict( sourcetest_01.jpg, conf0.35, iou0.5, imgsz640, verboseTrue ) boxes results[0].boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f类别{model.names[cls_id]}, 置信度{conf:.2f}, 框{xyxy})conf0.35表示只保留置信度大于0.35的目标框iou0.5是NMS去重的IoU阈值。这两个参数在车辆检测场景里影响很大conf设太高会漏检远处的小车设太低会出现大量误检的树上电线杆iou设太高一个车身可能出两个互相重叠的检测框计数时被算成两辆车。我一般先按conf0.25、iou0.5跑一遍用测试图像目检结果再根据误检和漏检的比例调。置信度门限在YOLO部署调优里是出场率最高的一个环节后面会专门展开。4.2 单帧图像上的静态计数逻辑如果只是统计一张图上有多少辆车直接把检测结果按类别累加即可from collections import Counter counts Counter() for box in boxes: cls_id int(box.cls[0]) counts[model.names[cls_id]] 1 total sum(counts.values()) print(f车辆总数: {total}) for name, cnt in counts.items(): print(f{name}: {cnt})这是最朴素的计数方式适合停车场照片、交通路口单帧截图这类场景。但真实监控场景里车辆是运动的同一辆车在相邻帧里会反复出现你每帧累加就会把一辆车数成几十辆。在热词里反复出现的重复计数指的就是这个问题。解决方案很明确跨帧计数必须引入跟踪算法给每辆车分配一个稳定的ID只对新增ID计数。4.3 基于ByteTrack的实时视频计数解决重复计数问题在YOLO生态里YOLOv8官方集成了ByteTrack跟踪器你可以直接用它做视频计数而不用自己实现卡尔曼滤波。我在实际项目里一般不在代码里手动实现复杂的跟踪逻辑而是直接借助Ultralytics的跟踪接口。import cv2 from ultralytics import YOLO from collections import Counter model YOLO(runs/vehicle_counter/exp1/weights/best.pt) cap cv2.VideoCapture(traffic_01.mp4) fps int(cap.get(cv2.CAP_PROP_FPS)) total_in Counter() # 累计出现的车辆ID计数 active_ids set() # 当前画面内的目标ID while cap.isOpened(): ret, frame cap.read() if not ret: break results model.track( frame, persistTrue, # 跨帧保持跟踪ID conf0.35, iou0.5, trackerbytetrack.yaml ) if results[0].boxes is not None and results[0].boxes.id is not None: boxes results[0].boxes for box in boxes: track_id int(box.id[0]) cls_id int(box.cls[0]) active_ids.add(track_id) total_in[model.names[cls_id]] sum( 1 for _ in [0] # 仅示例实际按新ID判断 ) cap.release() cv2.destroyAllWindows()这段代码大致展示了跟踪ID的获取方式但实际计数时要小心一个细节track()返回的结果里同一个跟踪ID会在每一帧重复出现不能直接累加。正确姿势是维护一个已见ID集合每帧检查当前出现的track_id是否第一次出现只有新ID才计入总数。上面的示例代码为了展示跟踪接口没有做好这一步你真正落地时要改成seen_ids set() ... for box in boxes: track_id int(box.id[0]) if track_id not in seen_ids: seen_ids.add(track_id) cls_id int(box.cls[0]) total_in[model.names[cls_id]] 1这才是正确的去重计数逻辑。ByteTrack的bytetrack.yaml配置文件里有两个关键参数track_thresh检测置信度阈值低于此值的检测框不参与跟踪和match_thresh匹配阈值目标与已有轨迹的相似度低于此值时视为新目标。在车辆密集场景下match_thresh设太低会出现ID频繁切换一辆车被分配多个ID导致计数虚高设太高会导致遮挡后目标跟丢。我一般是0.8起步密集场景调到0.85到0.9之间。4.4 应用场景车辆检测计数系统在交通场景中的边界在哪把检测加跟踪串起来你就能搭出一个基础的交通流量统计系统——入口计数、出口计数、按类别分时段统计车流量。这套方案的局限也很明确摄像头视角倾斜严重时车辆互相遮挡跟踪ID会频繁切换导致计数不准夜间场景红外补光下检测框抖动剧烈可以在推理前对视频帧做一次直方图均衡化来缓解。尽早给项目设定一个可量化的验收指标比如计数准确率在90%以上然后在测试视频上人工数一遍真实车辆数用这个数来衡量模型和跟踪参数的组合效果。5. 训练与部署避坑清单五个最容易翻车的点5.1 标签顺序错位导致模型学到的类别和实际不一致现象训练正常loss正常但推理时模型把摩托车识别成汽车汽车识别成卡车。原因data.yaml里names的类别顺序和标签txt里的class_id对不上。比如数据集原本的类别顺序是car、motorcycle、bus、truck你在yaml里写成了car、bus、truck、motorcycle模型学习时class_id1代表bus但标签里class_id1是motorcycle全乱了。这种错位在AI数据集准备环节极易出现尤其是多个来源拼在一起时。解决训练前用校验脚本打印每个类别ID对应的标签行数和数据集配套说明中的类别顺序对照一遍再目检几张已经标注的图片确认框和类别一致再开始训练。5.2 验证集指标高但实战检测差先怀疑数据划分泄漏现象训练结束显示mAP50有0.92但放到实际监控视频里检测效果很一般漏检频繁。原因如果数据集的图像是连续视频帧截取的随机划分时同一个车辆会同时出现在训练集和验证集里模型等于提前见过答案验证集指标虚高真实场景里车辆角度、距离一变就露馅。解决检查图像文件名是否有序号特征如果有按视频片段时间段划分而不是随机划分。如果数据集本身就是一堆随机抓拍图像那这个问题的影响不大。5.3 车辆目标小而密集时漏检严重调整imgsz和anchor相关参数现象距离摄像头远的车辆完全检测不到近处的车正常。原因输入分辨率imgsz640时远处的车辆目标可能只有十几个像素YOLO的特征图最小尺度是输入尺寸的1/32也就是20x20的网格小目标在这种尺度下难以被有效表达。解决把imgsz提升到960甚至1280检测速度会变慢但小目标召回率明显上升。另外可考虑对图像做切片推理但工程复杂度偏高优先调imgsz。需要强调imgsz要在训练和推理时保持一致否则检测框位置会漂移。5.4 batch大小设置不合理导致显存溢出或训练不稳定现象显存不够直接OOM崩溃或者batch太大训练早期loss剧烈震荡不收敛。原因6GB显卡跑yolov8s加640分辨率batch16刚好是临界点。不同机器显存差异很大不能照抄网上命令。解决OOM就把batch降到8或4同时可以把epochs适当增加来补偿每轮看到的数据量减少。如果是loss震荡降低初始学习率lr0从0.01到0.005。判断标准很简单——训练前几轮loss如果不降反升立刻停止调小学习率而不是硬等。5.5 视频流计数重复或漏计先检查tracker参数而不是模型现象车流量统计结果明显偏大每辆车平均被数了2到3次。原因跟踪配置里match_thresh太低跟踪器经常把同一个目标判定为新的跟踪对象产生大量ID switch。这种情况下不是检测模型的问题因为单帧检测框是准的跨帧关联出了问题。解决调高match_thresh到0.85左右同时检查是否有跳帧处理——如果视频本身只有10到15帧每秒车辆位移大跟踪更容易断。优先保证输入视频帧率稳定避免稀疏帧导致目标位移过大导致匹配失败。6. 进阶调优置信度门限的精调技巧和类别合并策略置信度门限是把这个数据集调到实用状态最关键的一个旋钮它直接决定误检和漏检的取舍。我的做法是先跑一批测试图像把每张图的检测结果导成JSON统计所有检测框的置信度分布然后观察误检框和漏检框分别落在哪个置信度区间。经验上误检的置信度集中在0.2到0.35真车的置信度通常在0.5以上。如果计算资源允许我建议做一次grid search在0.2到0.6之间取多个置信度门限值跑测试集计算每个门限下的精确率和召回率选择F1分数最高的那个门限。有时候精确率和召回率永远无法同时满意要由业务方决定更偏向哪一边——比如闯红灯抓拍漏检比误检严重得多门限就要压低。对于类别合并如果你的业务只关心机动车数量可以把car、bus、truck三类合并只保留motorcycle单独一类。合并的收益有两层一是类别减少后每类的正样本量增加小数据集的分类难度下降整体AP会提升二是二分类的置信度更稳定计数误差更小。做法很简单把标签txt里的car、bus、truck的class_id改成同一个数字。最后还有模型蒸馏的技巧值得尝试用不带跟踪的检测模型训练好后再把大模型如yolov8m在小数据集上微调后的输出作为软标签来训练yolov8s这个技巧在车辆检测里比较适合忙碌时候抽空做一轮。更简单的做法是把它当作训练和推理分离的pipeline——训练用yolov8m推理用yolov8s精度损失可控但延迟明显下降。我自己在类似项目里习惯把best.pt和last.pt对照测试视频各跑一遍有时候last.pt在某个类别上反而更准不要迷信best.pt的名字。这算是用这套车辆检测计数数据集一路走下来的一个经验提前检查这几个点能省下不少重新训练的时间希望帮到你。本文还有配套的精品资源点击获取
返回列表