☰
危化品运输车辆数据集:VOC转YOLO格式与YOLO训练全流程
2026/10/1 1:20:39 网站建设 项目流程

简介:这份危化品运输车辆数据集专为目标检测模型训练与验证设计,面向计算机视觉学习者、算法工程师及安监物流领域的智能监控应用开发者,主体为油罐车等危化品运输车辆,使用labelImg工具完成矩形框标注,类别统一为dangercar。压缩包共2000个文件,以XML标注文件为主,另含TXT使用授权说明,资源包约116.56MB;图像样本共2007张,标注框总计2211个,所有目标均为危化品运输车辆,标注准确且规则清晰。绝大多数样本为油罐车,还配套有视频讲解数据组织与使用要点,可直接用于模型训练、验证与格式转换,也可作为VOC标注格式的参考样例。需要说明的是,数据集只提供准确合理的人工标注,不附带训练权重或精度保证,用户可基于自身任务自行开展训练与评估。目前已有691人学习下载,适合需要危化品车辆识别数据的入门与进阶用户快速获取规范数据集。

1. 危化品运输车辆数据集:2007张图、1个类别,为什么值得你重新审视

做目标检测的人对“危化品运输车辆数据集VOC+YOLO格式2007张1类别.zip”这个标题应该不陌生。它给的是一份已经标注好的、针对危化品运输车辆的图片集,VOC和YOLO两种格式都替你准备好了,解压就能开训。所谓1类别,就是只检测“危化品运输车”这一个目标,常见的罐车、液罐半挂车都在里面。2007张图不算多,但对单一类别来说,配合预训练权重和合理的数据增强,完全够跑出一个能用的模型。

这个数据集真正解决的是行业里的一个尴尬问题:通用目标检测数据集里几乎没有危化品车辆这个类别,自己标注一批又贵又慢。有了现成的VOC和YOLO双格式数据,你省掉的是从零标注、格式转换、类别映射这些最耗时间的环节。适合谁用?搞化工园区安防、道路危险品运输监管、港口或物流园区车辆识别的工程师,以及刚入门YOLO想拿一份“干净数据”跑通全流程的学生。它的意义不在于2007张这个数字,而在于“开箱即用”这四个字。

2. VOC与YOLO格式的本质:标注文件里到底存了什么

2.1 两种格式的物理布局:解压后你应该看到什么

拿到zip压缩包,第一步不是急着解压训练,而是先看目录结构。VOC格式和YOLO格式的组织方式完全不同,弄混了后面训练直接报错。常见的目录组织是这样的:VOC部分有JPEGImages、Annotations、ImageSets/Main三个文件夹,JPEGImages放图片,Annotations放对应的xml标注文件,ImageSets/Main里是train.txt、val.txt这些划分文件。YOLO部分则通常是images和labels两个文件夹,images里是图片,labels里是每个图片对应的txt标注文件。有的打包者会把两个格式的图片分开各放一份,有的共用一份图片只把标注文件分开,解压后第一件事就是确认图片是否重复存放。

VOC格式的xml文件用标签树描述目标信息,最核心的几个节点是filename、size、object。filename给的是图片文件名,size里是图像的宽和高,object里包含目标的类别名name和边界框bndbox。bndbox里是xmin、ymin、xmax、ymax四个绝对像素坐标。注意VOC的坐标是从左上角开始算的,xmin和ymin是框左上角的像素位置,xmax和ymax是右下角的像素位置。这个坐标是绝对坐标,不需要归一化,直接写的就是图片上的真实像素值,读取的时候不需要做任何缩放换算。

YOLO格式的txt文件就简洁得多,每一行对应一个目标,格式是“class_id x_center y_center width height”。前一个数字是类别编号,从0开始计数,后面四个数全部是相对于图片宽高的归一化值,取值范围在0到1之间。x_center是目标中心点的横坐标除以图片宽度,y_center是中心点的纵坐标除以图片高度,width是目标框宽度除以图片宽度,height是目标框高度除以图片高度。由于只有1个类别,class_id只有0,不会有第二个数字出现。

