ARTICLE DETAIL

资讯详情

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

YOLOv8车辆检测实战:1793张三类别数据集训练与踩坑全记录

YOLOv8车辆检测实战:1793张三类别数据集训练与踩坑全记录 简介用于YOLO系列模型训练的三类别车辆检测数据集收录1793张车辆实拍图像覆盖car汽车、bus公交车、truck卡车三类目标适合计算机视觉初学者以及自动驾驶、交通监控等场景的开发者用于模型训练与效果验证。压缩包大小542.36MB共5380个文件包含1793张jpg图像、1793个xml标注及1794个txt标签同时提供VOC与YOLO两种通用标注格式并附classes.txt类别清单目录结构清晰可直接接入主流训练流程。目前已有349人学习下载。图像目标清晰、场景多样既可作为多类别车辆检测的基准训练集也便于研究者进行迁移学习、超参数调优与模型精度对比实验是快速搭建车辆检测项目的实用基础数据。1. 1793张三类别车辆数据够不够喂饱一个YOLO检测器第一次拿到“YOLO车辆检测三类别数据集 1793张car-detect-dataset三种类型”时我的第一反应是“这么点数据能干嘛”。但实际跑下来这个量级在车辆检测这种目标外形规整、类间差异明显的任务上已经足够让 YOLO 新手完整走通从训练到评估的流程也能在固定场景下得到一个可用的检测模型。这个数据集解决的是地下车库出入口、高速卡口、停车场监控这类相对固定的视野下识别三类车辆目标的需求。适合刚接触 YOLO、不想从爬图标注开始又希望尽快看到 mAP 数字的学生、毕业设计作者和做产品原型的工程师。它不适合零基础直接部署到开放道路但作为跑通流程、做算法验证和基线对比1793 张的规模并不寒酸。2. 把数据集拆开看目录结构、标注格式与三类目标怎么对应2.1 先查目录布局images、labels 与 data.yaml 三件套拿到手第一件事不是直接训练而是先看目录组织。YOLO 系列数据集的常见结构是 images/ 和 labels/ 平级下面再按 train/val/test 划分另外配一个 data.yaml 或 classes.txt 记录类别名。car-detect-dataset 如果按这种结构组织训练阶段几乎零改动如果它把所有 jpg 和 txt 混在一个源目录里就得先自己写划分脚本。car-detect-dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── img_0001.txt │ │ └── ... │ ├── val/ │ └── test/ └── data.yaml这是最常见的 YOLO 数据组织方式。train 和 val 分开是必须的因为训练过程中验证集负责判断模型是否过拟合没有它你只能靠 loss 曲线猜。test 目录在严格实验中只用于最终评估不要用它反复调参否则测试集就失去了独立性。很多公开数据集只给 train 和 valtest 可以留空YOLOv8 也能正常训练。如果数据集只给了一个全是图片和标签的平铺目录你需要按 8:1:1 划分。我一般用脚本随机切但要注意先按图片的“场景”而不是按文件名简单哈希分否则同一时段连续帧可能同时出现在训练和验证里会造成 mAP 虚高。场景重复在车辆检测数据里尤其常见因为监控视频抽帧得到的图片前后几帧内容几乎一样应该把同一段连续帧放到同一个子集里。2.2 YOLO 标注的五个数字从 bbox 到归一化坐标每张图片在 labels/train 下都有一个同名 txt内容形如0 0.621094 0.482292 0.298437 0.539583 1 0.813672 0.391667 0.321094 0.466667 2 0.451172 0.593750 0.274219 0.452083第一列是类别 ID后面四个数是 x_center、y_center、width、height。这四个数全部相对于图片宽高做了归一化取值范围在 0 到 1 之间。注意这里不是 xmin/ymin/xmax/ymax 的 PASCAL VOC 格式很多从 VOC 转过来的人会在这里翻车把左上右下坐标原样塞进去导致框跑到画面外训练时 loss 还在降但推理结果全是乱框。验证标注是否规范最直接的方法是写一个统计脚本检查坐标范围、框面积和类别分布。我拿到的每个数据集都会先跑一遍这样的检查不花多少时间但能避免后面训练一小时后才发现标签有毒。import os from collections import Counter label_dir labels/train cls_counter Counter() bad_lines [] for name in os.listdir(label_dir): path os.path.join(label_dir, name) with open(path, r) as f: for line_no, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: bad_lines.append((name, line_no, columns ! 5)) continue cls int(parts[0]) x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_lines.append((name, line_no, out of range)) cls_counter[cls] 1 print(class distribution:, cls_counter) print(bad lines count:, len(bad_lines)) for item in bad_lines[:20]: print(item)这段代码做了三件事统计每个类别 ID 出现次数检查每行是否有 5 个字段检查坐标是否越界。类别分布是后面判断是否均衡、要不要做重采样的依据越界行可能来自标注工具导出错误也可能是裁剪图片后没重新归一化。如果 bad_lines 不为空先修数据再训练否则训练过程中会出现莫名其妙的 loss 上升甚至 NaN。2.3 三类目标具体是哪三类不要猜先看 classes.txt标题只写了“三类别”没有明确说出是哪三类。常见做法是把车辆分成 car、truck、bus也有数据集用 car、bus、van甚至把摩托车算作第三类。这个关键信息写在 data.yaml 的 names 字段里不去确认就直接训练后面看混淆矩阵时会对不上号。import yaml with open(data.yaml, r, encodingutf-8) as f: cfg yaml.safe_load(f) print(cfg.get(names))输出大概率是 [car, truck, bus] 这种按顺序排列的字符串列表。类别 ID 的排列顺序决定了训练时数字 0 对应第一个名字。如果 data.yaml 里 names 是 [car, bus, truck]但标注 txt 里的 0 实际代表 truck那么模型学到的映射完全错位mAP 再高也没有意义。这是数据集本身最隐蔽也最容易踩的坑拿到后先打印前几个标签文件确认。import os label_dir labels/train for name in sorted(os.listdir(label_dir))[:3]: path os.path.join(label_dir, name) print(f--- {name} ---) with open(path) as f: print(f.read().strip())把输出的类别 ID 和 data.yaml 里的 names 一一对照。如果发现 ID 顺序与 names 不一致要么改 txt 里的 ID要么改 data.yaml我一般选择改 data.yaml因为改标签文件要遍历所有文件容易改错。这里还有一个细节YOLOv8 会把 classes 数量作为模型输出层维度如果 names 里写了 3 类但标签里出现了类别 ID 3训练时不会立刻报错而是在计算损失时越界导致 loss 直接变成 NaN。所以上面的统计脚本里最好把类别 ID 和 len(names) 再做一次比较。3. 用 YOLOv8 在这个数据集上跑通训练命令、参数与最小配置3.1 数据集的 YAML 配置怎么写YOLOv8 训练时通过 data 参数指定数据配置文件不需要改动模型源码。如果数据集自带 data.yaml先确认里面 path、train、val 是绝对路径还是相对路径。绝对路径在自己机器上能用换一台机器就失效我习惯改成相对路径方便复现和拷贝。path: car-detect-dataset train: images/train val: images/val test: images/test names: 0: car 1: truck 2: bustrain 和 val 指向的是图片目录而不是标签目录。Ultralytics 的 DataLoader 会在读取图片路径后自动把路径里的 images 替换成 labels 去找同名 txt。所以如果你手滑把 train 写成了 labels/train它反而会尝试读取 images/labels/train 下的图片直接报找不到文件。这是配置阶段最常见的低级错误但报错信息不一定直接说是路径问题有时会提示“Dataset not found”或“No images found in ...”新手容易在这里卡很久。验证路径是否正确的快速方法是直接打印目录列表。如果 train 有 1434 张图片、val 有 359 张比例大概 8:2就符合预期。如果 val 目录是空的训练也会启动但在最后评估时 mAP 全为 0且 loss 曲线里 val 线是平的。所以配置完后我一般先跑一次yolo detect train ... epochs1看前置日志日志会打印加载了多少训练和验证图片这时确认数量和目录一致再上全量训练。3.2 训练命令与关键超参数epochs、batch、imgsz在数据集根目录执行下面的命令是最小的可跑通配置yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ batch16 \ imgsz640 \ device0 \ projectruns \ namecar_detect_v8nmodel 用 yolov8n.pt 是官方预训练权重不是从零开始训练。这对 1793 张的数据量至关重要迁移学习能让你少跑一半的 epoch并且最终 mAP 也明显更高。epochs 设 100 对这个小数据集来说偏多通常 50 轮左右已经收敛搭配早停更稳。batch 16 在显存 8G 左右的显卡上是安全值如果显存溢出就降到 8或者加cacheFalse减少缓存占用。imgsz 640 是默认值车辆如果以中大型目标为主640 够用如果监控画面里车辆离得远、目标只占几十个像素建议把 imgsz 提到 960或者降到 480 配合多尺度训练。训练过程中日志会输出每轮的 box_loss、cls_loss、dfl_loss以及验证集的 mAP50、mAP50-95。不要只看 train loss它下降不代表泛化就好。关注 val 曲线如果 val mAP 连续 20 轮不涨就是早停条件。YOLOv8 默认没有开启早停需要显式加patience20我一般加上避免人不在机器前时白等几个小时。还有一种常见做法是加plotsTrue训练结束自动生成 results.png、confusion_matrix.png、PR_curve.png。这些图都在 project 目录下。建议第一次训练时加上它后面分析结果基本靠这些图而不是只看终端数字。如果换了小显存机器还可以加workers4控制数据加载线程在 Windows 上 workers 设 0 或 2 更稳线程太高容易触发 DataLoader 崩溃。3.3 预训练权重怎么选yolov8n 与 yolov8m 的取舍官方预训练权重按规模分成 n、s、m、l、x 五档参数量从 3M 到 70M 不等。1793 张、三类目标直接上 x 是自找麻烦模型容量大但数据不足反而容易过拟合训练时间也成倍增加。我一般先用 n 把整个链路跑通看 loss 和 mAP 是否正常再换 m 冲一次精度。yolo detect predict modelyolov8m.pt sourcesample.jpg第一次执行这个命令会自动把 yolov8m.pt 下载到当前目录的 weights 缓存位置。预训练模型下载是 YOLO 生态里体验最好的环节之一不需要手动找链接命令行直接拖下来。n 和 m 在 COCO 上的公开参考指标分别是 mAP50 约 37 和 50 左右但这只是参考在你自己的车辆数据集上会不同。m 比 n 精度更高但推理速度更慢、内存占用更大。如果之后要部署到 RK3588、树莓派这类边缘设备n 是更现实的选择如果只是在服务器上做实验、不急着上设备m 对小车和远车的召回率明显更好。选择权重的另一个维度是和数据集场景是否接近。COCO 预训练权重里本身包含车辆类目所以迁移过来是自然顺畅的。如果你拿到的数据集是红外、夜间或无人机视角与 COCO 的自然光场景差异很大预训练带来的收益会下降这时候应该考虑用更大的 m 权重让模型有更多特征拟合空间。在 V100 或 A100 上m 和 n 的训练时间差距不大但在 1060 这类老卡上n 的训练速度快一倍以上先用 n 做调试更省时间。4. 训练后怎么看结果混淆矩阵、损失曲线与 mAP 的前后故事4.1 损失曲线三条线box_loss、cls_loss、dfl_loss 怎么读训练结束后runs/car_detect_v8n/ 目录下会有 results.png 和 results.csv。YOLOv8 的损失分成三部分box_loss 是预测框和真值框的 IoU 差异cls_loss 是分类误差dfl_loss 是分布焦点损失负责框的边界回归。三者量级不同第一次看的人会误以为没收敛因为 box_loss 通常在 1.0 左右cls_loss 在 0.5 左右dfl_loss 在 0.8 左右。判断收敛不能只看某个 loss 的绝对值要看它的曲线是否进入平台期。我一般会写一个小脚本读取 results.csv把最后一行的验证指标打印出来省得翻图import csv with open(runs/car_detect_v8n/results.csv) as f: rows list(csv.DictReader(f)) last rows[-1] print(epoch:, last[epoch]) print(mAP50:, last[metrics/mAP50(B)]) print(mAP50-95:, last[metrics/mAP50-95(B)])csv 列名在不同版本里可能略有差异如果 KeyError可以先打印 last.keys() 看实际列名。逻辑上只看三个指标验证集的 mAP50、mAP50-95 和验证集 loss。如果 train loss 一直降但 val loss 在后半段翻转上升说明过拟合了。1793 张的数据量下过拟合很常见表现就是 mAP50 在峰值后开始小幅度下降。这时不要加数据先降低模型复杂度或增强正则比如把dropout调高、把mosaic关掉、提前在峰值点取权重。还有一类特殊情况cls_loss 从一开始就极低比如 0.01 以下。这通常意味着类别严重不均衡模型把所有目标都预测成最多的那一类因为这样分类损失已经很小。单看 cls_loss 会发现它漂亮收敛但 mAP 起不来。出现这种情况要回到 2.2 的类别分布统计给少类目标做重采样而不是继续调超参。4.2 混淆矩阵“总和不为 1”的坑与类别偏移问题YOLOv8 画出的混淆矩阵是归一化的但它的归一化方向是“按真实类别的样本数归一化”也就是说每一行的和为 1而不是整个矩阵的总和为 1。很多人在热力图里看到对角线外的格子也有数字全表总和不是 1就以为训练出错了其实这是 YOLO 系列一直以来的展示方式。要看类别偏移正确做法是横着看每一行。比如真实 truck 这一行模型把 68% 的 truck 判成了 truck把 30% 判成了 car说明模型对 truck 和 car 的区分度不够。这时要看是不是 truck 样本太少或者图像里卡车尾部和轿车尾部视觉上太接近。不要直接加大 conf 阈值那是把误检压下去了但漏检也会上升正确做法是增加 truck 的样本量或做针对性的数据增强。如果某个类别的训练样本只有十几个混淆矩阵的百分比会失真一个误判就能让数字跳 10 个点。所以看混淆矩阵时还要看 Ultralytics 生成的 confusion_matrix_normalized.png 旁边有没有 counts 图。counts 矩阵显示每个格子的具体数量能帮你判断 30% 这个比例背后到底是一个误检还是三十个误检。如果总量少宁可多跑几个 epoch 再用统计指标说话。4.3 mAP50 与 mAP50-95 的差距说明了什么验证结果里两个关键指标mAP50 计算 IoU 阈值取 0.5 时的精度mAP50-95 把阈值从 0.5 到 0.95 每 0.05 取一档再对所有阈值求平均。如果 mAP50 高但 mAP50-95 明显低说明模型能框出车但框的位置不够紧致边界偏移大。这在车辆检测里非常常见因为斜停的车和横穿的卡车形状差异大固定锚框很难贴合真实轮廓。遇到这个情况优先检查数据集中是否存在很多宽高比极端的目标。YOLOv8 的 anchor 是自动学习的能做的调整有限真正有效的是提高 imgsz 到 960让目标在特征图上占据更多像素边界回归更精细。另一个方法是训练时开多尺度比如imgsz640配合scale0.5让模型适应不同距离下的目标尺度。部署端也可以降低 NMS 的 IoU 阈值试试比如从 0.7 降到 0.5能减少重叠框之间的相互抑制。mAP50 和 mAP50-95 差距过大的另一个原因是验证集里标注框本身就画得松。如果标注人员只框了车体而不包括后视镜和保险杠模型预测的框会更紧IoU 计算吃亏。我在评估前会随机抽 20 张图把标签框画在原图上人工看一眼确认标注质量。这一环省不得因为数据集的 mAP 高低有一半是由标注一致性和边界定义决定的。5. 车辆检测数据集踩坑手册从标注校验到训练翻车的五个常见问题5.1 现象训练 loss 正常但 AP 全为 0训练和验证都跑完了loss 正常下降但打印出的 mAP50 和 mAP50-95 全是 0。最常见原因是标签类别 ID 从 1 开始写而 data.yaml 的 names 下标从 0 开始所有类别整体前移了一格。比如真实的 car 应该是 0数据集里写成了 1模型学习到的类别 1 实际上对应 bus验证时对不上。解决方法是写脚本把 txt 第一列全部减 1并检查最小值是否变成 0。运行前先备份原始标签。import os for split in [train, val]: folder flabels/{split} for name in os.listdir(folder): path os.path.join(folder, name) with open(path, r) as f: lines f.readlines() new_lines [] for line in lines: parts line.strip().split() if not parts: continue cls int(parts[0]) - 1 if cls 0: print(negative cls found:, name, parts[0]) continue new_lines.append(f{cls} .join(parts[1:]) \n) with open(path, w) as f: f.writelines(new_lines)逻辑就是暴力减 1如果减出负数就打印出来提醒你检查原始标签。这个操作只解决 ID 偏移不是万能修复。还有一类 AP 全为 0 的原因是验证图片和训练图片完全重复模型在训练时看到过验证图但验证时因为类别映射错乱反而全错此时优先检查是否存在数据泄露。5.2 现象验证集 mAP 很高实际视频里漏检在验证集上 mAP50 到了 0.85但拿到实际监控视频里跑远处的车经常漏检。原因是验证集里的目标尺寸普遍较大而视频中远距离车辆的框在 640x640 缩放下只有十几个像素YOLOv8 的下采样倍数会把小目标特征直接扔掉。先统计标签里小框占比import os THRESH 0.02 # 对应 640 图上约 13 像素 for name in os.listdir(labels/train): with open(flabels/train/{name}) as f: for line in f.read().strip().splitlines(): parts line.split() w, h float(parts[3]), float(parts[4]) if w THRESH or h THRESH: print(f{name}: small box {w:.3f} x {h:.3f})如果大量目标小于 0.02就要把输入分辨率提到 960 或 1280或者把原始大图切成四块分别检测再合并。切片检测能保留小目标信息但推理时间翻倍不适合实时场景。另一个思路是换用带 P2 输出头的模型结构它能保留更高分辨率的特征图对小目标更友好。修改模型结构对新手不推荐先把 imgsz 提上去是最直接的后悔药。5.3 现象truck 被误检成 cartruck 被大量误检成 car通常在类别不均衡或两类目标外形接近时出现。侧面视角下卡车和轿车的轮廓确实难以区分特别是皮卡车型。模型缺少足够多的 truck 样本自然会偏向把不确定目标判成样本多的 car。先用命令统计每类框数awk -F {count[$1]} END {for (k in count) print k, count[k]} labels/train/*.txt如果 truck 只有 car 的三分之一先做最简单的复制增强把含 truck 的图片复制几份每份做不同的亮度、平移、翻转再放进训练集。这个方法效果立竿见影我曾在只有 200 张 truck 样本的数据集上把 truck 的 AP 从 0.61 提到 0.74。如果复制后提升不明显再考虑在 Mosaic 增强中强制混入多张含 truck 的图片让模型每次迭代都能看到足够多的 truck 特征。5.4 现象训练到一半 loss 突然 NaN训练到某一步 loss 变成 NaN常见原因有三个学习率过大、batch 归一化不稳定、标签里有零面积或非法坐标框。YOLOv8 默认 lr00.01在有些自定义数据上会偏大AMP 混合精度在特定显卡驱动下也会触发 NaN。但根据我的血泪经验90% 的 NaN 是标签问题而不是超参问题。先运行这把标签扫描器import os issues [] for split in [train, val]: label_dir flabels/{split} for name in os.listdir(label_dir): path os.path.join(label_dir, name) with open(path) as f: for i, line in enumerate(f, 1): parts list(map(float, line.split())) if len(parts) ! 5: issues.append((name, i, 5 columns expected)) elif parts[2] 0 or parts[3] 0 or parts[4] 0: issues.append((name, i, zero area)) for issue in issues[:30]: print(issue)如果找到零面积框删除该行或重新标注。如果没有坏标签就把lr0改成 0.005加ampFalse关掉混合精度再试一轮。注意不要把两个改动同时做否则不知道是哪个因素修好的。我习惯先关 AMP因为它在 10 系显卡上触发 NaN 的概率很高如果关掉后正常再逐步把学习率调回去找边界。5.5 现象标签文件里出现小数超过 1标签里出现 x 1 或 y 1 的值说明标注工具导出的是像素坐标没有按图片宽高归一化或者图片被裁剪后标签没有重新计算比例。YOLO 训练虽然不会立刻报错但会把框算到画面外导致该目标永远学不进去。用脚本做归一化修复import os from PIL import Image for split in [train, val]: img_dir fimages/{split} label_dir flabels/{split} for name in os.listdir(img_dir): img_path os.path.join(img_dir, name) label_path os.path.splitext(img_path)[0].replace(images, labels) .txt if not os.path.exists(label_path): continue w_img, h_img Image.open(img_path).size with open(label_path) as f: lines f.read().strip().splitlines() new_lines [] for line in lines: cls, x, y, w, h line.split() x, y, w, h map(float, (x, y, w, h)) if x 1 or y 1 or w 1 or h 1: x, y x / w_img, y / h_img w, h w / w_img, h / h_img new_lines.append(f{cls} {x:.6f} {y:.6f} {w:.6f} {h:.6f}) with open(label_path, w) as f: f.write(\n.join(new_lines))这里最容易漏的是图片扩展名.jpg、.jpeg、.png混在一起时用os.path.splitext(img_path)[0]取主文件名最稳妥。脚本假设大于 1 的字段都是像素坐标如果某些框已经归一化且宽高恰好超过 1会被误处理。所以运行前先打印原始坐标 max 值确认。修复后重新跑 2.2 的检查脚本看到所有坐标都在 0 到 1 区间内才算修干净。6. 让 1793 张数据发挥出两倍价值增强策略、类别重加权与部署前验证6.1 用增强参数把有限样本变出更多视角YTLOv8 的数据增强可以通过训练命令直接覆盖不需要写额外代码。车辆检测对光照和视角变化敏感我一般会在默认增强基础上做小幅调整yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs60 \ batch16 \ imgsz640 \ hsv_h0.02 hsv_s0.7 hsv_v0.4 \ translate0.1 scale0.5 fliplr0.5 \ mosaic0.8hsv 扰动模拟早晚阳光和阴影车辆反光场景很吃这一套。scale 和 translate 控制在 0.5 和 0.1 以内避免把车切出画面太多。mosaic 不要保持默认的 1.0尤其是小数据集下马赛克拼接会让模型学到“车都带边框”的假特征反而降低泛化。0.8 是折中值既能混入背景又保留大部分完整车辆。6.2 类别重加权用样本复制解决不均衡YOLOv8 没有直接的类别权重参数最实用的做法是给少数类做图像级复制每次复制配不同增强。不要只复制原图否则模型会把重复样本背下来验证集里一旦出现接近重复的图mAP 会虚高到不可信。我一般复制时同时做水平翻转和亮度变化这样等效于扩充了真实场景。这里要提到一个容易踩的坑复制后标签文件也要同步复制且文件名不能重复。最常见的翻车方式是只复制了图片而忘了复制 txt训练时找不到标签直接跳过该图。批量操作时用脚本统一命名别手动拖。6.3 部署前验证导出 ONNX 并对照同一张图训练完最后一步不是看 mAP而是把模型导出到 ONNX 或 TensorRT再用同一张测试图对比 PyTorch 输出和部署引擎输出。命令yolo export modelruns/car_detect_v8n/weights/best.pt formatonnx imgsz640导出后要先查输出节点名和形状很多部署框架要求固定输入尺寸。比对时如果两边框相差超过 2% 的 IoU先检查预处理是否一致归一化方式、通道顺序、缩放插值算法。我之前有一次部署翻车就是因为训练时用的是 BGR部署端用了 RGB结果所有框向右偏移了十几个像素人眼很难看出来但和真值框一比立刻现形。我的习惯是每次拿到新数据集先用 10 张图跑 1 轮验证代码链路确认数据加载、标签读取、loss 计算都正常再上全量训练。这个习惯帮我省下了很多无效的等训练时间。数据量小不是失败理由标注质量、类别平衡和部署一致性才是真正拉开差距的地方。希望这些经验对你有帮助。本文还有配套的精品资源点击获取
返回列表