
简介面向车辆检测与目标识别研究的工程车辆图像数据集共收录1000张已标注图片涵盖重型卡车、沥青车、搅拌车、清障车、洒水车、拖拉机、挖掘机、压路机、吊车、自卸车等常见类型可支撑YOLO、Faster R-CNN、SSD等深度学习模型的训练与评估适用于自动驾驶、智能交通监控、工地安全等场景。压缩包内共1998个文件由999张jpg原图与999个xml标注文件组成整体约77.42MBxml文件按PASCAL VOC/COCO格式记录每辆车的位置边界框与类别信息可直接投入模型训练流程省去手动标注成本。每张图像均由专业人员精确标注边界框与类别一应俱全有助于提升算法在实际工程环境中的泛化能力。目前已有3916人学习下载对于需要高质量车辆检测数据的开发者和研究人员而言是一份可直接上手的实用资源。1. 工程车辆数据集到手先别急着训1000张已标注图能撑起多大场面接到一份「工程车辆数据集11000张IMG已标注」的交付很多人第一反应是直接扔进YOLO。我的建议是先停一下。1000张已标注图片做工地出入口识别、渣土车车厢状态检测、挖掘机违规作业告警这类场景的算法验证和项目demo完全够用但要直接扛夜间、扬尘和恶劣天气上线数据量还差一个量级。这篇文章把这个数据包从验收到训练、从踩坑到补数据的完整链路讲清楚适合刚拿到数据集、想尽快跑通检测模型并交付结果的人照着做。这里说的IMG指交付的图片包可能是jpg、png也可能混着硬件采集的raw格式导出的bmp统一转码是第一步。2. 数据体检先行把IMG图片和标注文件整理成可训练的标准格式拿到手先别急着配环境。工程车辆数据集最常见的问题不是模型训不动而是图片和标注对不上。所谓「已标注」常见交付是VOC格式的xml也可能是labelme导出的json少数情况下直接就是YOLO的txt。不管哪种都要先做一遍完整清点把图片、标注、尺寸三者的对应关系理清楚后面训练才不会翻车。2.1 第一步清点图片-标注对应关系与损坏文件检查写个脚本把所有图片过一遍这是整个流程里性价比最高的一步import os from PIL import Image import xml.etree.ElementTree as ET img_dir images ann_dir annotations missing_ann [] broken_img [] size_unmatch [] for img_name in os.listdir(img_dir): stem, ext os.path.splitext(img_name) if ext.lower() not in (.jpg, .jpeg, .png, .bmp): continue ann_file os.path.join(ann_dir, stem .xml) if not os.path.exists(ann_file): missing_ann.append(img_name) continue try: im Image.open(os.path.join(img_dir, img_name)) w, h im.size except Exception: broken_img.append(img_name) continue tree ET.parse(ann_file) size tree.getroot().find(size) aw int(size.find(width).text) ah int(size.find(height).text) if (aw, ah) ! (w, h): size_unmatch.append(img_name) print(缺标注:, len(missing_ann), missing_ann[:10]) print(损坏图片:, len(broken_img), broken_img[:10]) print(尺寸不一致:, len(size_unmatch), size_unmatch[:10])这段脚本输出三个清单。缺标注的图片在训练时不报错但会白占batch并把对应位置的特征拉偏损坏图片会在dataloader读图时崩掉崩得毫无规律尺寸不一致说明标注软件在resize后导出的xml宽高和原图对不上转换坐标时会让目标框整体偏移。三者都命中过其中尺寸不一致最隐蔽训练能跑mAP死活上不去。修复损坏图片可以用PIL重存一遍顺手把bmp统一转成jpgfrom PIL import Image import os for fn in os.listdir(img_dir): if fn.lower().endswith(.bmp): im Image.open(os.path.join(img_dir, fn)).convert(RGB) out fn[:-4] .jpg im.save(os.path.join(img_dir, out), quality95) os.remove(os.path.join(img_dir, fn))bmp单张动辄几MBYOLO训练时反复读盘会拖慢整个训练节奏。转jpg后体积缩小一个量级质量损失对检测任务影响很小。如果缺标注超过5%建议先找交付方补全再往下走。血泪经验带缺漏的数据集训出来的模型召回率会莫名低一截这口锅后面很难甩掉。2.2 标注格式归一VOC/COCO/YOLO三种结构的选择工程车辆数据集很少直接给YOLO格式。常见交付是VOC xmllabelImg导出或COCO jsonlabelme批量导出coco2017数据集结构也是这个套路。YOLO训练要的是每张图一个同名txt类别用索引表示。VOC转YOLO的核心脚本import xml.etree.ElementTree as ET classes [excavator, loader, dump_truck, roller] def voc_to_yolo(xml_file, out_file): tree ET.parse(xml_file) root tree.getroot() size root.find(size) w float(size.find(width).text) h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) xc (x1 x2) / 2 / w yc (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h xc min(max(xc, 0.0), 1.0) yc min(max(yc, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{classes.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(out_file, w, encodingutf-8) as f: f.write(\n.join(lines))关键点classes列表的顺序决定txt里每个数字代表哪个类别必须和后面数据集yaml里的names保持一致索引对不上模型就白训了。clamp那四行是防呆有些xml里标注框会超出图像边界直接写进txt会让目标框中心落到图外YOLO的anchor匹配直接失效。标完记得检查一下输出txt里有没有出现负坐标或大于1的坐标。如果交付的是COCO json转YOLO的思路类似遍历annotations数组每条的bbox是[x, y, width, height]绝对像素值除以图像宽高就是归一化坐标import json def coco_to_yolo(json_path, img_dir, label_dir): with open(json_path, encodingutf-8) as f: data json.load(f) img_map {img[id]: img[file_name] for img in data[images]} cat_map {cat[id]: i for i, cat in enumerate(data[categories])} anns_by_img {} for ann in data[annotations]: img_id ann[image_id] anns_by_img.setdefault(img_id, []).append(ann) for img_id, anns in anns_by_img.items(): img_file img_map[img_id] stem os.path.splitext(img_file)[0] # 读取图片真实尺寸避免用json里的宽高可能被resize过 im Image.open(os.path.join(img_dir, img_file)) w, h im.size lines [] for ann in anns: cat_idx cat_map[ann[category_id]] x, y, bw, bh ann[bbox] xc (x bw / 2) / w yc (y bh / 2) / h lines.append(f{cat_idx} {xc:.6f} {yc:.6f} {bw / w:.6f} {bh / h:.6f}) with open(os.path.join(label_dir, stem .txt), w) as f: f.write(\n.join(lines))注意这里读取的是图片文件的实际尺寸而不是json里记录的宽高。很多标注工具在标注时预览图是缩放过的json里存的是预览尺寸直接拿来归一化会让所有框集体偏移。如果交付的是旋转框标注四点坐标先别急着转成水平框那是第5章mmrotate的活转了反而丢信息。2.3 数据集目录结构与类别统计先把家底盘清楚统一成YOLO风格的目录结构这是后面所有训练命令的地基construction_vehicle/ ├── images/ │ ├── train/ # 约700张 │ ├── val/ # 约200张 │ └── test/ # 约100张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── construction_vehicle.yamltrain和val的划分不要随机打散按工地或拍摄时段分。同一个工地同一个摄像头的连续帧同时进train和val模型等于开卷考试val指标虚高上线就露馅。如果交付方没给划分按文件名前缀分桶是最快的办法。在写yaml之前先统计一遍类别把「挖掘机」「excavator」「挖机」这类同义写法统一掉import os import xml.etree.ElementTree as ET def count_classes(ann_dir): counter {} for fn in sorted(os.listdir(ann_dir)): tree ET.parse(os.path.join(ann_dir, fn)) for obj in tree.getroot().iter(object): name obj.find(name).text counter[name] counter.get(name, 0) 1 return counter for cls, cnt in sorted(count_classes(annotations).items(), keylambda x: -x[1]): print(f{cls}: {cnt})这一步一定要做。工程车辆数据集的类别名往往五花八门同一种车四五个叫法。类别统计看一眼就知道哪些是少数类后面第4章的类不均衡处理就靠这份清单定位。顺便说一句有些交付的xml里object没有name字段只有id这种通常是对应某个类别文件需要找交付方要映射表别自己猜。3. 用YOLOv8训练自己的工程车辆数据集最小命令与必调参数YOLOv8算得上当前把「训练自己的数据集」这件事做得最省心的框架。ultralytics把数据加载、增强、训练、评估、导出一条链路都封装好了工程车辆这类垂直场景用默认配置加几个关键参数就能跑但前提是知道每个参数在1000张这个量级下该怎么设。3.1 数据划分与最小训练命令数据划分这一步我一般写个简单的Python脚本按文件名前缀分桶避免人工拖动文件时漏掉对应labelimport os import shutil from collections import defaultdict base construction_vehicle img_dir os.path.join(base, all_images) label_dir os.path.join(base, all_labels) groups defaultdict(list) for fn in os.listdir(img_dir): prefix fn.split(_)[0] # 按文件名前缀分工地 groups[prefix].append(fn) # 按前缀分组后每组按7:2:1分到train/val/test for prefix, files in groups.items(): files.sort() n len(files) for i, fn in enumerate(files): stem os.path.splitext(fn)[0] if i int(n * 0.7): split train elif i int(n * 0.9): split val else: split test shutil.copy(os.path.join(img_dir, fn), os.path.join(base, images, split, fn)) shutil.copy(os.path.join(label_dir, stem .txt), os.path.join(base, labels, split, stem .txt))按前缀分组而不是随机划分是为了避免同源连续帧泄漏。如果文件名里看不出场景信息就按时间戳排序后切片保证同一时间段只进一个split。目录就绪后训练命令很简单yolo detect train \ dataconstruction_vehicle.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ patience20 \ seed42 \ device0yolov8s.pt是预训练权重在COCO上训过。工程车辆这种垂直场景用预训练权重迁移比从头训收敛快得多1000张的数据从头训基本是浪费算力。如果显存吃紧batch降到8、imgsz降到512效果差距不大先跑通再说。3.2 超参数到底怎么调5个必调参数说明参数我的默认值调法踩坑点modelyolov8s.pt数据少用s或m别一上来就xx模型在1000张图上必过拟合val mAP反而更低imgsz640原图分辨率接近640就不用硬拉大把640的图拉到1280小目标没变清晰显存先崩epochs150看val loss收敛就停不必硬跑满1000张图150轮已经偏多配合patience用batch16显存不够降8不要降到1batch太小BN统计量漂移mAP抖动patience20早停防过拟合设太大等于没设设太小在波动期误停这些参数不是玄学是按数据量推出来的。1000张规模下模型容量一大就记题yolov8x参数量是s的5倍多泛化全靠数据多样性硬上大模型等于背答案。imgsz是另一个大坑施工监控的原始视频流经常是1920x1080抽帧后挖掘机可能只占一百多个像素硬拉高分辨率对检测帮助有限真正的瓶颈是标注质量和场景多样性。先把这两件事做好再考虑调参。3.3 训练结果怎么判读别只盯mAP训练结束会输出一组指标。mAP50和mAP50-95最先看但很多人忽略了混淆矩阵和P-R曲线。工程车辆数据集的几个典型现象loss曲线在epoch 40左右降到平后面一直震荡说明lr该衰减了或者数据里有脏标注。混淆矩阵里loader和dump_truck互相串通常是把轮式装载机和自卸车标注搞混了这种错标改标注比调模型快。val的mAP明显高于train的mAP那不是模型好是验证集太简单或划分泄漏。训练日志里每轮的val box loss可以拉出来画个曲线。如果val loss在某个epoch之后稳定回升而train loss还在降说明已经走进过拟合区间。这时候要么提前停要么把增强参数调弱再训。YOLOv8的runs目录下自带results.png直接看那张图就行。模型训完顺手跑一次验证集推理把结果图拼一张大图肉眼过一遍这比任何指标都诚实。4. 1000张小数据集的4个典型坑过拟合、漏标、类不均衡和验证集污染小数据集训练翻车就那么几类下面这四个坑是我在工程车辆项目里反复踩过的每个都按「现象 → 原因 → 解决」写清楚遇到可以直接照方抓药。4.1 现象loss降了mAP上不去train mAP 90val mAP 60原因模型在训练集上已经开始背题验证集泛化不住。叠加验证集太小比如只有几十张图mAP波动会非常大偶尔一个错检就能把指标拉下去好几个点。解决方式分两头走一是把val扩到200张以上宁可少训点也别让验证集失真二是用K-Fold交叉验证重新评估ultralytics官方给了K-Fold脚本思路把数据切成5份轮流做验证得到稳定指标。过拟合明显的把weight_decay从默认0.0005提到0.001再训一轮通常能压住一点。4.2 现象召回率低挖掘机经常漏检尤其远处的小目标原因人工复查标注时发现大量漏标——标注员只框了近处大目标远处小挖掘机全成了背景。模型学到的场景里「挖掘机大目标」成立小目标全是负样本自然检不出来。解决用训好的模型对训练集做一次预测把置信度0.3以下的框导出来人工过一遍能找回不少漏标。这是标注质检的常规做法实测1000张图找回100多个漏框很常见。补标后再训一轮召回率会明显回升这是改标注比改模型快的典型场景。4.3 现象mosaic增强一开小目标反而没了原因YOLOv8默认开启mosaic四张图拼一张工程车辆这种大目标经常被裁到边缘小目标被拼图切割后几乎不可见。mosaic适合密集小目标场景对工地监控的单车大目标反而有害。解决训练后期关掉mosaicYOLOv8直接给了参数yolo detect train \ dataconstruction_vehicle.yaml \ modelyolov8s.pt \ epochs150 \ close_mosaic10close_mosaic10表示最后10个epoch关闭mosaic让模型在真实分布上做最后收敛。这个参数对工程车辆基本是必开。同样的道理hsv_h、hsv_s颜色增强对夜间和扬尘场景有帮助但别把饱和度拉得太猛黄色挖掘机变成橙色模型的颜色特征就乱套了。小数据集最怕增强过度增强是给大数据集锦上添花的不是给小数据集雪中送炭。4.4 现象压路机死活训不出来mAP只有20多原因类别极不均衡。工程车辆数据集里常见分布是挖掘机500张、渣土车300张、装载机150张、压路机50张。少样本类别正样本不足特征学不出来且验证集里这类样本也少mAP指标对它是失效的。解决最直接的办法是把少类图片做离线增强随机旋转、平移、亮度变化各生成几份把数量拉平到接近中位类别。更稳的做法是给loss按类别加权让模型在少类上犯错时付出更大代价。如果允许引入外部数据公开的自动驾驶数据集里卡车类可以参考注意domain gap——监控视角和车载视角的差异很大引入后要人工筛查。4.5 现象离线测试效果很好现场demo被当场打脸原因验证集污染。这是最隐蔽的一个坑。数据包里的图片往往来自同一条视频流按帧抽出的连续序列随机切分后训练集里某帧的相邻帧出现在验证集里模型等于提前看到了答案。解决按拍摄时间或工地分组划分同一段视频的帧只能进同一个split。这条规则要在2.3分桶脚本里强制实现不能偷懒用随机random_split。验证集污染是数据科学的后悔药无法覆盖的问题只能从划分逻辑上根除。5. 进阶用法旋转框检测与数据扩充优先级工程车辆和普通轿车最大的区别是长条结构。挖掘机的动臂、渣土车的车斗在俯视监控里经常斜着停水平框会把大量背景框进去两台相邻车辆的水平框一重叠NMS直接把目标消掉。这种情况用旋转框检测更贴合mmrotate是MMDetection生态里做旋转框的主流工具训练DOTA数据集那一套配置可以直接迁移过来。需要把标注转成DOTA的四点格式labelme导出四个角点后再按x1,y1,x2,y2,x3,y3,x4,y4排列训练命令大致是python tools/train.py configs/oriented_rcnn/oriented_rcnn_r50_fpn_1x_dota.py具体config路径以mmrotate官方仓库为准。但坦白讲1000张做旋转框检测是偏少的DOTA那种规模一般要几千张起步。如果当前水平框的mAP已经够用优先把扩充数据放在第一位旋转检测是第二步的事。扩充数据的优先级我按性价比排新增工地场景和拍摄时段扩大场景多样性补小目标标注夜间和扬尘数据简单复制增强。扩充不是无脑加图工程车辆数据集的瓶颈通常在场景单一——同一个工地的2000张图不如三个工地的1000张图有用。每补一批数据就重跑一次2.1的体检和2.3的类别统计确认新数据没有破坏原有分布。最后一条教训我最早接手类似数据包时急着跳过体检直接训练结果验证集泄漏让demo指标虚高现场测试被甲方当场指出漏检返工了整整一周。从那以后凡是拿到数据先跑第2章的检查脚本成了固定流程。这个小习惯省下的返工时间远超脚本本身希望帮到你。本文还有配套的精品资源点击获取