2.2 为什么要同时提供VOC和YOLO:你的训练框架决定你用哪份

如果你用的是YOLOv5、YOLOv8这类框架,你会直接用YOLO格式的txt标注,因为框架内置的数据加载器只认txt。如果你要用mmdetection、Detectron2或自己写训练脚本,那VOC格式的xml更通用,因为很多框架的VOC数据加载器是现成的。提供双格式的意义在于免去你自己做格式转换这一步,也避免你因为转换脚本写得不对导致坐标错乱。

这里有个很多人容易忽略的点:同一个数据集转成两种格式,如果转换脚本坐标算错了,两份标注的框就会对不上。所以拿到数据的第一件事不是直接用,而是随机抽几张图,把VOC的框和YOLO的框分别画出来对比一下,确认两个格式标注的是同一个目标区域。这个验证花不了十分钟,但能避免你训练到一半才发现边界框全部偏移。

3. 用Python把VOC转成YOLO:转换脚本与坐标归一化细节

3.1 目录准备:不要在原数据上直接改

不管你是要验证双格式一致性,还是想自己重新划分数据集,我都建议复制一份到工作目录再动手。直接在原zip解压出来的文件夹上做转换或修改,一旦出错想恢复就得重新解压,浪费时间。我一般的工作目录结构如下,把VOC作为原始输入,YOLO作为输出目标:

workdir/ ├── voc_data/ │ ├── JPEGImages/ │ ├── Annotations/ │ └── ImageSets/ │ └── Main/ └── yolo_data/ ├── images/ ├── labels/ └── classes.txt

classes.txt里只有一行内容:dangerous_goods_vehicle。这个文件不是可有可无的,yolov5训练时靠它确定类别名,推理时靠它把类别编号映射回可读名称。有的数据集打包者会把它放在yolo目录下,有的会忘掉,如果你发现没有,自己建一个只含类别的文本文件就行。注意类别名不要用中文,虽然YOLO能读,但后面部署到C++或TensorRT时中文类别名会出现编码问题。

3.2 转换脚本:解析XML、计算归一化坐标、写入txt

核心转换逻辑是遍历Annotations目录下所有xml文件,解析每个目标框的绝对坐标,然后除以图片宽高得到归一化值。图片宽高从哪里拿?优先从xml的size节点读,不推荐用PIL去打开图片确认尺寸,因为一张张读图片IO开销大,2007张图跑下来明显慢。下面是完整的转换脚本,用Python的标准库就能跑,不需要额外安装依赖。

import os import xml.etree.ElementTree as ET voc_annotations = "voc_data/Annotations" yolo_labels = "yolo_data/labels" yolo_images = "yolo_data/images" os.makedirs(yolo_labels, exist_ok=True) os.makedirs(yolo_images, exist_ok=True) # 定义类别映射,classes.txt里第0行就是这个类别 class_name_to_id = {"dangerous_goods_vehicle": 0} for xml_file in os.listdir(voc_annotations): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(voc_annotations, xml_file)) root = tree.getroot() # 从XML的size节点读取原始宽高,避免逐张读图片 width = int(root.find("size/width").text) height = int(root.find("size/height").text) # 转换后的txt文件与xml同名,只是后缀不同 txt_path = os.path.join(yolo_labels, xml_file.replace(".xml", ".txt")) lines = [] for obj in root.iter("object"): # 跳过difficult标注,这类目标在VOC里算作难以辨认的样本 difficult = obj.find("difficult") if difficult is not None and difficult.text == "1": continue name = obj.find("name").text if name not in class_name_to_id: print(f"跳过未知类别: {name} in {xml_file}") continue class_id = class_name_to_id[name] bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) # 坐标归一化:中心点坐标除以宽高,框宽高也除以宽高 x_center = ((xmin + xmax) / 2) / width y_center = ((ymin + ymax) / 2) / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}") # 空标注文件也要生成,YOLO训练时会跳过空txt对应的图片 with open(txt_path, "w") as f: f.write("\n".join(lines))

