简介面向煤矿传送带异物检测场景的VOC格式标注数据集适合工业安全监测与计算机视觉目标检测方向的开发者使用。资源提供2345张煤矿传送带图像的标注信息压缩包内共2000个XML标注文件约186.53MB每个XML均按Pascal VOC标准记录目标类别与边界框坐标可直接用于训练YOLO、Faster R-CNN、SSD等常见检测模型。标注文件包含train、test等划分前缀便于分层构建训练集与验证集。目前已有884人学习下载适合需要真实工业场景数据来完成异物检测算法验证、模型调优或论文实验的工程师与研究人员。通过这批标注数据可快速搭建煤矿传送带异物识别实验环境减少数据采集与标注成本专注于检测模型的设计和改进。1. 煤矿传送带异物检测数据集2345张VOC标注图到底能训练什么模型做煤矿传送带异物检测的人手里最缺的不是模型而是能用的数据。跑井下带式输送机的场景常见异物是锚杆、托盘、木块、大块矸石偶尔还有遗落的工具目标小、背景黑、粉尘重通用数据集根本覆盖不了。这个名为“煤矿传送带异物检测数据集”的项目提供了2345张真实井下图像全部用Pascal VOC格式标记这意味着拿到手的第一步不是训练而是先读懂XML里的坐标和类别再按yolov8训练自己的数据集的标准流程转成YOLO格式。适合谁刚接手煤矿视觉项目、手里有现场相机但没标注数据的工程师以及想用公开VOC数据集快速验证异物检测方案的算法新人。别急着跑训练先把标注质量摸一遍后面能省出几周的排错时间。2. 先读懂VOC标注从文件夹结构到XML里的每个字段2.1 一个VOC工程的标准目录与xml文件长什么样Pascal VOC格式的标注核心不是图像而是每个图像对应的XML文件。这个数据集的结构严格按VOC布局组织拿到手后你会看到JPEGImages和Annotations两个主目录这很关键——训练时图像和标注是分开存的靠文件名一一对应。打开任何一个XML里面记录的远不止一个目标框。annotation folderJPEGImages/folder filenameimg_0031.jpg/filename size width1920/width height1080/height depth3/depth /size object nameanchor/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin421/xmin ymin356/ymin xmax587/xmax ymax489/ymax /bndbox /object /annotationXML里最重要的是两个结构块根部的size和每个目标的bndbox。size告诉你要处理的图像实际宽高转换坐标全靠它bndbox里的四个值是像素坐标左上角(xmin, ymin)到右下角(xmax, ymax)这个框是模型学习的原始依据。如果这个框标偏了、标小了后面做任何格式转换都救不回来。是类别名这个数据集里常见的标签包括anchor、tray、coal、rock之类具体类别数取决于标注时的定义。有一点要说明VOC格式本身没有严格的类别清单全靠标注人员当时怎么命名同一个异物在不同批次里可能叫anchor也可能叫Anchor训练时会被当成两类这是VOC数据集最常见的历史包袱。2.2 自己检查一遍标注质量四个必做的脚本化检查VOC格式看着简单真要用起来第一步不是转换是体检。标注质量决定训练上限一个越界的框就能让loss曲线呈周期性飙升。我一般会用Python脚本批量检查四个项目坐标越界、宽高为零、类别名拼写、xml与图像文件对应关系。import os import xml.etree.ElementTree as ET from PIL import Image ann_dir Annotations img_dir JPEGImages issues [] for xml_name in os.listdir(ann_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(ann_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): issues.append(f【缺图像】 {xml_name} - {img_name}) continue with Image.open(img_path) as im: real_w, real_h im.size size_tag root.find(size) xml_w int(size_tag.find(width).text) xml_h int(size_tag.find(height).text) if (xml_w, xml_h) ! (real_w, real_h): issues.append(f【尺寸不符】 {xml_name}: xml{xml_w}x{xml_h}, 实际{real_w}x{real_h}) for obj in root.iter(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) if xmin xmax or ymin ymax: issues.append(f【宽高异常】 {xml_name}: bbox({xmin},{ymin},{xmax},{ymax})) if xmin 0 or ymin 0 or xmax real_w or ymax real_h: issues.append(f【越界】 {xml_name}: bbox超出图像范围) with open(voc_check_report.txt, w) as f: f.write(\n.join(issues) if issues else No issues found.) print(f检查完成共发现 {len(issues)} 个问题报告已保存到 voc_check_report.txt)这段脚本的核心价值在于“对照真实图像尺寸检查XML”而不是信任XML里写的size。很多人踩坑的原因是标注工具中途改过图像尺寸或者数据搬运时图像被压缩过但标注文件还是旧坐标。另外xmin xmax这种判断能把零面积框直接揪出来这类框训练时会变成NaN损失。跑完脚本后优先处理“缺图像”和“越界”两类问题不做的话后面转换的全是脏数据。3. 把VOC数据集转成YOLO格式转换脚本与四个边界坑3.1 转换脚本从xml到txt的一键脚本VOC格式虽然标注信息全但主流的YOLO系模型yolov5、yolov8都不直接吃XML它们需要的是每张图像对应一个txt每行是一类一框的归一化坐标。所以拿到这个数据集后的第一个实际动作是写一个批量转换脚本。下面这个脚本是我常用的模板直接遍历Annotations目录把每个XML转成一个同名txt放进labels目录。import os import xml.etree.ElementTree as ET # 定义类别列表。注意顺序定义了label的id一旦训练开始就别乱改顺序 class_names [anchor, tray, coal, rock, wood, scraps] xml_dir Annotations img_dir JPEGImages label_dir labels # 转换后的YOLO标签存放目录 os.makedirs(label_dir, exist_okTrue) def convert_xml_to_yolo(xml_path, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: print(f警告未知类别 {name} 出现在 {xml_path}已跳过) continue class_id class_names.index(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.0 / img_width y_center (ymin ymax) / 2.0 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height # 防止float计算导致超界裁剪到[0,1]区间 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) box_width min(max(box_width, 0.0), 1.0) box_height min(max(box_height, 0.0), 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) return lines for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) # 读取对应图像尺寸。这里假设JPEGImages里的图和XML文件名一一对应 img_name xml_name.replace(.xml, .jpg) img_path os.path.join(img_dir, img_name) # 实际项目中图像尺寸优先从XML的size字段读但要注意XML可能撒谎 tree ET.parse(xml_path) root tree.getroot() img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) yolo_lines convert_xml_to_yolo(xml_path, img_width, img_height) label_path os.path.join(label_dir, xml_name.replace(.xml, .txt)) with open(label_path, w) as f: f.write(\n.join(yolo_lines)) print(f转换完成: {xml_name} - {os.path.basename(label_path)})转换脚本的成败在于三处。第一类别列表的顺序一旦确定就不能改因为转换后txt里的数字0、1、2代表的就是class_names列表的下标训练到一半改了顺序模型输出和标注就彻底错位。第二宽度和高度用的是float而不是int这样保证除法结果是浮点数。第三最后一步的min/max裁剪能兜住标注框边缘坐标等于图像边界时生成的1.000001这种越界值YOLO训练对越界标签有宽容度但最好还是裁剪干净。转换后随手打开一个txt看看0 0.4235 0.5123 0.1124 0.2331这种格式才是正常的。3.2 归一化坐标的公式与边界值处理VOC转换YOLO的坐标换算逻辑说到底就是“像素坐标除以图像尺寸”。但四个边界值最容易被忽略图像左边缘xmin0转化后x_center可能在0.01左右没问题但如果异物紧贴右下边缘公式算出的x_center可能到0.99以上而box_width加上这个中心点后超过1.0就形成了实际上的越界框。极端情况下一个标注框的xmin1920, xmax1920宽度是0训练时会产生除零或NaN。还有个隐藏坑是宽高比。如果数据集里的图像不是统一尺寸有的图是1920x1080有的是1280x720转换时按各自图的尺寸归一化模型训练时会resize到统一尺寸比如640x640这会带来宽高比变形。默认yolov8用letterbox方式保持比例不会拉伸所以只要归一化坐标正确不同尺寸的源图是可以混用的。但如果某张图是压缩过的xml尺寸和实际尺寸不一致那归一化后的框就会整体偏移这对小目标检测是致命的你会在训练时发现loss在下降但精度永远上不去。4. 异物检测数据集的五个避坑记录现象、原因、解决4.1 类别名大小写不一致训练时类别爆炸现象转换脚本跑完发现labels目录下的txt里出现了两个锚杆类别训练时置信度互相打架mAP一直很低。原因VOC标注时部分XML里写的是Anchor部分写的是anchor转换脚本把它们当成了两个类。这在多人协作标注时很常见标注工具自动补全首字母大写或者不同批次标注员习惯不一样。解决转换前先统计所有name标签的唯一值把拼写统一。直接在XML级别改比转换后再改txt稳妥用脚本把nameAnchor/name替换成nameanchor/name再跑转换。这个动作要做在转换脚本之前否则越早改越好改了之后夹带原标签的txt全得删掉重来。4.2 图像旋转标注未同步GT框全错位现象现场拍回来的图有的转了90度存储图像打开是竖着的但XML里的坐标还是横图时的坐标。训练出来模型把正常位置的东西全漏检。原因手机或防爆相机拍摄时带了EXIF旋转信息标注工具没读取或者读取逻辑不一致导致标框坐标对应的是旋转前的图像。这个坑在“从现场拿图”的数据集里出现频率很高。解决转换前先把所有图像统一方向并重存用PIL读图并exif_transpose后保存为统一方向同时重新生成XML里的size字段。标准流程是“先统一图像再标/转标注”但拿到的是别人标好的数据那就只能写脚本检测对比xml里size和图像实际宽高如果宽高互换大概率是旋转问题。4.3 异物目标过小中大尺寸锚框失效现象训练后验证集mAP看着有0.7但实际井下测试时小锚杆一个都检不出来。原因异物在传送带上的占比很小2345张图里大部分目标在32x32像素以下。yolov8默认的锚框是P3/P4/P5三层P3层负责小目标但如果训练时输入resize到640小目标的信息在多次下采样后几乎丢失。解决把训练输入尺寸调大到960甚至1280并开启多尺度训练。同时检查数据集中小目标的占比如果超过50%换用yolov8n的P2层版本或者改用mmrotate训练dota数据集那套多尺度策略虽然mmrotate是针对旋转框的但它的多尺度训练参数对水平小目标同样有效。4.4 训练集和验证集样本重叠现象训练loss正常下降val loss前期也正常但到第20个epoch后val mAP突然剧烈抖动精度不升反降。原因划分训练集和验证集时用了随机乱序但灰尘大的场景里连拍图像高度相似同一块矸石的不同帧被分到了两边造成数据泄露。模型记忆的是场景而非异物特征。解决按拍摄时间或批次划分保证同一个时间段拍的图像全进训练集验证集用另一天的数据。这个数据集有没有提供划分好的train.txt和val.txt决定了你是否需要自己按文件名规则切。如果文件名带时间戳直接用时间戳切最稳妥。4.5 井下光照不均增强策略把模型带偏现象训练时开了随机旋转90度和左右翻转验证集上效果反而比不开增强还差。原因传送带场景里异物是放在带面上的有明确的重力方向。旋转90度后异物像是在侧壁上挂着翻转后传送带运行方向变了模型学到的是“带面纹理异物位置”而不是异物本身的特征。解决数据增强只保留轻微的色彩抖动、缩放和灰度噪声关掉旋转和大角度翻转。煤矿井下图像本身的亮度差异已经够大不需要再加亮度增强。这个数据集2345张图不算多但真实场景下宁可少增强也要保住物理合理性。5. 用YOLOv8训练自己的数据集从yaml配置到mAP迭代5.1 编写数据集yaml与训练命令VOC标注转换到YOLO格式后训练的第一步是写一个数据集配置文件。yolov8用yaml描述数据路径和类别信息这个文件写错了训练会直接报错或类别对不上。我通常把转换好的labels目录和数据拆分成train/val两个子集然后让yaml指向它。# dataset.yaml # 训练集和验证集的图像路径建议写绝对路径避免相对路径在不同机器上失效 path: /home/engineer/data/conveyor_forgeign_body train: images/train val: images/val # 类别列表必须和转换脚本里的class_names顺序完全一致 names: 0: anchor 1: tray 2: coal 3: rock 4: wood 5: scraps训练命令我一般用yolov8n起步先不急着上大模型。传送带异物检测不需要识别几百类目标少、任务单一模型参数量不是瓶颈数据质量和配置才是。# 先跑30个epoch看数据有没有问题 yolo train datadataset.yaml modelyolov8n.pt epochs30 imgsz1280 batch8 device0 # 确认稳定后再上yolov8s跑完整训练开启多尺度 yolo train datadataset.yaml modelyolov8s.pt epochs120 imgsz1280 batch8 device0imgsz设1280的原因在4.3节里说了——传送带上的异物很多是小目标640分辨率下十几个像素的锚杆会被下采样吞掉。显存不够就把batch从8降到4或者先用640跑通验证全流程再上1280。前30个epoch宁可少跑一点目的是看loss曲线是否正常下降以及有没有NaN。如果出现了NaN不要去调学习率先回头查标签文件里有没有0宽度的框或者越界坐标。5.2 每次迭代后要看的指标和要做的调整训练不是把命令丢进去等结束每轮迭代都要盯几个关键指标。第一个是train/cls_loss的下降曲线如果出现台阶式下降或者突然跳高多半是学习率过高或数据里有脏标签。第二个是val/box_loss如果验证loss在某个epoch后开始回升说明过拟合了需要增强或增加数据。第三个是metrics/mAP50-95这个指标比mAP50更能反映小目标的检测质量传送带场景小目标多mAP50-95和mAP50的差值会比较大这是正常的但如果差值超过0.25就需要注意。还有一个容易被忽视的动作把验证集的预测结果可视化出来画框保存成图。不要只盯指标曲线因为mAP是数字看不到模型到底把锚杆检成了托盘还是矸石。每轮迭代后抽几张典型的图看预测框和真实框的位置关系。框偏左半格说明输入尺寸和锚框比例不匹配框漏在尾部说明训练时目标被裁切。这些细节在数字上看不出来但图上一眼就能定位。这个阶段也用得上yolov5训练自己的数据集的常见流程很多在v5上验证过的经验可以直接搬比如预热训练、余弦退火、以及把置信度阈值设低一点做二次筛选。这2345张图的数据量不够撑起大规模预训练但足够做迁移学习微调。6. 收尾技巧先跑通100张冒烟集再决定要不要全量投喂数据科学的翻车往往不在模型而在“第一次训练就全量灌数据”。我的习惯是用这个2345张的煤矿传送带异物检测数据集时先随机抽100张图标注不动直接转换成YOLO格式训练30个epoch。冒烟测试的意义不是验证精度而是验证全链路VOC转换脚本有没有产生空txt、装箱脚本是否能在1280分辨率下正常运行、模型的loss能否正常收敛。这100张图如果能跑通后面全量数据只是在等时间而不是在赌配置。冒烟集跑完后把模型的预测结果和真实框叠加导出成图片人眼扫一遍。这个动作只要十分钟但能直接把4.1到4.5的所有坑暴露出来因为100张图里只要有1张脏数据预测图上就能看出来。我踩过的最大一次翻车就是一个类别的名称里带了空格从XML一直传到yaml模型训练了200个epoch后才发现全部检错。玄学一点说数据集工程就是“先信自己写的脚本再信模型最后信数据”顺序反了时间就全搭进去了。全量训练不再赘述如果冒烟通过正式训练的配置直接复用即可只是把epoch拉长到120到150。希望帮到你。本文还有配套的精品资源点击获取