简介这份煤矸石识别数据集面向煤炭分选、矿物识别方向的算法开发者与高校研究人员用于训练和验证煤炭、煤矸石、高岭石三类目标的检测或分类模型适合具备一定深度学习基础、需要真实场景样本的读者。资源包共105个文件以102张现场采集的原始jpg图片为主体另含2个txt说明文件与1个json标注文件整体约2.27MB体积轻便便于快速下载与本地调试。标注采用COCO JSON格式可直接对接主流目标检测框架的数据加载流程省去格式转换环节。图片均为现场实拍覆盖不同光照与堆放状态能较好反映实际工况下的类间差异有助于提升模型对煤炭与煤矸石、高岭石混淆样本的区分能力。目前已有426人学习下载可作为小样本训练、数据增强或迁移学习实验的基础素材也适合用于验证标注流程与评估指标。1. 煤矸石识别数据集上手102 张现场图与 COCO JSON 标注能跑出什么皮带机旁的分选工盯着监控屏煤和矸石混在一起往下走肉眼分到第三个小时基本就靠猜。这是我第一次接触煤矸石识别任务时的真实场景也是这份数据集存在的理由。它包含 102 张现场采集的原始图片标注格式为 COCO JSON覆盖煤炭、煤矸石、高岭石三类目标。文件名里那串_png.rf.xxxx.jpg的哈希后缀说明图片经过了一轮导出或增强处理但内容仍是现场工况下的真实画面不是实验室摆拍。这份资源适合三类人想快速验证煤矸石分选模型可行性的算法工程师、需要现场数据做迁移学习起点的研究者、以及带学生做工业视觉课题的高校教师。102 张不算多但胜在类别定义清晰、标注格式标准能直接喂给 Detectron2、MMDetection 或 YOLO 系列框架。下面从数据本身讲到训练落地再到我踩过的坑一步步拆开。2. 拆开 COCO JSON三类目标的标注结构与字段含义拿到一个 COCO 数据集第一件事不是急着训练而是把 JSON 读明白。这份数据的标注文件遵循标准 COCO 格式顶层包含images、annotations、categories三个核心数组。categories里定义了三个类别煤炭、煤矸石、高岭石。images记录每张图的文件名、宽高和 id。annotations则是每个标注框的bbox、category_id、image_id和area。2.1 用 Python 快速统计类别分布与框尺寸在训练之前我习惯先跑一段统计脚本看看三类目标的数量是否均衡、框的尺寸分布是否合理。这能提前判断要不要做重采样或锚框调整。import json import numpy as np from collections import Counter # 加载 COCO 标注文件路径按实际存放位置修改 with open(annotations/instances.json, r, encodingutf-8) as f: coco json.load(f) # 建立 category_id 到类别名的映射 cat_map {c[id]: c[name] for c in coco[categories]} print(类别映射:, cat_map) # 统计每个类别的标注框数量 cat_counter Counter(ann[category_id] for ann in coco[annotations]) for cid, name in cat_map.items(): print(f{name}: {cat_counter.get(cid, 0)} 个框) # 统计所有框的宽高分布判断目标尺度 widths [ann[bbox][2] for ann in coco[annotations]] heights [ann[bbox][3] for ann in coco[annotations]] print(f框宽: min{min(widths):.1f}, max{max(widths):.1f}, mean{np.mean(widths):.1f}) print(f框高: min{min(heights):.1f}, max{max(heights):.1f}, mean{np.mean(heights):.1f}) # 统计每张图平均标注数量 img_ann_count Counter(ann[image_id] for ann in coco[annotations]) print(f每图平均标注数: {np.mean(list(img_ann_count.values())):.2f})这段脚本输出三个关键信息类别框数、框的宽高范围、每图平均标注数。如果某一类框数明显偏少比如高岭石只有个位数训练时就要考虑过采样或类别权重。框的宽高均值能帮你判断默认锚框是否合适——如果平均框只有 30 像素宽而模型默认锚框最小是 32那就要调小。2.2 可视化验证标注框是否对齐统计数字看完还得肉眼确认标注框有没有偏移。COCO 的bbox格式是[x, y, width, height]原点在左上角。有些导出工具会写成[x1, y1, x2, y2]直接训练会导致框错位。import cv2 import os # 取前 5 张图做可视化抽查 img_dir images/ sample_ids [img[id] for img in coco[images][:5]] for img_id in sample_ids: img_info next(i for i in coco[images] if i[id] img_id) img_path os.path.join(img_dir, img_info[file_name]) image cv2.imread(img_path) if image is None: print(f读取失败: {img_path}) continue # 找出该图所有标注框 anns [a for a in coco[annotations] if a[image_id] img_id] for ann in anns: x, y, w, h [int(v) for v in ann[bbox]] cv2.rectangle(image, (x, y), (x w, y h), (0, 255, 0), 2) label cat_map[ann[category_id]] cv2.putText(image, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(fcheck_{img_id}.jpg, image) print(f已保存 check_{img_id}.jpg)跑完打开生成的图片重点看三件事框是否紧贴目标边缘、类别标签是否和画面内容一致、有没有漏标的大块目标。现场采集的图片常有粉尘遮挡或光照不均标注员容易把边界模糊的区域标大或标小。如果发现某类框普遍偏大训练时 IoU 阈值就要相应放宽。提示COCO 的bbox是左上角加宽高不是左上角加右下角。如果你用的框架要求xyxy格式转换时记得x2 x w别直接拿bbox[2]当右下角横坐标。3. 从 COCO 到 YOLO 格式转换脚本与训练配置COCO JSON 通用性强但 YOLO 系列训练时用 txt 标注更顺手。这一章把转换、划分、配置串起来给一份能直接跑的流程。3.1 写一个 COCO 转 YOLO 的转换脚本YOLO 的标注格式是每行class_id x_center y_center width height全部归一化到 0 到 1 之间。转换时要注意类别 id 的映射——COCO 的 category_id 不一定从 0 开始连续YOLO 要求从 0 开始。import json import os from pathlib import Path with open(annotations/instances.json, r, encodingutf-8) as f: coco json.load(f) # COCO category_id 可能不连续重新映射为 0 起始的连续 id cat_ids sorted([c[id] for c in coco[categories]]) cat_id_to_yolo {cid: idx for idx, cid in enumerate(cat_ids)} print(类别重映射:, cat_id_to_yolo) # 建立 image_id 到文件信息的索引 img_id_to_info {img[id]: img for img in coco[images]} # 按 image_id 分组标注 from collections import defaultdict img_anns defaultdict(list) for ann in coco[annotations]: img_anns[ann[image_id]].append(ann) label_dir Path(labels) label_dir.mkdir(exist_okTrue) for img_id, anns in img_anns.items(): info img_id_to_info[img_id] w_img, h_img info[width], info[height] lines [] for ann in anns: x, y, bw, bh ann[bbox] # 归一化并转换为中心点坐标 x_center (x bw / 2) / w_img y_center (y bh / 2) / h_img nw bw / w_img nh bh / h_img cls cat_id_to_yolo[ann[category_id]] lines.append(f{cls} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}) # 文件名去掉扩展名换成 .txt stem Path(info[file_name]).stem with open(label_dir / f{stem}.txt, w) as f: f.write(\n.join(lines)) print(f已生成 {len(img_anns)} 个标注文件)脚本里cat_id_to_yolo是关键它把 COCO 原始类别 id 重新映射成 0、1、2。归一化时用图片实际宽高不是固定值。如果你的图片尺寸不一致这一步必须逐图计算不能偷懒用统一尺寸。3.2 划分训练集与验证集102 张图做训练验证集不能太小否则指标波动大。我一般按 8:2 划分同时保证每个类别在验证集里都有出现。随机划分容易让某类目标全跑进训练集所以用分层抽样更稳。import random from collections import defaultdict random.seed(42) # 固定随机种子保证可复现 # 按每张图包含的类别做分层 img_classes defaultdict(set) for ann in coco[annotations]: img_classes[ann[image_id]].add(ann[category_id]) # 简单策略按类别数量排序后交替分配 all_ids sorted(img_classes.keys(), keylambda i: len(img_classes[i]), reverseTrue) val_ids set() for i, img_id in enumerate(all_ids): if i % 5 0: # 每 5 张抽 1 张进验证集 val_ids.add(img_id) train_ids [i for i in all_ids if i not in val_ids] print(f训练集: {len(train_ids)} 张, 验证集: {len(val_ids)} 张) # 生成 train.txt 和 val.txt内容为图片绝对路径 with open(train.txt, w) as f: for img_id in train_ids: f.write(os.path.abspath(os.path.join(images, img_id_to_info[img_id][file_name])) \n) with open(val.txt, w) as f: for img_id in val_ids: f.write(os.path.abspath(os.path.join(images, img_id_to_info[img_id][file_name])) \n)每 5 张抽 1 张102 张大约得到 20 张验证集。这个比例在小数据集上比较平衡既不会让训练集太少也能让验证指标有统计意义。固定random.seed(42)是为了下次重跑时划分一致方便对比不同模型的实验结果。3.3 YOLOv8 训练配置与参数说明转换完直接上 YOLOv8写一个 data.yaml 指向图片和标注路径。# data.yaml path: /home/user/coal_dataset train: train.txt val: val.txt names: 0: coal 1: gangue 2: kaolinite训练命令用命令行启动关键参数逐个说。yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch8 \ lr00.001 \ patience30 \ augmentTrue \ projectruns/coal \ nameexp1modelyolov8n.pt选 nano 版是因为数据量小大模型容易过拟合。imgsz640是默认值如果原图分辨率远大于 640可以试 960但显存要够。batch8在 102 张图上大约每轮 10 个 iteration太小会导致梯度噪声大太大又容易过拟合。patience30表示 30 轮验证指标不提升就早停小数据集上这个值别设太小否则还没收敛就停了。augmentTrue开启默认增强包括 HSV 抖动、随机翻转、马赛克增强对现场采集图片的粉尘和光照变化有针对性。注意如果高岭石类别的框数远少于煤炭和煤矸石训练时会出现类别不平衡。常见做法是在 data.yaml 里加cls_pw参数或者用copy_paste增强专门复制少数类目标。4. 现场采集图片的预处理粉尘、光照与尺寸对齐现场图片和标准数据集最大的差别在于成像条件不可控。粉尘会让目标边缘模糊光照不均会让同一类目标在不同区域呈现不同色调图片尺寸也可能不统一。这一章讲三个预处理动作每个都对应一个具体问题。4.1 直方图均衡化对抗光照不均皮带机上的照明灯往往从一侧打光导致图片半边亮半边暗。直接训练会让模型把亮度当成类别特征换一个光照条件就翻车。我一般用 CLAHE限制对比度自适应直方图均衡化做预处理它在小窗口内做均衡不会把噪声放大。import cv2 def apply_clahe(image_path, output_path, clip_limit2.0, tile_size(8, 8)): img cv2.imread(image_path) # 转 LAB 空间只对 L 通道做均衡避免颜色偏移 lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimitclip_limit, tileGridSizetile_size) l_eq clahe.apply(l) lab_eq cv2.merge((l_eq, a, b)) result cv2.cvtColor(lab_eq, cv2.COLOR_LAB2BGR) cv2.imwrite(output_path, result) # 批量处理 import glob for p in glob.glob(images/*.jpg): apply_clahe(p, p.replace(images/, images_clahe/))clip_limit2.0控制对比度增强幅度值越大增强越猛但超过 3.0 容易把粉尘噪声也放大。tile_size(8,8)表示把图片分成 8x8 的块分别均衡块越小局部适应性越强但计算量也越大。只对 L 通道操作是为了不改变色相煤炭和煤矸石的颜色差异得保留住。4.2 统一图片尺寸与 letterbox 填充YOLO 训练时会把图片缩放到imgsz指定尺寸但缩放方式影响长宽比。如果原图是 1920x1080直接缩到 640x640 会把目标压扁。常见做法是 letterbox等比例缩放后用灰边填充到正方形。def letterbox(img, new_shape640, color(114, 114, 114)): h, w img.shape[:2] # 计算缩放比例取小的那个保证长边不超过 new_shape r min(new_shape / h, new_shape / w) new_unpad (int(round(w * r)), int(round(h * r))) dw new_shape - new_unpad[0] dh new_shape - new_unpad[1] # 上下左右各填充一半 top, bottom dh // 2, dh - dh // 2 left, right dw // 2, dw - dw // 2 img_resized cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) img_padded cv2.copyMakeBorder(img_resized, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor) return img_padded, r, (left, top)填充色用 (114,114,114) 是 YOLO 的默认灰训练和推理保持一致。返回的缩放比例r和偏移量(left, top)在推理时要把检测框映射回原图坐标别忘了。4.3 粉尘遮挡下的标注框修正粉尘遮挡会导致标注员把框画大把粉尘区域也包进去。训练时模型学到的是“大框”推理时遇到清晰目标反而框不准。我的做法是统计框内像素方差方差过大的框说明包含大量背景需要收紧。import numpy as np def check_bbox_quality(img, bbox, var_threshold800): x, y, w, h [int(v) for v in bbox] roi img[y:yh, x:xw] if roi.size 0: return False # 计算灰度方差方差大说明框内混杂背景 gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) var np.var(gray) return var var_threshold # 对每个标注框做检查方差超阈值的标记出来人工复核var_threshold800是经验值煤炭和煤矸石表面纹理丰富方差本身就不低设太小会误杀。这个脚本的输出不是自动修正而是生成一个待复核列表让人工决定是收紧框还是保留。全自动修正容易把正确标注也改坏。5. 训练避坑小数据集上的五类翻车现场102 张图训练检测模型坑比大数据集多得多。下面五条是我和同行交流时反复听到的翻车记录每条按现象、原因、解决写。5.1 验证集 mAP 高但现场测试全错现象训练日志里 mAP0.5 到了 0.85拿现场新拍的图片测试模型把煤矸石全标成煤炭。原因验证集和训练集来自同一批图片光照、角度、粉尘条件高度相似。模型学到的是这批图片的特定特征不是煤矸石的本质特征。102 张图如果全是一个时间段、一个机位拍的过拟合几乎必然。解决划分验证集时按拍摄时段或机位分组让验证集包含训练集没见过的条件。如果数据本身多样性不足用强增强模拟条件变化比如随机调整亮度、对比度、添加高斯噪声。我一般会把hsv_v调到 0.5 以上hsv_s调到 0.7逼模型忽略颜色和亮度的表面差异。5.2 高岭石类别 AP 始终为 0现象煤炭和煤矸石的 AP 正常高岭石一直是 0混淆矩阵里高岭石全被预测成煤矸石。原因高岭石和煤矸石在灰度图上非常接近都是灰黑色纹理也相似。如果标注时两类边界不清晰模型无法区分。另外高岭石框数可能只有十几二十个样本太少。解决先检查标注一致性把高岭石和煤矸石的标注图并排看确认标注员自己分得清。如果确实难分考虑合并两类或者引入额外特征——比如用多光谱图像但这份数据只有可见光。样本少的话用copy_paste增强把高岭石目标复制到其他图片上每轮随机贴几个能有效增加少数类样本。5.3 训练 loss 震荡不收敛现象前 20 轮 loss 下降之后开始上下震荡验证 loss 反而上升。原因学习率太大或者 batch size 太小导致梯度噪声大。102 张图 batch8 时每轮只有约 10 个 iteration梯度方向波动明显。解决把lr0从 0.01 降到 0.001加cos_lrTrue让学习率余弦衰减。如果显存够把 batch 提到 16但要注意小数据集上大 batch 可能让模型在少数几个样本上过拟合。另一个办法是加warmup_epochs5前 5 轮用极小学习率预热让模型先稳定下来。5.4 推理时框重叠严重现象一张图里同一个目标被检出多个框NMS 后仍然有重叠。原因小数据集上模型对目标边界不确定同一个目标在不同锚框上都给出高置信度。另外如果标注框本身有重叠模型会学到重复检测。解决调低 NMS 的 IoU 阈值从默认 0.7 降到 0.5让重叠框更容易被抑制。同时检查标注文件把 IoU 大于 0.8 的同类框合并。YOLO 推理时加iou0.5参数即可。5.5 图片文件名哈希导致路径错乱现象转换脚本跑完labels 文件夹里文件名和 images 对不上训练时找不到标注。原因这份数据的文件名带_png.rf.xxxx.jpg后缀Path.stem会保留.rf.xxxx部分而图片实际扩展名是.jpg。如果脚本里用stem做匹配两边不一致。解决统一用Path(file_name).stem提取文件名确保图片和标注用同一套命名。或者在转换时把文件名里的.rf.段去掉重命名图片和标注。我一般写一个normalize_filename函数把哈希后缀统一截掉只保留序号部分。def normalize_filename(name): # 把 012_png.rf.1fcc9085833b5c1b93d64d030804ac7e.jpg 转为 012.jpg stem Path(name).stem if _png.rf. in stem: stem stem.split(_png.rf.)[0] return stem .jpg6. 用 102 张图做迁移学习冻结策略与验证技巧数据量小的时候从零训练基本没戏迁移学习是唯一出路。但怎么冻结、冻结多少层、验证时看什么指标有几个具体技巧。6.1 分层冻结与解冻策略YOLOv8 的 backbone 前几层学的是边缘和纹理这些特征对煤矸石识别同样有用应该冻结。后几层学的是语义特征和具体类别强相关需要解冻微调。我的做法是前 10 层冻结 50 轮之后全部解冻再训 100 轮。from ultralytics import YOLO model YOLO(yolov8n.pt) # 第一阶段冻结 backbone 前 10 层 for i, (name, param) in enumerate(model.model.named_parameters()): if i 10: param.requires_grad False model.train(datadata.yaml, epochs50, imgsz640, batch8, lr00.001, namestage1) # 第二阶段解冻全部层用更小学习率微调 for param in model.model.parameters(): param.requires_grad True model.train(datadata.yaml, epochs100, imgsz640, batch8, lr00.0005, namestage2)第一阶段学习率可以稍大因为只调后面几层。第二阶段解冻全部后学习率减半避免把预训练权重破坏得太厉害。如果第二阶段验证指标反而下降说明解冻太多可以只解冻最后 5 层试试。6.2 用混淆矩阵和 PR 曲线定位问题训练完别只看 mAP混淆矩阵能告诉你哪两类在互相误判。YOLO 训练结束后会在runs/目录下生成confusion_matrix.png打开看对角线以外的值。如果煤矸石和高岭石之间的误判率高说明这两类特征确实接近需要回到标注环节确认边界定义。PR 曲线看的是每个类别的查准率和查全率平衡点。煤炭的 AP 高但高岭石低说明模型对高岭石的特征学习不足。这时候可以单独把高岭石的图片抽出来看模型在哪些图上漏检是目标太小还是遮挡太严重。6.3 现场测试的验证清单训练指标好看不代表现场能用。我每次交付前会走一遍这个清单检查项具体操作通过标准新图片测试用训练集没见过的现场图推理三类目标都能检出无明显漏检光照变化同一目标在不同光照下测试类别判断一致置信度波动小于 0.2遮挡测试目标被粉尘部分遮挡仍能检出框不严重偏移推理速度在目标硬件上测 FPS满足产线节拍要求类别混淆人工核对高岭石和煤矸石误判率低于 10%这份数据集只有 102 张现场测试的图片最好另外采集不要从训练集里挑。如果现场测试误判率高优先检查标注一致性而不是急着调模型参数。我见过太多人反复改学习率、换模型结构最后发现是标注员把煤矸石和高岭石标反了。从那以后我每次拿到新数据集都强制先跑一遍标注可视化随机抽 20 张图逐张核对确认没问题再开始训练。这个习惯帮我省下了至少三次白跑的实验。希望帮到你。本文还有配套的精品资源点击获取