这段代码的坐标计算要仔细看,x_center用的是(xmin + xmax) / 2,也就是左边界加右边界取平均值,而不是xmin加上宽度的一半。虽然数学上等价,但初学者容易写成xmin + width / 2,然后忘记width这个变量还没有除以图片宽度,导致中心点坐标超过1。y_center同理。所有归一化坐标必须确保在0到1之间,如果出现负数或大于1的值,说明源XML的标注框越界了,这种框训练时会被YOLO直接丢弃,看似没报错,实际有效样本少了。

类别映射只处理了dangerous_goods_vehicle这一种,如果xml里有其他类别,脚本会打印提示并跳过。这其实是有意的设计,不是bug,因为目的是确认标注里没有混入背景或其他车辆类别。如果打印出来的未知类别过多,说明数据集标注不够干净,这时候要回到VOC文件里人工检查。

3.3 数据划分:随机种子固定,验证集才有可比性

转换完格式后要划分train/val/test。很多数据集打包者会在ImageSets/Main里提供官方划分,但我建议自己重新划分一次,原因很简单:官方划分可能把难例集中放在验证集,也可能出现的图片重复,自己划分能确保分布均匀。划分时一定要固定随机种子,否则每次运行结果不同,实验结果没法复现。

import os import random import shutil random.seed(42) image_dir = "voc_data/JPEGImages" image_files = [f for f in os.listdir(image_dir) if f.endswith(".jpg")] random.shuffle(image_files) train_num = int(len(image_files) * 0.7) val_num = int(len(image_files) * 0.2) train_files = image_files[:train_num] val_files = image_files[train_num:train_num + val_num] test_files = image_files[train_num + val_num:] def copy_files(file_list, src_imgs, dst_imgs, dst_labels, label_suffix=".txt"): for f in file_list: shutil.copy(os.path.join(src_imgs, f), os.path.join(dst_imgs, f)) label_name = f.replace(".jpg", label_suffix) src_label = os.path.join("voc_data/Annotations", label_name) dst_label = os.path.join(dst_labels, label_name) if os.path.exists(src_label): shutil.copy(src_label, dst_label) copy_files(train_files, "voc_data/JPEGImages", "yolo_data/images", "yolo_data/labels")

这里有个前提假设是标注文件的命名和图片文件完全对应,jpg换txt后缀。如果数据集里存在一些图片没有对应标注,copy_files里if os.path.exists的判断会直接跳过,不会报错,但你要知道这些无标注图片不会参与训练。划分比例的选法要看你的数据量,2007张图我用7:2:1,训练集1400张左右,验证集400张左右,测试集200张左右。如果你发现训练集里车辆角度分布不均衡,比如侧面的特别多、正面的特别少,可以把验证集比例降到0.15,把多出来的图补给训练集。

4. 用YOLOv5或YOLOv8训练单类别模型:最少参数与调优顺序

4.1 为什么建议从yolov5s或yolov8n起步:算力与精度的平衡

危化品运输车辆这个任务的检测难度不算高,目标尺度偏大,角度变化集中在正侧和斜侧,没有特别小的目标。这意味着你不需要一上来就用yolov8x这种大模型,2007张图的规模喂给大模型,很快会过拟合。我一般会先用yolov5s或yolov8n跑通基线,看看验证集mAP能到多少,再决定要不要换更大的模型。对单类别检测来说,yolov8n的参数量约3.2M,推理速度快,训练一张图在单张V100上大概0.1秒到0.2秒,2007张图几十个epoch就够跑完。用yolov8x参数量接近70M,训练时间成倍增加,但精度提升有限,因为类别少、目标特征本身区分度大。

