简介煤与脉石检测数据集基于YOLOv9格式标记面向智能矿山、矿物分选及目标检测算法研究者可用于训练煤和脉石两类目标的识别与定位模型标签简洁适合从零搭建检测流程。压缩包共含201个文件其中100张原始JPG图片与100个TXT标注文件一一对应TXT内记录目标框坐标与类别ID另附1个YAML配置文件定义类别名称和训练路径整体包体仅2.99MB便于快速下载与反复实验。该数据集已有564人学习下载图像涵盖不同光照、角度和粒度条件下的煤与脉石样本数据未经预处理保留了真实场景的复杂度方便使用者自行设计数据增强方案。将文件直接放入YOLOv9工程并调整YAML路径即可开始训练也可作为基线数据集评测其他检测算法性价比高。1. 煤与脉石检测数据比模型更卡脖子在选煤厂和井下皮带运输场景煤和脉石的分选一直是最耗人力的环节。老师傅盯着皮带看几小时眼睛花了漏检率就压不下来难点从来不在模型真正卡脖子的是数据。带标记的煤和脉石目标检测数据集不好凑原始图片干净、标注框不乱飞的更难找。手头这份 yolov9 标记的煤和脉石检测数据集压缩包里全是原始图片解压就是标准 YOLO 格式煤和脉石两类目标框得整整齐齐。它的价值不在省标注工时而是提供了一批能直接拿去训练的干净样本。做选煤厂视觉分选、皮带矸石识别的工程师拿它当基线数据新手拿它练 YOLOv9 训练流程也够得着。2. 先看懂这份数据YOLOv9 标注格式与目录逻辑2.1 目录结构与标签文件长什么样zip 解压后第一件事不是急着训练而是先把目录结构摸清楚。YOLO 系列目标检测数据集基本都遵守同一套约定图片和标签分开存放每张图片对应一个同名 .txt 标签文件。这份数据也不例外常见结构如下coal_gangue_dataset/ ├── images/ │ ├── coal_001.jpg │ ├── coal_002.jpg │ ├── gangue_001.jpg │ └── ... ├── labels/ │ ├── coal_001.txt │ ├── coal_002.txt │ ├── gangue_001.txt │ └── ... └── data.yaml标签文件每一行对应一个目标框格式是五个字段class_id x_center y_center width height。前两个是类别 id 和框中心点的归一化坐标后两个是框的宽和高所有数值都除以了图片宽高范围在 0 到 1 之间。随手打开一个标签文件内容形如0 0.5123 0.4801 0.3211 0.2754 1 0.7201 0.6211 0.1833 0.2109第一行是煤目标类别 0中心点位于图片宽度的 51.23%、高度的 48.01% 处框占图片宽度的 32.11%、高度的 27.54%第二行是脉石目标类别 1。两行之间换行分隔文件末尾建议保留换行符部分解析器对缺失末尾换行的文件会少读最后一行这个细节很少有人提遇到样本数对不上时可以少排查十分钟。如果标注工具导出的是像素坐标换算方式是中心点 x 除以图片宽度、框宽除以图片宽度y 和高度同理。这里要提醒一句归一化坐标是浮点数精度取决于标注工具导出时保留的小位数。只有四位小数的话一张 1920×1080 的图里误差可能到几十个像素小目标检测时框的贴合度会受影响。我拿到数据集后习惯先看标签里坐标的小数位数少于四位的会在训练前做一轮坐标重算避免模型在小目标上学到偏差。如果 zip 里没有现成的 data.yaml自己补一个也很快第 3 章会给完整写法。动手改任何东西之前先随机抽二十张图和对应的 txt 人工对一遍把边界框画回图上看框贴不贴目标。数据集的标注质量决定训练上限后面调参只能在标注质量的下限附近挣扎这一步省不得。画框验证用一段简单脚本就能做import cv2, random from pathlib import Path img_files list(Path(images).glob(*.jpg)) p random.choice(img_files) label_p Path(labels) / (p.stem .txt) img cv2.imread(str(p)) h, w img.shape[:2] for line in label_p.read_text().strip().splitlines(): cls, xc, yc, bw, bh map(float, line.split()) x1, y1 int((xc - bw / 2) * w), int((yc - bh / 2) * h) x2, y2 int((xc bw / 2) * w), int((yc bh / 2) * h) color (0, 255, 0) if int(cls) 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(check.jpg, img)这段脚本把归一化坐标换算回像素坐标绿框是煤、红框是脉石。随机多跑几次每次看框的中心点是不是落在目标中间、框边是不是贴着目标轮廓。抽查二十张以上没发现明显偏移再谈训练的事。2.2 为什么选 YOLOv9 格式而不是 VOC 或 COCO先说结论YOLO 格式是纯文本一行一个框不给多余信息对嵌入式部署和边缘端推理最友好。VOC 的 XML 和 COCO 的 JSON 在迁移时都要写一轮解析脚本而 YOLOv9 官方仓库的 train_dual.py 直接读的就是这种 txt不需要中间转换层训练前少一道出错的工序。实际工程里三种格式的维护成本差异很大格式载体解析成本增量修改成本适用阶段YOLO txt纯文本最低一行一框最低改一行即可训练与部署全链路VOC XMLXML 树中需遍历节点中需维护树结构标注工具交换COCO JSON单文件 JSON高需维护 id 映射高动一个框要改数组评测比赛矿业现场的迭代节奏决定了 YOLO 格式是唯一合理的选择。选煤厂的皮带工况不是一成不变煤种换了、皮带速度调了、光照角度变了检测模型就得跟着补数据重训。YOLO 格式改一个框就是改一行文本VOC 的 XML 要维护 annotation 树COCO 的 JSON 要改 annotations 数组还容易把 image_id 搞乱现场工程师维护几轮就崩溃。这份数据直接用 YOLOv9 格式等于把「数据集与训练框架的适配成本」先抹平了。另外训练框架对格式的兼容性也值得考虑。YOLOv9 官方仓库、ultralytics 那一套、以及很多边缘推理框架的 demo默认都是读 YOLO txt。数据格式统一意味着同一个数据集可以在不同框架之间来回切换方便对比哪个版本在这个场景下精度更高、速度更快。如果你后续想试试 YOLOv8 或者 RT-DETR这份数据的标签文件可以原样复用不用重标。2.3 原始图片的价值标注质量可以追溯「全是原始图片」这句话乍看平淡实际含金量不低。很多公开数据集给的是二次压缩图或经过马赛克增强、MixUp 的合成图训练出来的模型拿到现场一测就翻车因为现场的皮带光照、粉尘、运动模糊跟训练数据的分布对不上。原始图片保留了相机直出的光照条件和分辨率你能看到煤块表面的反光、脉石颗粒的纹理、皮带上的煤粉背景这些正是现场推理时真正要面对的干扰。这类图片还有一个隐藏价值可追溯。当你发现某个误检反复出现可以回到原始图上确认是标注错了还是模型特征学习偏了。如果是标注问题直接修正对应 txt 里的坐标即可不需要重做整个数据集。比如模型总是把某一小片高反光区域误判成煤回到原始图一看发现那里其实是水流反光标注里没有这类负样本。对做工程落地的人来说这份数据的可追溯性比框得有多精细更重要。另外原始图片做数据增强的效果也比压缩图好。亮度扰动、对比度扰动、高斯噪声这些增强操作在原始图上做不会放大压缩伪影在二次压缩图上做增强伪影会被当成特征学进模型一到现场就糊。所以「全是原始图片」这个属性直接决定了它适不适合做训练基线。拿到数据后还可以顺手统计一下图片分辨率的分布如果大部分图在 640×640 以上训练时用 imgsz 640 就不会白白丢失细节。3. 让数据集跑起来训练前的准备与校验3.1 环境准备与解压检查训练 YOLOv9 需要 PyTorch 环境。我一般用 conda 建独立环境Python 3.8 到 3.10 之间都行PyTorch 1.13 以上配合 CUDA 11.7 或 12.1 是官方仓库验证过的组合。装好之后克隆 yolov9 官方仓库再把数据集解压到仓库外部的独立目录不要让数据集文件夹嵌进仓库里免得 git 操作把大文件误提交。环境初始化命令conda create -n yolov9 python3.9 conda activate yolov9 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/WongKinYiu/yolov9.git pip install -r yolov9/requirements.txt解压前先做两件事第一确认压缩包完整可以看 zip 内文件总数第二确认解压后的路径不含中文和空格YOLOv9 的 dataloader 在 Windows 上对非 ASCII 路径处理很玄学报错往往还指向别的地方。Linux 下我习惯这样做unzip yolov9标记的煤和脉石检测数据集.zip -d coal_gangue_dataset cd coal_gangue_dataset find . -name *.txt | wc -l第一行解压到纯英文目录名第二行统计标签文件数量。这个数字必须和图片数量对得上对不上就是压缩包传输损坏或目录结构缺失后面训练一定出问题。我习惯在解压后立刻做这一步而不是等训练报错再去排查等到训练阶段才发现数据不全排查成本至少翻三倍。提示压缩包在 Windows 下解压容易把目录名搞成带中文的形态解压后统一重命名成 images、labels 这种纯英文目录能省掉后面一整类编码报错。3.2 编写 data.yaml 与目录整理数据集要能被 YOLOv9 读取核心是一个 data.yaml。煤和脉石是两类目标类别定义如下path: /data/coal_gangue_dataset train: images/train val: images/val nc: 2 names: 0: coal 1: ganguepath 指向数据集根目录train 和 val 是相对于根目录的子路径。nc 是类别数names 是类别名列表顺序必须和标签文件里的 class_id 严格对齐——标签里 0 就是 coal1 就是 gangue写反了训练不会报错但推理结果会完全错乱。如果原数据集没有划分 train 和 val需要自己先切。YOLOv9 也支持在 yaml 里直接写 train.txt 和 val.txt 的文件路径列表但我更推荐按文件夹划分先把图片按 8:2 随机分到 images/train 和 images/val标签文件同步复制到对应目录结构更清晰换机器迁移也方便。划分脚本要注意保持图片和标签的路径对应关系一致cd coal_gangue_dataset mkdir -p images/train images/val labels/train labels/val python - EOF import random, shutil from pathlib import Path imgs sorted(Path(images).glob(*.jpg)) random.seed(42) random.shuffle(imgs) split int(len(imgs) * 0.8) for i, img in enumerate(imgs): part train if i split else val label Path(labels) / (img.stem .txt) shutil.move(str(img), fimages/{part}/{img.name}) shutil.move(str(label), flabels/{part}/{label.name}) EOF划分时注意按整个数据集随机而不是按文件名顺序切避免同一场景的连续帧全部落进训练集或验证集导致验证指标虚高。random.seed(42) 固定随机种子保证每次划分结果可复现这在后续对比实验时非常重要。data.yaml 里的 path 字段建议写成绝对路径训练脚本的工作目录经常变相对路径容易踩空。3.3 校验脚本标签、图片、类别一次对齐我每次拿到新数据集都会先跑一遍校验脚本用 Python 就能做。脚本要覆盖三个维度图片和标签是否一一对应、标签格式是否合法、坐标和类别是否越界from pathlib import Path img_dir Path(coal_gangue_dataset/images) label_dir Path(coal_gangue_dataset/labels) imgs {p.stem for p in img_dir.iterdir() if p.suffix.lower() in (.jpg, .jpeg, .png)} labels {p.stem for p in label_dir.iterdir() if p.suffix .txt} print(仅图片无标签:, len(imgs - labels)) print(仅标签无图片:, len(labels - imgs)) for label_file in sorted(label_dir.glob(*.txt)): for line in label_file.read_text(encodingutf-8).strip().splitlines(): parts line.split() if len(parts) ! 5: print(f格式错误: {label_file.name}: {line}) cls_id int(parts[0]) if cls_id not in (0, 1): print(f类别越界: {label_file.name}: {line}) values list(map(float, parts[1:])) if any(v 0 or v 1 for v in values): print(f坐标越界: {label_file.name}: {line})这段脚本做了四件事。第一计算只有图片没有标签、只有标签没有图片的样本数量这两类都是训练时的定时炸弹前者会让对应图片在训练时被跳过后者会让数据读取报错。第二检查每行是否为五段格式段数不对说明标注文件被污染常见原因是 Excel 打开过 txt 后存成了带制表符的格式。第三检查类别 id 是否越界煤和脉石只有 0 和 1 两个合法值。第四检查归一化坐标是否都在 0 到 1 之间大于 1 的情况经常出现在手工标注导出时除错了分辨率不校验的话训练 loss 会居高不下而且很难定位原因。脚本输出为空才算数据这一关过了。脚本里有个细节值得说用 read_text 时要显式指定 encodingutf-8Windows 下不指定会默认用 GBK 读遇到 UTF-8 编码的标签文件可能抛 UnicodeDecodeError。反过来如果标签文件被 Windows 记事本存成了带 BOM 的形式第一行的首字符会变成不可见字符int() 转换会直接失败这种问题只能靠脚本的 try-except 兜住后人工定位。校验通过后再看一眼前面画框脚本的输出确认坐标换算和实际目标位置没有系统性偏差。4. 训练与评估参数设置、指标解读与模型导出4.1 数据划分与训练命令数据校验通过后先确认目录结构是 images/train、images/val、labels/train、labels/val 四件套然后写训练命令。以 YOLOv9 官方仓库为准训练入口是 train_dual.pypython train_dual.py \ --data coal_gangue_dataset/data.yaml \ --cfg models/detect/yolov9-c.yaml \ --weights yolov9-c.pt \ --batch-size 16 \ --imgsz 640 \ --epochs 150 \ --device 0几个关键参数说明一下。imgsz 640 是 YOLOv9 的默认训练分辨率皮带上的煤块和脉石通常占画面比例较大640 够用不需要上到 1280那只会让显存翻倍而精度提升有限batch-size 16 在 24G 显存的卡上是稳妥值8G 显存就降到 8weights 用官方预训练权重做迁移学习煤和脉石虽然在 COCO 里没有对应类别但预训练权重里学到的纹理和边缘特征依然有效从零初始化训练完全不可取loss 曲线会非常难看。常用参数的选择逻辑汇总如下参数推荐值调整依据imgsz640目标占画面比例大就不需要提高显存受限时优先降低batch-size8168G 显存用 824G 用 16梯度爆炸时再降epochs100150看验证集 mAP 是否 plateau平台期超过 20 个 epoch 就停lr00.0050.01迁移学习用 0.01loss 震荡改 0.005optimizerSGD 或 AdamW小数据集优先 SGD收敛稳定大 batch 用 AdamW 省心注意显存溢出时优先降 batch-size不要动 imgsz。batch-size 只影响梯度稳定性imgsz 直接影响模型的感受野适配改错了重训成本完全不同。训练过程的日志和权重会落到 runs/train/ 目录。如果训练中断可以用 --resume 接续YOLOv9 会自动读取最近一次 checkpoint。训练时长方面150 epoch 在单张 24G 卡上跑这份量级的数据集大概几小时内能出结果数据集如果只有几千张图100 epoch 就够收敛跑多了反而过拟合。4.2 训练过程监控盯哪几个指标训练时我一般盯三个东西loss 曲线、验证集的 mAP50、以及 best.pt 和 last.pt 的行为。YOLOv9 的训练日志里 box_loss、cls_loss、dfl_loss 三条线前 30 个 epoch 快速下降是正常的。如果 loss 前期震荡剧烈大概率是学习率设置问题默认的 lr00.01 对迁移学习稍微偏高可以改成 0.005 重跑或者加一个 warmup 让前几个 epoch 用小学习率预热。mAP50 是煤和脉石检测最该看的指标因为现场的判定标准是「框住了就算对」对 IoU 的严格程度不像自动驾驶那么苛刻。mAP50 到 0.9 以上模型基本可用了mAP50-95 反映的是框的精确程度现场质检如果要求框必须贴合目标轮廓才需要盯它。如果验证集 mAP50 明显低于训练集说明过拟合优先加数据增强而不是调模型结构。训练结束后看 runs/train/exp 目录下的 results.png里面的 PR 曲线和混淆矩阵能直观告诉你脉石是不是被漏检或者煤是不是被误判成脉石。曲线尾部掉点就是少数类样本不够的典型信号。4.3 模型导出与部署前验证训练完成后第一步是把 PyTorch 权重转成 ONNX再按部署端需求转 TensorRT 或 OpenVINO。YOLOv9 官方仓库自带导出脚本python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --imgsz 640导出后强烈建议做一次精度对齐验证用同一张图分别跑 PyTorch 模型和 ONNX 模型对比输出的检测框坐标和置信度偏差超过 0.5% 就要检查是否在导出时改了预处理参数。这一步看起来多余实际上部署端翻车十有八九出在预处理不一致——训练时用 letterbox 加归一化导出端少一步推理结果就全偏了。我见过不止一次有人在导出 ONNX 时把归一化除 255 这步漏掉模型在本地测试一切正常推到边缘盒子上结果一团浆糊最后查了一天发现是预处理没对齐。导出验证还有个土办法用现场的皮带照片直接测不要用训练集里的图。训练集里的图模型早就见过了测出来的结果没有参考价值现场新拍的图才能暴露预处理差异和分布偏移问题。如果现场测得的结果和验证集指标差很多先怀疑预处理再怀疑数据分布这个排查顺序能省下大把时间。5. 避坑清单这份数据集最容易翻车的五个点5.1 坑一类别名不统一标签全部作废现象训练能跑但验证时模型把煤和脉石混成一团precision 和 recall 双双极低看检测结果完全分不清两类。原因data.yaml 里 names 的顺序和标签文件里的 class_id 对不上或者有某段脚本手工改过部分 txt 的类别号导致一部分标签的 0 是煤另一部分的 0 是脉石。解决用第 3 章的校验脚本跑一遍全量标签确认每个文件的类别 id 只在 0 和 1 之间再人工抽查十张图的标签内容和画面内容是否一致。类别错位的代价是训练完才发现重训成本几个晚上这笔时间花在训练前非常值。我还会顺手检查一下有没有类别 id 写成负数或者 0.0 这种浮点形态的情况解析时 int() 转换会静默出错。5.2 坑二JPG 的 EXIF 旋转信息导致标注错位现象人工看图片是对的目标位置也看得清楚但训练出的模型预测框整体偏移斜着放置的煤块识别尤其差竖构图图片几乎全错。原因部分相机和手机拍摄的 JPG 带 EXIF Orientation 字段OpenCV 的 imread 默认不处理这个字段读出来的像素矩阵是「未旋转」的而标注工具按「旋转后」的视觉画面标了坐标两者错位。现场工程师常用手机补拍样本这个坑在矿业场景非常常见。解决统一预处理用 PIL 读取时按 exif_transpose 把图片转正后覆盖保存再重新生成对应标签from PIL import Image, ImageOps from pathlib import Path for p in Path(images).glob(*.jpg): img Image.open(p) img ImageOps.exif_transpose(img) img.save(p, quality95)处理完后重跑校验脚本重点抽查竖构图图片。如果用了转正后的图片记得把原图备份到另一个目录避免后续发现转正有误时没法回退。5.3 坑三煤多脉石少类别不平衡现象训练出来煤的 recall 很高脉石的 recall 明显偏低漏检集中在脉石上而且脉石的目标框经常只框住一半。原因选煤厂皮带上煤是主流脉石样本天然少数据集里两类的框数量可能差好几倍模型在多数类上学得更充分少数类只能学到模糊的特征。解决先做统计再决定策略看每类的框数量from collections import Counter from pathlib import Path counter Counter() for txt in Path(labels).glob(*.txt): for line in txt.read_text().strip().splitlines(): counter[int(line.split()[0])] 1 print(counter)输出形如 Counter({0: 5213, 1: 1731})0 是煤1 是脉石。差两倍以内还能靠训练时调 cls loss 权重硬扛差三倍以上就得上手段复制少数类样本、对脉石做轻度几何增强、或者用少数类单独重采样。我一般先做复制少数类到接近一比二的比例再跑一轮看脉石 recall 变化效果不明显再加增强。复制少数类时要复制整个图片的标注文件而不是只复制几行否则同一张图里煤的样本也被重复计算。5.4 坑四zip 解压后路径带中文现象训练加载数据时报错找不到图片报错信息指向一个不存在的路径但打开文件管理器路径明明存在。原因压缩包或解压出的文件夹名带了中文Windows 下 YOLOv9 的 dataloader 对非 ASCII 路径支持不稳定Linux 下一般没事Windows 下概率翻车报错信息还经常是误导性的 No such file or directory。解决解压后立刻把整个项目路径改成纯英文例如 coal_gangue_dataset训练脚本、数据集、输出目录全部不要出现中文。这个习惯我吃了两次亏才养成第一次折腾半小时最后把路径改英文立刻就好了。如果是 Windows 下用资源管理器解压还要留意 zip 内文件名的编码部分压缩工具解压出的中文文件名是乱码同样会引发路径问题。5.5 坑五空标签文件与损坏图片混在数据集里现象训练到一半突然报错某个 batch 加载失败或者 loss 直接变成 NaN 后一路不恢复。原因部分 txt 文件是空的里面没有任何检测目标部分图片文件在拷贝或压缩时损坏dataloader 读到损坏图片时抛异常个别情况是图片其实是 0 字节文件。解决跑一遍清洗脚本把空标签文件和打不开的图片都移到一个 backup 目录不直接删除防止误删后又想找回from PIL import Image from pathlib import Path bad [] for p in Path(images).iterdir(): try: with Image.open(p) as im: im.verify() except Exception: bad.append(p.name) print(f损坏图片: {p.name}) for p in Path(labels).glob(*.txt): if p.stat().st_size 0: print(f空标签: {p.name})这里用 im.verify() 而不是 im.load()前者只校验文件完整性不加载像素速度快得多几千张图几秒就跑完。空标签文件直接看 size 是否为 0。这两类脏数据清掉后训练过程会稳定很多至少不会出现训练到第几十个 epoch 突然被打断的恶心情况。6. 进阶把这份基线数据做成增量迭代的起点6.1 增量训练现场新图片怎么补进来基线数据集训出可用模型后真正的挑战是现场工况变化。煤种一变、皮带速度一调、光照角度一换原模型精度就会掉。我的做法是持续收集现场抓拍的原始图片攒到两三百张就做一轮增量训练。增量训练不是从零重新标注而是用当前模型先自动标注一遍人工只修正错框和漏框再混合旧数据一起训练。这样每轮标注成本压到最低模型始终跟着现场工况走不会出现训完三个月就明显退化的窘境。6.2 硬例挖掘用置信度筛出该补的样本增量训练最怕补进去的都是简单样本模型学不到新东西。我一般用置信度做硬例筛选把现场图片喂给当前模型把置信度低于 0.6 或者出现漏检、误检的样本挑出来人工确认这些才是真正值得补标注的样本。如果部署端是 ONNX我会复用同一套推理接口做筛选脚本思路如下import onnxruntime as ort from pathlib import Path sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name for img_path in Path(field_images).glob(*.jpg): img preprocess(img_path) # 与训练一致letterbox 归一化 RGB out sess.run(None, {input_name: img})[0] obj_conf out[0, 4, :] if obj_conf.size 0 or obj_conf.max() 0.6: print(f候选硬例: {img_path.name}, 最高置信度{obj_conf.max():.3f})obj_conf 取的是输出张量里目标置信度那一维的最大值低于阈值说明模型对这一帧完全没有把握。低置信度意味着目标外观和训练集差异大或者存在遮挡和模糊。把这些样本补进增量集模型提升最明显。这一招在矿业场景比堆测试集指标有用得多。6.3 现场验证的三个习惯动作模型训完部署前我习惯固定做三件事。第一拿现场录制的十分钟皮带视频做连续帧推理看有没有单帧闪烁和连续漏检静态图指标再好动态场景才是真实工况。第二统计误检框的位置分布如果误检集中在皮带边缘的托辊区域说明训练数据里缺这类背景样本需要补采集负样本。第三把推理结果和人工分选记录对齐看一天确认模型漏检集中在哪种煤质条件下这是下一轮数据采集的方向。从那以后我每次拿到新数据集都强制走一遍完整流程解压校验、类别统计、画框抽查、训练监控、硬例挖掘五步缺一不可翻车概率直线下降。希望这份数据集和这套流程能帮到你少走我走过的弯路。本文还有配套的精品资源点击获取