ARTICLE DETAIL

资讯详情

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

工程车辆检测数据集怎么用:VOC+YOLO双格式解析与YOLOv8训练实战

工程车辆检测数据集怎么用:VOC+YOLO双格式解析与YOLOv8训练实战 简介面向目标检测算法训练与工地安全监控场景这是一套工程车辆检测数据集覆盖5067张图片的标注信息包括挖掘机、叉车、装载机、压路机、混凝土运输车、卡车及工人共7个类别并提供Pascal VOC与YOLO两种主流格式。此次打包的压缩包共2000个文件主要内容为1999个xml标注文件和1个说明txt文件整体约321.86MB可解压后直接用于目标检测模型的训练输入或标注格式转换。除标注数据外资源还附带类别对照与使用说明帮助使用者快速理解每个类别的中英文含义和标注方式。工程机械与工人共存的目标组合适合在智慧工地、施工安全监测、无人车辆感知等方向开展模型训练与效果评测。目前已有586人学习下载适合有一定深度学习基础、需要真实工程场景数据进行迁移学习或算法验证的开发者也可作为相关算法竞赛或课程设计的基础数据。1. 挖掘机叉车工程车辆检测数据集为什么通用模型在这里会“翻车”工地、料场、仓库里做车辆检测拿 COCO 预训练权重直接推理结果往往很尴尬挖掘机被认成普通卡车叉车在侧视角下漏检装载机和推土机互相混淆。这不是模型能力不行而是 COCO 里工程车辆的细分类目太少绝大多数从业者又不具备自采标注的条件。挖掘机叉车工程车辆检测数据集VOCYOLO格式5067张7类别.7z 提供的正是解决这类场景需求的现成弹药5067 张贴近真实施工环境的图像7 个工程车辆类别同时附 PASCAL VOCXML和 YOLOtxt两种标注格式解压以后不用做跨格式转换就能直接进训练管线。适合正在做智慧工地、港口调度、厂区安防监控或者准备用 YOLOv8 训练自己的工程车辆检测模型的团队和个人。2. 拆开压缩包核对家底VOC 与 YOLO 双格式标注的读取与一致性校验拿到压缩包先别急着训练。工程车辆数据集的坑大部分不在模型而在标注文件本身。常见做法是先解压、核对目录、校验两种格式是否真的对应同一批框确认之后再做数据集划分和训练。2.1 解压与目录确认先做这三步.7z 压缩包用 7-Zip 命令行解压。Linux 环境下如果没有 7z 命令先装 p7zip然后解压到指定目录避免文件散落一地# Debian/Ubuntu 安装 7z sudo apt install p7zip-full # 先预览压缩包内容看有没有顶层目录 7z l 挖掘机叉车工程车辆检测数据集VOCYOLO格式5067张7类别.7z | head -30 # 正式解压到工程目录 7z x 挖掘机叉车工程车辆检测数据集VOCYOLO格式5067张7类别.7z -oengineering_vehicle_dataset先跑7z l再解压是因为有的压缩包自带一级目录有的没有。如果自带顶层目录解压后数据集中在engineering_vehicle_dataset/压缩包名/下面写后续脚本的路径时得把这一层加进去。解压完成后依次确认三件事图片数量是否等于 5067 张左右、标注目录里是否有跟图片同名的 XML 和 txt、图片文件名里是否带中文或空格。文件名带中文或空格在 Windows 下用没问题复制到 Linux 训练机上就容易出幺蛾子后面第 4 章专门讲。2.2 读 VOC 标注XML 里的字段具体对应什么VOC 格式的标注文件是一个 XML每个文件对应一张图片。用 Python 读取时关键是拿到size里的图像宽高以及每个object里的name和bndbox。工程车辆数据集的标注习惯和通用 VOC 数据一致但类别名是以压缩包内实际标注为准的先用脚本统计全部类别顺便验证是否真的是 7 类import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter voc_dir Path(engineering_vehicle_dataset/VOC/Annotations) class_counter Counter() for xml_file in sorted(voc_dir.glob(*.xml)): root ET.parse(xml_file).getroot() for obj in root.findall(object): name obj.find(name).text class_counter[name] 1 for name, count in class_counter.most_common(): print(f{name:20s} {count:5d} 个实例) print(f类别总数: {len(class_counter)})这段代码把每个 XML 文件里的name标签汇总计数。如果跑完发现类别数不是 7先停下来核对压缩包内说明别急着往下走。继续读单个 XML 里的框import xml.etree.ElementTree as ET xml_file engineering_vehicle_dataset/VOC/Annotations/000001.xml root ET.parse(xml_file).getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) print(f图像尺寸: {img_w} x {img_h}) for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) print(f{name}: ({xmin}, {ymin}) - ({xmax}, {ymax}))bndbox里存的是像素坐标左上角和右下角闭区间。注意xmax、ymax可能比图像宽高小 1这是 VOC 的常见细节后续转 YOLO 做归一化时分母直接用img_w和img_h即可不用减 1。2.3 读 YOLO 标签并与 VOC 互相校验归一化坐标与对象数核验YOLO 格式的 txt 每行 5 个数类别 ID、归一化中心 x、归一化中心 y、归一化宽、归一化高。数据集的 YOLO 目录是否分好了 train/val不同打包方式不一样但单个标签文件的读取逻辑是固定的from pathlib import Path txt_file Path(engineering_vehicle_dataset/YOLO/labels/000001.txt) img_w, img_h 1920, 1080 # 从对应图片读取不要手写 for line in txt_file.read_text().splitlines(): line line.strip() if not line: continue parts line.split() cls_id int(parts[0]) x_center float(parts[1]) y_center float(parts[2]) bw float(parts[3]) bh float(parts[4]) # 反归一化回像素坐标 x1 (x_center - bw / 2) * img_w y1 (y_center - bh / 2) * img_h x2 (x_center bw / 2) * img_w y2 (y_center bh / 2) * img_h print(f类别ID{cls_id}, 像素框: ({x1:.1f}, {y1:.1f}) - ({x2:.1f}, {y2:.1f}))因为这份数据集同时给了 VOC 和 YOLO最该做的事是校验两个格式是不是同一个框。最省事的办法是对比每个文件里的目标数量VOC 里object节点数应该等于 YOLO txt 里的有效行数。import xml.etree.ElementTree as ET from pathlib import Path voc_dir Path(engineering_vehicle_dataset/VOC/Annotations) yolo_dir Path(engineering_vehicle_dataset/YOLO/labels) mismatch [] for xml_file in sorted(voc_dir.glob(*.xml)): root ET.parse(xml_file).getroot() voc_count len(root.findall(object)) txt_file yolo_dir / f{xml_file.stem}.txt if not txt_file.exists(): mismatch.append((xml_file.name, YOLO 标签缺失)) continue yolo_count len([ln for ln in txt_file.read_text().splitlines() if ln.strip()]) if voc_count ! yolo_count: mismatch.append((xml_file.name, fVOC{voc_count}, YOLO{yolo_count})) if mismatch: print(f发现 {len(mismatch)} 个不匹配文件前10个:) for name, msg in mismatch[:10]: print(f {name}: {msg}) else: print(全部一致VOC 与 YOLO 标注对象数量完全匹配)对象数量一致只是及格线。严谨一点的做法是随机抽 5 张图把 VOC 框和 YOLO 反归一化后的框做 IoU 计算IoU 接近 1 说明转换无误。我自己接手的标注数据里偶尔会出现两个格式来自不同标注版本的情况——数量一样坐标差了几十像素。抽样做 IoU 能省下训练完才发现标签错位的血泪时间。3. 用 YOLOv8 训练自己的工程车辆数据集data.yaml、损失曲线与 mAP 判读确认标注没问题后下一步就是进入 yolov8 训练自己的数据集的环节。工程车辆检测属于中粒度目标检测类别差异集中在局部结构挖斗、货叉、吊臂对特征分辨率有一定要求。即使压缩包里已经给了 YOLO 格式也依然要按 YOLOv8 的约定组织目录和配置。3.1 处理目录结构并生成 data.yamlYOLOv8 的 data.yaml 支持两种路径写法直接指向 train/val 图像目录或者指向包含图像绝对路径的 txt 文件。如果压缩包内的 YOLO 目录本身就是 images/train、images/val 的结构直接写目录即可。如果解压后只是一堆图片和一个 labels 目录就用 txt 索引方式不做物理移动import random from pathlib import Path base Path(engineering_vehicle_dataset) images sorted((base / images).glob(*.jpg)) random.seed(2024) random.shuffle(images) val_count int(len(images) * 0.15) with open(base / train.txt, w, encodingutf-8) as f: for img in images[val_count:]: f.write(str(img.resolve()) \n) with open(base / val.txt, w, encodingutf-8) as f: for img in images[:val_count]: f.write(str(img.resolve()) \n) print(ftrain: {len(images) - val_count}, val: {val_count})固定random.seed(2024)保证划分可复现。这个脚本假设图片都在images/扁平目录下标签在images同级的labels/目录YOLOv8 会自动根据图片路径推导对应标签路径。如果数据集已经分好 train/val则跳过这一步。然后建 data.yaml。类别名称以第 2 章统计出来的实际名称为准工程车辆数据集常见的是挖掘机、叉车、装载机、推土机、自卸车、压路机、吊车这 7 类下面的names需要按实际标注替换# data.yaml path: /absolute/path/to/engineering_vehicle_dataset train: train.txt val: val.txt nc: 7 names: 0: excavator 1: forklift 2: loader 3: bulldozer 4: dump_truck 5: road_roller 6: cranepath必须写绝对路径train/val 写相对路径时基于path解析。类别 ID 的顺序必须和标签 txt 里的cls_id一一对上错一位就是灾难。检查方法是读一个 txt 文件看第一列的类别最大值是否小于nc。3.2 定模型规模与训练命令先从 YOLOv8n 起步工程车辆数据集 5067 张如果单类别的样本量不多优先从 YOLOv8n 开始因为它参数量小、训练快能快速暴露数据问题。跑通基线后再换 YOLOv8s 提升精度不必一上来就上 YOLOv8m 或更大的模型。工程车辆目标通常比较大不像交通标志那样需要极高分辨率的小目标特征n 和 s 模型在这个任务上有明显性价比优势。# 安装 ultralytics 包 pip install ultralytics # 从 YOLOv8n 预训练权重开始训练 yolo detect train \ dataengineering_vehicle_dataset/data.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience30patience30表示验证集指标连续 30 轮不提升就早停对于 5000 张规模的数据集这比硬跑满 epoch 更省时间。batch16在 8GB 显存下跑 YOLOv8n 比较稳妥如果显存只有 6GB先降到 8不然中途 OOM 报错虽然不影响已保存的权重但浪费整体等待时间。imgsz640是 YOLOv8 的默认推理分辨率训练时数据会做随机缩放实测 640 在工程车辆这类大目标场景下已经够用。3.3 训练过程中判读哪几条曲线训练时不要只盯总 loss。YOLOv8 的 loss 由 box_loss、cls_loss、dfl_loss 三部分加权组成对应 yolo 损失函数里定位、分类、边框分布三个分支。工程车辆类别外形差异大但框都比较规整所以box_loss的收敛速度通常比cls_loss快。看曲线时重点盯两条训练集train/box_loss是否持续下降验证集val/box_loss是否同步下降或者至少稳定。如果 train 下降而 val 曲线在第 40 轮后开始翘头说明过拟合已经发生再练下去 val 的 mAP 不会涨。# 训练结束后直接看验证集指标 yolo val \ dataengineering_vehicle_dataset/data.yaml \ modelruns/detect/train/weights/best.pt \ imgsz640val命令跑完后重点看mAP50和mAP50-95两个指标。mAP50 对框的位置要求稍宽松偏向“检没检出来”mAP50-95 更严格同时衡量类别置信度和框精度。工程车辆场景如果目标是做安全帽、区域入侵这类管理类应用mAP50 达到 0.8 以上就能用如果要做车型计费、自动方量估算必须把 mAP50-95 拉上去否则框的位置偏一点后续测量误差会被放大。4. 这份数据集最常见的四个坑类别不平衡、标签错位、过拟合与乱路径标注数据不是拿来就能训得漂亮。工程车辆数据采集中机器出场频率天然不均热门车型样本多、冷门机型样本少这是数据集最常见的伤病。第 2 章的统计脚本跑完后如果发现某些类别只有几十个实例而某些类别上千训练时模型会把所有车辆都倾向预测为多数类。4.1 类别不平衡少数类只有几十个实例时怎么办现象训练时 loss 正常下降但验证时稀有类别的 recall 几乎为 0模型把所有工程车辆都识别成常见那两类。原因YOLOv8 的 default 训练没有按类别加权多数学的车吃到的梯度多决策边界偏向多数类。5067 张图7 个类别如果某一类少于 100 个实例模型很难学到稳定的特征。解决对包含稀有类别的图片做硬重采样复制一份扩充样本量。注意复制图片时连同它的标签一起复制而不是只复制标签否则同一张图被标记两次参与训练的数据分布会变得更怪import shutil from pathlib import Path from collections import Counter train_imgs Path(engineering_vehicle_dataset/images) train_labels Path(engineering_vehicle_dataset/labels) rare_classes set() # 统计每类的实例数找出少于阈值的类 class_count Counter() for txt in train_labels.glob(*.txt): for line in txt.read_text().splitlines(): if line.strip(): class_count[int(line.split()[0])] 1 for cls_id, cnt in class_count.items(): if cnt 100: rare_classes.add(cls_id) print(f稀有类别 {cls_id}, 实例数 {cnt}) # 复制包含稀有类别的图片和标签文件名加 _dup 后缀 for txt in train_labels.glob(*.txt): lines [ln for ln in txt.read_text().splitlines() if ln.strip()] if not lines: continue classes_in_img {int(ln.split()[0]) for ln in lines} if classes_in_img rare_classes: img_path train_imgs / f{txt.stem}.jpg if img_path.exists() and not txt.stem.endswith(_dup): shutil.copy(img_path, train_imgs / f{txt.stem}_dup.jpg) shutil.copy(txt, train_labels / f{txt.stem}_dup.txt)这段代码的副作用是被复制的图片若同时包含多数类多数类样本也会被顺带增广但整体比例仍然朝稀有类倾斜。复制后重新统计类别分布如果稀有类样本量仍然不足 200建议同时降低少数类别的置信度阈值推理时再结合场景单独处理。4.2 标签错位坐标越界、空标签文件与列数异常现象训练启动时终端报警告比如 “WARNING: image ... corrupted” 或者某个 txt 文件只有 3 列更隐蔽的是训练正常跑完但 val 阶段某些框明显画偏。原因VOC 转 YOLO 时坐标归一化算错或者从其他工具导出时把宽高顺序写反。另一个常见来源是压缩包内部标注文件不完整某些图片没有对应标签生成空 txt。解决在进入训练前写一个全量扫描脚本把标签文件里坐标不在 [0, 1] 区间、列数不为 5、类别 ID 超出nc的行全部找出来from pathlib import Path labels_dir Path(engineering_vehicle_dataset/labels) nc 7 bad_files [] for txt in labels_dir.rglob(*.txt): for line_no, line in enumerate(txt.read_text().splitlines(), 1): if not line.strip(): bad_files.append((txt, line_no, 空行)) continue parts line.split() if len(parts) ! 5: bad_files.append((txt, line_no, f列数{len(parts)})) continue try: cid, xc, yc, bw, bh map(float, parts) except ValueError: bad_files.append((txt, line_no, 非数字)) continue if int(cid) nc: bad_files.append((txt, line_no, f类别ID越界: {cid})) if not (0 xc 1 and 0 yc 1 and 0 bw 1 and 0 bh 1): bad_files.append((txt, line_no, f坐标越界: {parts})) print(f异常文件数: {len(bad_files)}) for txt, line_no, msg in bad_files[:20]: print(f{txt}:{line_no} {msg})扫描出的文件如果数量少直接删掉对应图片和标签如果数量多说明整批转换有问题需要回到 VOC 原始标注重新生成。数据集既然是双格式这个坑的责任应该由压缩包作者兜底但作为使用者训练前自检一遍是低成本高收益的动作。4.3 过拟合信号val 损失不降反升时先别调模型现象训练日志里train/box_loss一路降到 1.0 以下但val/box_loss在某个 epoch 后不再下降甚至上升验证集 mAP 也停在远低于预期的水平。原因5067 张的数据量对 7 个类别来说并不算大如果用了 YOLOv8m 或 YOLOv8l 训练模型容量过大直接背训练集另一种可能是增强参数开得过猛比如degrees180把挖掘机的姿态转得太离谱模型学到的是旋转伪影而不是车辆特征。解决先换回 YOLOv8n 跑一个精简对比加上freeze10冻结骨干网络前 10 层让模型只更新后层参数显著降低小数据集上的拟合压力。如果 val 曲线在第 30 轮左右就开始翘头把patience设成 20早停能省下大量等待。yolo detect train \ dataengineering_vehicle_dataset/data.yaml \ modelyolov8n.pt \ epochs80 \ imgsz640 \ batch16 \ freeze10 \ patience20freeze10在 YOLOv8 里表示冻结前 10 个模块的参数不是冻结 10 层卷积理解成“骨干网络全部冻结、只练检测头”即可。工程车辆的视觉特征与 COCO 预训练权重里的车类特征有重叠冻结骨干在小数据集上是常规操作。4.4 中文文件名与路径编码训练机上报错的第一来源现象Windows 下解压后把整个目录拖到 Linux 训练机运行 train 命令时提示图像文件找不到或者标签和图片对不上号。原因压缩包内文件名如果混有中文、空格、括号Linux 下文件系统本身没问题但 ultralytics 的数据加载器以及某些依赖库对非 ASCII 路径的兼容性时好时坏。更棘手的是 XML 里如果写了中文路径ET.parse会直接抛UnicodeDecodeError。解决解压后立即把所有目录和文件名改成纯 ASCII空格换成下划线。一个通用脚本import re from pathlib import Path def ascii_name(name: str) - str: new name.replace( , _) new new.replace(, ().replace(, )) new re.sub(r[^\x00-\x7F], , new) # 去掉非 ASCII 字符 return new root Path(engineering_vehicle_dataset) for f in sorted(root.rglob(*), reverseTrue): # 从叶子节点向上改 if f.is_file(): new_file f.with_name(ascii_name(f.name)) if new_file.name ! f.name: f.rename(new_file) for d in sorted([p for p in root.rglob(*) if p.is_dir()], reverseTrue): new_dir d.with_name(ascii_name(d.name)) if new_dir.name ! d.name: d.rename(new_dir)注意rglob加上reverseTrue的原因自底向上重命名否则改完子目录后父目录路径也会变化循环会失效。执行完重跑第 2 章的校验脚本确认图片与标签的对应关系没有被破坏。5. 把检测模型真正用起来增强、导出与部署边界的验证方法训练只是开始落地部署时还有很多细节。这个阶段我一般分三步验证先在测试图上跑可视化确认边界框贴合车型轮廓再导出 ONNX/TensorRT 引擎检查推理速度最后评估实际工况下的鲁棒性。# 在测试图片上看模型效果 yolo predict \ modelruns/detect/train/weights/best.pt \ sourcetest_scene.jpg \ imgsz640 \ conf0.35 \ save_txtTrue # 导出 ONNX 和 TensorRT 引擎 yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 yolo export modelruns/detect/train/weights/best.pt formatengine imgsz640 halfTrue device0工程车辆在施工画面里往往有遮挡、扬尘和极端光照模型训练时的增强参数如果不够激进真实部署时 mAP 衰减会比预期快。我习惯在部署前用一批现场照片专门做“压力测试”看挖掘机在逆光、夜间低照度、铲斗遮挡这些情况下的漏检分布。实测下来夜间场景是工程车辆检测模型最明显的短板因为训练数据大多来自白天夜间样本的分布漂移直接反映在置信度整体下降上。如果现场有夜间值班需求优先补红外或弱光样本继续微调。TensorRT 导出后640 分辨率输入、T4 级别显卡上单帧推理耗时常在毫秒级但真正的瓶颈往往在视频解码和跟踪后处理检测模型只占主链路的一部分。部署前用一段 1080p25 的视频流测完整 pipeline把检测耗时、跟踪耗时和排队耗时分开统计再决定是优化模型还是优化后处理逻辑。我自己在这个方向上的教训是拿到一份标注数据集最先做的永远不是跑训练而是花四十分钟做单向校验——类别统计、坐标扫描、双格式对比全部过了再动手。这个习惯帮我避开过至少两次标签错位、一次类别 ID 顺序颠倒的翻车现场。数据集的原始价值已经在里面了剩下的是踩平训练路线上这些坑让模型真正用自己的数据长出来。希望帮到你。本文还有配套的精品资源点击获取
返回列表