选择yolov5还是yolov8,看你的部署环境。如果你要转TensorRT或者用C++部署,yolov5的生态更成熟,网上能找到大量的部署案例。如果你只是做实验、出结果快,或者后续要接分割任务,yolov8更合适。两者训练命令大同小异,数据集配置文件格式也几乎一致,核心都是指定图片路径、标签路径和类别数。

4.2 数据集配置文件与训练命令:一个都不能错

yolov5用data.yaml,yolov8也用data.yaml,但格式略有差异。yolov8的yaml里path是指向数据集根目录的,train和val是相对路径;yolov5的yaml里train和val可以是绝对路径。这里最容易踩的坑是路径分隔符,Windows下用反斜杠,Linux下用正斜杠,如果你在Windows上写好的yaml直接拷到Linux服务器上跑,必须检查路径格式。

# data.yaml path: ./yolo_data # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 1 # 类别数,只有危化品运输车一类 names: ["dangerous_goods_vehicle"]

训练命令在yolov8下是这样:

yolo detect train \ model=yolov8n.pt \ data=data.yaml \ epochs=200 \ imgsz=640 \ batch=16 \ optimizer=SGD \ lr0=0.01 \ close_mosaic=50

参数说明方面,关键参数是关闭马赛克增强的轮数close_mosaic=50,这个值意思是训练到第50轮后关闭mosaic增强。马赛克增强能让模型在训练早期看到更多样的背景组合,但最后几十轮如果不关闭,模型会过度依赖拼接出来的假图片特征,在真实场景上表现反而下降。尤其危化品车辆的数据集只有2007张,mosaic在后期的影响更明显。lr0设成0.01是SGD优化器的常用起始学习率,如果你用AdamW,起步学习率一般要降到0.001,直接套用0.01很容易发散。

如果你用的是yolov5,训练命令变成python train.py --data data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 200 --close-mosaic 50,参数含义相同,只是传参方式从命令行选项变成了--加名称的形式。yolov8把配置集中到model、data这两个参数,其他全部是可选覆盖项,理解难度低一些。

4.3 单类别训练的三个必调参数:从默认值出发的调优路径

第一是imgsz。640是默认值,但如果你的监控画面里车辆占比大,可以提高到960或1280,代价是显存占用翻倍、训练时间变长。如果车辆在画面里占比小,比如在高速公路卡口图片中车辆只占整张图的十分之一,那imgsz反而要调大,因为小目标在低分辨率下特征丢失严重。第二是batch。16是一个保守值,单卡V100可以开到32甚至64,显存不够时先降batch而不是降imgsz,因为imgsz对精度的影响大于batch。第三是epochs。200轮对2007张图来说足够,配合早停策略,当验证集mAP连续30轮不上升就停止,能省下近一半训练时间。

训练过程中要监控的指标不是损失值,而是验证集的mAP和recall。单类别的检测任务,recall比precision重要,因为漏检一辆危化品车比误检一辆普通卡车的后果严重得多。YOLO训练日志里每个epoch会输出mAP50、mAP50-95、precision、recall四个值,你主要看mAP50和recall,mAP50-95是跨IOU阈值的平均,对单类别任务参考价值有限,因为目标本身不存在多类别混淆问题。

5. 避坑清单:坐标错乱、loss翻车与验证集泄漏

5.1 XML里的filename与实际图片名不一致,训练时数据加载报错

现象是训练刚开始就报错找不到图片,或者数据加载器警告“image not found”。原因是数据集打包者可能在xml里写的filename和JPEGImages里的实际文件名不一致,常见的差一个空格、后缀大小写不同、文件名带前缀。解决方法是写一个脚本遍历所有xml,读取filename字段,再检查对应图片是否存在,不存在的记录到log。但更稳的做法是不要依赖xml里的filename,直接用Annotations目录下的xml文件名去反查JPEGImages里的jpg同名文件,因为打包者一般不会把xml命名和jpg命名独立处理,反查成功率更高。

5.2 归一化坐标出现负值或大于1,训练时loss变成nan

现象是训练到某一轮loss突然变成nan,或验证集mAP始终为0。原因是xml里的bndbox坐标有错误,比如xmin大于xmax、ymin大于ymax,或者坐标超出了图片尺寸。这类错误在手工标注时很常见,尤其当标注工具对图片做过裁剪但xml没有同步更新。解决方法是转换脚本里对归一化坐标做一次防御性检查,任何小于0或大于1的坐标直接打印出xml文件名和原始坐标值,然后跳过这个目标框。不要自动截断到0和1,因为截断后的框位置已经不对,拿去训练只会把错误信息教给模型。

5.3 类别编号不是从0开始,模型把所有目标都当成背景

现象是训练loss正常下降,但预测结果全为空,或者把目标全预测成背景。原因是打包者给出的boxes.txt类别编号可能从1开始,也可能类别名不是唯一一个,导致模型学到的类别编号与实际不符。解决方法是打开任意一个txt标注文件,确认第一列数字只有0。如果出现非0数字,说明标注文件的类别ID和data.yaml里的nc、names配置对不上。最直接的做法是跑一个统计脚本,遍历所有txt文件,提取所有出现过的class_id,看是不是只有0。如果只有0,那就是data.yaml配置问题;如果出现其他数字,需要写脚本把全部class_id重映射到0。

5.4 划分数据集前不固定随机种子,实验结果无法复现

现象是两次训练用完全相同的参数和代码,验证集mAP却差了几个点。原因是你没有固定random.seed,导致每次运行程序时随机划分的训练集和验证集不同。模型在A验证集上表现好,在B验证集上表现差,这不是模型不稳定,而是验证集本身变了。解决方法是数据划分脚本开头写上random.seed(42),这个42不是魔法数字,你可以换成任何整数,只要固定就行。同时把划分好的文件清单导出成train.txt和val.txt存到项目目录,这样后续不管谁跑实验,用的都是同一份划分。

5.5 解压后图片直接放在根目录而不是images子目录,训练命令找不到数据

现象是训练命令执行后提示Dataset not found或no labels found。原因是数据集的zip解压后,图片可能直接在dataset根目录下,而yolov8的data.yaml里train写成images/train,路径对不上。解决方法是不要直接改yaml里的路径到处试,先看解压目录的实际结构,用ls命令确认图片到底在哪个子目录,再按实际结构修改data.yaml。这里要提醒一点,数据集提供者打包的目录结构和标准YOLO项目约定的结构可能不同,不要假设images/train一定存在,一定要亲眼确认。

6. 用统计与可视化验证标注质量:训练前花十分钟省三天

训练开始前,我强烈建议做一次标注质量可视化,不要盲目开训。方法是在yolo_data下写一个脚本,读取每张图的txt标注,把框画回图片上存成新图,随机抽50张肉眼检查一遍。重点看两件事:框是否贴合车辆轮廓、是否有漏框的车辆。框偏大偏小都还好说,模型能自己修正,漏框才是致命问题,漏框的车会被模型当成背景学进去。

更进一步的验证方法是用脚本统计所有标注框的中心点分布和宽高比。危化品运输车辆的宽高比通常集中在2到5之间,因为罐体是细长结构。如果你的标注框宽高比出现大量小于1的,说明车辆是竖直方向停放或者标注方向有问题。中心点分布可以画成散点图,如果发现某个区域完全没有中心点,比如图片上半部分一辆车都没有,那要回到原图确认是不是漏标了。

统计脚本的核心是读取txt文件里的归一化坐标,乘以图片宽高还原成像素坐标,然后计算宽高比。宽高比异常的框要单独列表输出,我遇到过标注员把罐体和车头分开标两个框的情况,这种框会严重干扰模型学习,必须合并或删除。把标注质量这关把好,训练出来的模型才不会带着坏样本的偏见,部署到真实监控场景时才不会在关键时刻漏报。这也是我做目标检测一直坚持的习惯:数据不验证,模型跑得再快也是白搭。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询