简介面向煤矸石分选、智能矿山监控及目标检测教学场景的图像识别数据集聚焦煤炭、煤矸石、高岭石三类目标的识别任务可支撑选煤厂自动排矸、矸石含量分析等实际应用。资源包共105个文件压缩后仅2.27MB包含102张现场采集的原始jpg图片、1份COCO JSON格式标注文件和2个txt说明文件其中JSON标注文件可直接导入主流检测框架txt文件用于补充类别与数据组织信息。该数据集目前已有426人学习下载图片来自真实作业现场光照、角度和物料堆叠状态多样有助于提升模型在复杂工况下的泛化能力。标注格式规范统一省去自行标注与格式转换的繁琐环节可快速用于YOLO、Faster R-CNN、MMDetection等模型的训练与验证适合矿山智能化研究、算法入门及项目原型开发。1. 煤矸石识别数据集在传送带分拣场景里怎么用选矿传送带上的视觉分拣最难的不是算法而是标注。102张现场采集原图、COCO JSON格式、煤炭/煤矸石/高岭石三类标签这个规模属于典型少样本检测场景但场景高度一致背景、光照、拍摄角度都来自同一条产线模型不必泛化到陌生环境只要把标注吃透、格式转对完全能训出一个固定工位可用的检测器。拿到新数据集先别急着跑训练脚本。COCO JSON是存储格式不是训练格式102张图划分不当验证集只有十几张mAP抖动到无法判断模型好坏。下面按落地顺序展开拆读标注结构、可视化检查框质量、转YOLO格式、小样本调参、混淆矩阵验证每步都可复用。2. COCO JSON标注结构拆解煤矸石数据集的三个顶层字段2.1 为什么现场采集数据优先选COCO JSON而不是单类TXTCOCO JSON从MS COCO数据集演化而来成为目标检测、实例分割通用的标注交换格式。煤矸石识别这套数据选它有三个实际理由。第一类别定义写在categories里煤炭、煤矸石、高岭石的id与名称可以随时调整不影响标注主体第二images和annotations解耦一张现场图上可以同时出现煤堆和矸石块多个框挂到同一个image_id下结构上天然支持多目标第三它是框架中立格式从YOLO到MMDetection再到Detectron2都能解析未来换模型不用重标一遍。现场采集的原始图片还要考虑另一个问题相机分辨率、压缩质量、文件名规则不统一。COCO要求images里显式记录width和height等于强制标注工具导出时做了一次尺寸登记后续转YOLO的归一化坐标才拿得到精确分母。如果发现转换后的txt里出现大于1的坐标优先怀疑这里填错了而不是训练代码有问题。2.2 images、annotations、categories三个数组的字段映射数组字段对应煤矸石场景的含义imagesid / file_name图ID与原文件名一张现场采集图一条记录imageswidth / height像素宽高做格式转换时的分母annotationsid / image_id框ID与所属图片一对多关系靠它建立annotationscategory_id与categories的id关联三分类标签就落在这里annotationsbbox[x, y, w, h]左上角坐标加宽高像素单位annotationsarea框面积保留该字段方便统计目标尺度annotationsiscrowd0为单目标框1为密集簇煤堆密集时建议用0避免融合categoriesid / name1煤炭、2煤矸石、3高岭石bbox的坐标系是COCO最容易踩的坑。(x, y)是左上角(w, h)向右下延伸而Labelme导出的JSON用的是[x_min, y_min, x_max, y_max]YOLO用的又是中心点坐标。拿到这个数据集先抽两个框手算一遍w是否约等于右下角x减左上角xh同理确认是COCO原生格式后再批量处理。2.3 用Python解析并校验标注提前发现缺字段问题import json from pathlib import Path from collections import Counter def inspect_coco(ann_path: str) - None: with open(ann_path, r, encodingutf-8) as f: data json.load(f) cat_id2name {c[id]: c[name] for c in data[categories]} img_id2info {img[id]: img for img in data[images]} label_counter Counter() empty_boxes 0 out_of_range 0 for ann in data[annotations]: name cat_id2name.get(ann[category_id], unknown) label_counter[name] 1 img img_id2info[ann[image_id]] x, y, w, h ann[bbox] if w 0 or h 0: empty_boxes 1 if x w img[width] 1 or y h img[height] 1: out_of_range 1 print(各类别框数量:, dict(label_counter)) print(空框数:, empty_boxes, 越界框数:, out_of_range) inspect_coco(annotations.json)这段脚本在正式训练前做静态体检。json.load负责把文件解析成dict如果文件里有拖尾逗号或缺失逗号会抛JSONDecodeError报错提示的行号经常不准更快的验证是在命令行执行jq . annotations.json /dev/nulljq能直接指出语法错误的具体位置。类别计数用Counter统计能快速看出三类样本的比例为后续是否调整损失权重提供依据空框和越界框检查用于拦截标注工具导出时的异常值。注意如果读取端爆出failed to deserialize the json body into the target type: input: missing field一类的强类型反序列化错误大概率不是JSON文件本身损坏而是代码里的字段名与文件键名不一致比如把bbox写成了box。优先对字段名少去怀疑文件。2.4 类别id起始值从1开始的COCO到从0开始的YOLOCOCO的categories按惯例从1编号YOLO的类别索引从0开始。转换脚本必须做一次显式偏移把category_id减去最小id或者按排序后的categories重新编号。如果直接拿category_id当YOLO class id用所有类别整体错位一位训练不会报错但验证集mAP会惨不忍睹且这个错非常难排查。另一个容易忽略的点是id连续性标注工具如果删除过类别categories里可能出现1、3而没有2排序后枚举出来的索引和原始id对不上同样会导致标签错位。解析时用sorted(data[categories], keylambda c: c[id])保证顺序稳定再按枚举值做映射。3. 煤矸石标注可视化与现场图片质检102张图的框质量检查3.1 用OpenCV在原图上叠加标注框102张图靠人眼一张张看最可靠。写一个可视化脚本把bbox画回原图输出到独立目录一屏一屏翻比直接训练后看预测结果快得多。import cv2 import json from pathlib import Path ID2NAME {1: coal, 2: gangue, 3: kaolinite} COLORS {1: (0, 0, 255), 2: (0, 255, 0), 3: (255, 0, 0)} def draw_boxes(coco_path: str, img_dir: str, out_dir: str) - None: data json.loads(Path(coco_path).read_text(encodingutf-8)) Path(out_dir).mkdir(parentsTrue, exist_okTrue) img_id2anns {} for ann in data[annotations]: img_id2anns.setdefault(ann[image_id], []).append(ann) for img in data[images]: src cv2.imread(str(Path(img_dir) / img[file_name])) if src is None: print(warning: 图片不存在, img[file_name]) continue for ann in img_id2anns.get(img[id], []): x, y, w, h [int(v) for v in ann[bbox]] color COLORS[ann[category_id]] cv2.rectangle(src, (x, y), (x w, y h), color, 3) label ID2NAME[ann[category_id]] cv2.putText(src, label, (x, max(0, y - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) cv2.imwrite(str(Path(out_dir) / img[file_name]), src) draw_boxes(annotations.json, raw_images, vis_output)这里ID2NAME用英文而不是中文原因是cv2.putText内置的Hershey字体不支持中文直接写中文会渲染成问号。COLORS按类别区分颜色coal用红、gangue用绿、kaolinite用蓝翻图时通过色块就能快速定位同类分布。脚本里加了src is None的防御分支一张图读不出来就跳过并在控制台告警避免整个流程中断。框宽高转int是有意为之。COCO JSON允许浮点坐标但画线函数需要整数坐标直接int()截断不会影响检查效果如果后续要基于画框截图做二次标注修正则保留原始浮点值避免拿截断后的坐标去改标注。3.2 现场场景里煤炭、煤矸石、高岭石的视觉混淆点检查时重点看三类容易标错的区域。第一类煤块表面有镜面反射高光区域在照片里呈白色长条形的反光容易被误框成高岭石实际白色区域和煤块是同一物体需要按轮廓边缘收紧边界框第二类煤矸石里常见的泥岩与煤伴生灰黑程度接近标注框往往横向覆盖两种材料这种框不是错但会导致模型学到混合特征理想做法是沿颜色分界切开第三类高岭石呈白色或浅黄色土状压在深色矸石上时框容易画大把周围煤屑一并括进去。判断框质量的标准只有一个边界是否贴着物体外缘而不是有没有严丝合缝。现场作业图普遍有粉尘和运动模糊框比物体大5%以内可以接受超过20%就需要修正。3.3 统计小目标与密集区域决定是否切图训练102张原图如果是1920x1080直接训练没问题如果来自工业相机分辨率可能到4000x3000矸石只占画面一小块。写个统计脚本按面积占比判断小目标占比import json from pathlib import Path data json.loads(Path(annotations.json).read_text(encodingutf-8)) img_id2size {img[id]: (img[width], img[height]) for img in data[images]} small, large_img 0, 0 for ann in data[annotations]: w, h ann[bbox][2], ann[bbox][3] iw, ih img_id2size[ann[image_id]] if w * h / (iw * ih) 0.01: small 1 if iw * ih 2_000_000: large_img 1 print(小目标框数量:, small, 大尺寸图片数:, large_img)COCO官方小目标定义为面积小于32x32像素但工业相机分辨率高一个煤块占600x500像素物理上不小像素上很大用固定像素判断会严重失实。面积占比小于1%作为相对小目标标准更适合现场图。如果小目标占比高于三成训练时把imgsz提到960甚至1280或者先把大图切成512的patch训练再推理拼接如果大图数量多但小目标少保持imgsz640即可。4. COCO JSON转YOLO格式把102张现场图组织成可训练的数据集4.1 YOLO标签格式与目录约定YOLO训练自己的数据集要求的标签不是JSON而是每张图对应一个同名txt每行一个目标类别索引、中心点x、中心点y、宽、高全部归一化到0到1。这种格式的优点是不依赖原图尺寸同一份标签文件可以适配任意输入分辨率缺点是信息冗余少坐标边界一旦丢失很难人工发现。目录按images和labels两套平行结构组织这是YOLOv5、YOLOv8等主流框架的统一约定datasets/gangue/ ├── images/ │ ├── train/ # 82张现场图 │ └── val/ # 20张现场图 ├── labels/ │ ├── train/ # 对应txt │ └── val/ └── data.yaml4.2 转换脚本category_id偏移与坐标归一化import json from pathlib import Path def coco_to_yolo(coco_json: str, label_root: Path) - None: data json.loads(Path(coco_json).read_text(encodingutf-8)) img_id2info {img[id]: img for img in data[images]} img_id2anns {} for ann in data[annotations]: img_id2anns.setdefault(ann[image_id], []).append(ann) cat_sorted sorted(data[categories], keylambda c: c[id]) cat_id2yolo {c[id]: i for i, c in enumerate(cat_sorted)} label_root.mkdir(parentsTrue, exist_okTrue) for img in data[images]: lines [] for ann in img_id2anns.get(img[id], []): yolo_id cat_id2yolo[ann[category_id]] x, y, w, h ann[bbox] cx (x w / 2) / img[width] cy (y h / 2) / img[height] nw w / img[width] nh h / img[height] if not (0.0 cx 1.0 and 0.0 cy 1.0): print(warning: 归一化坐标越界, img[file_name]) continue lines.append(f{yolo_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: txt_name Path(img[file_name]).with_suffix(.txt) (label_root / txt_name).write_text(\n.join(lines) \n, encodingutf-8) coco_to_yolo(annotations.json, Path(datasets/gangue/labels))cat_sorted先按原id排序再用enumerate生成从0开始的yolo_id这步同时解决id从1起始和id不连续两个问题。坐标转换公式是COCO的左上角加宽高换算成YOLO中心点中心x等于x加w一半再除以图宽。归一化越界写成warning而不是直接抛异常因为102张图中偶尔一张框比图还大跳过单框保留其他框能维持数据集完整。代码里保留了6位小数。这个精度对640分辨率训练足够折算回像素误差小于0.001像素没必要用更多位。注意转换前先确认标注框坐标与images里的width、height是同一张图的尺寸。部分标注工具导出前做过缩略图预览会把坐标缩放到预览尺寸直接用它除以原始宽高会让所有框漂移。4.3 train/val划分与data.yaml写法cd datasets/gangue python - EOF import random from pathlib import Path img_root Path(images) label_root Path(labels) imgs sorted(img_root.glob(*.jpg)) sorted(img_root.glob(*.png)) random.seed(42) random.shuffle(imgs) split int(len(imgs) * 0.8) for phase, img_list in [(train, imgs[:split]), (val, imgs[split:])]: (img_root / phase).mkdir(parentsTrue, exist_okTrue) (label_root / phase).mkdir(parentsTrue, exist_okTrue) for img in img_list: img.rename(img_root / phase / img.name) txt label_root / img.with_suffix(.txt).name if txt.exists(): txt.rename(label_root / phase / txt.name) EOF这个划分脚本把20%的图留作验证。random.seed(42)固定随机种子保证每次执行结果一致复现实验时不会因为划分变化而得到不同基线。划分前先确认labels与images的文件名一一对应如果出现只有标签没有图的孤儿txt训练时会报found no images或标签数量不匹配的错。data.yaml:path: /home/trainer/datasets/gangue train: images/train val: images/val nc: 3 names: 0: coal 1: gangue 2: kaolinitenames顺序必须与转换脚本里的cat_id2yolo映射一致。这个文件里填的类别索引和转换出来txt里的第一个数值是对齐的如果这里写错一个训练不报错但混淆矩阵会显示两列预测总是错位。4.4 最小训练命令与首次验证yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ patience40modelyolov8s.pt会先下载预训练权重COCO数据集预训练模型的backbone对边缘和纹理特征的提取能力对小样本场景比随机初始化可靠得多。就算本地只有CPU102张图也能跑完几十轮慢但能出结果。patience40表示连续40轮验证指标不上升就早停防止数据太少导致后续训练完全过拟合。训练结束后用同一份data.yaml验证yolo detect val datadata.yaml modelruns/detect/train/weights/best.pt5. 102张图的小样本调参煤炭与煤矸石的分类边界怎么拉干净5.1 预训练权重与freeze策略的选择YOLOv8s在COCO上预训练的权重对矿石场景最有用的是backbone部分前置网络学会了边缘、纹理、颜色渐变等通用特征这些特征对煤和矸石同样有效。小样本下最怕的是网络容量大而数据少模型把训练集的噪声细节背下来。常见做法是冻结backbone前10层只训练深层的检测头yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ patience40 \ freeze10freeze10冻结的是YOLOv8的backbone前10个模块从stem到C2f的大部分特征提取层都在内。冻结带来的直观变化是训练更快、loss下降更慢但更稳验证mAP通常不会虚高。如果第一轮训练跑完发现mAP50在0.5以下且train和val差距很大把freeze调回0或5让深层也参与微调。5.2 数据增强参数传送带场景哪些增强该开、哪些该关参数默认值建议值说明mosaic1.00.54图拼接对小目标有利但102张图片重复拼接出虚拟场景煤的镜面反射纹理被截断degrees0.05传送带角度固定旋转超过5°会制造现实中不存在的姿态hsv_h / hsv_s / hsv_v0.015 / 0.7 / 0.40.02 / 0.5 / 0.3煤矸石识别高度依赖颜色与灰度增强过大让煤发白、矸石发黑fliplr0.50.5水平翻转不改变物体形态传送带场景安全scale0.50.3缩放过大把中等煤块变成小目标干扰面积占比统计mosaic在YOLOv8里默认打开对102张的小数据集尤其需要谨慎。它的初衷是增加背景多样性和小目标出现频率但样本量小时同一张图会反复参与拼接模型反而学会了对拼接缝的响应。实践做法是mosaic0.5同时把mixup保持默认关闭。注意数据增强参数相互耦合不建议只调一个。缩小scale的同时把mosaic降下来否则拼接后的目标尺度分布会变得不真实。5.3 类别不均衡先加样本再调损失权重三分类数据如果框数量差距大比如煤炭350框、煤矸石200框、高岭石80框YOLOv8的默认cls权重会让模型牺牲少数类来换多数类AP。常见做法是按框数倒数归一化设cls权重但只有102张图时调权重的作用远小于补充样本。现实中移动一下拍摄机位或换一批矸石翻面拍摄高岭石样本量可能翻倍。如果实在无法补充可以在预处理阶段把含高岭石的图片复制几份并做轻微平移再用同样的txt标签训练相当于给少数类加了离线增强效果比调权重更直接。5.4 早停、mAP与loss三条曲线的协同判断102张图训练单看一条曲线会误判。box_loss持续下降但val loss上升是过拟合的明确信号mAP50和mAP50-95同时攀升才代表真正的泛化。训练结束后查看runs/detect/train/results.png关注最后几十轮的曲线走势而不是最终数值。早停靠patience40兜底但如果mAP50在第120轮附近就已经平台后续50轮只是震荡可以把epochs直接降到150省下重跑对照实验的时间。指标健康状态异常信号mAP500.6以上验证集低于训练集超过10个百分点mAP50-95高于mAP50的60%远低说明框定位不稳定precision / recall0.7上下两者差距大说明置信度阈值不合适6. 验证阶段的实操技巧用混淆矩阵定位煤与矸石的误检6.1 从val输出里读混淆矩阵训练完执行yolo detect val \ datadata.yaml \ modelruns/detect/train/weights/best.pt \ conf0.25验证过程会把混淆矩阵输出到runs/detect/val/confusion_matrix.png。这张图上对角线越亮越好行列分别对应真实类别和预测类别。对煤矸石识别来说最需要看的是gangue落在coal列上的值如果明显偏离对角线说明模型把矸石当成了煤炭先不急着调阈值回到第3章的框质量检查看这类误检框是不是本身把煤和矸石框在了一起。6.2 高岭石与煤矸石边界误检的修正手段混淆矩阵里高岭石对应行列上非对角线元素偏亮通常来自两类情况一是框内混入两种矿物二是高岭石的土状表面和矸石泥岩表面在灰度纹理上太接近。前者是标注问题把框沿颜色分界切开重标后者是特征问题尝试在增强里把hsv_h提到0.03给高岭石输入更多颜色变化或者增加一个近红外通道输入——如果现场部署相机支持煤矸石分选在短波红外波段区分度高得多。6.3 用置信度阈值卡误检的边界yolo predict \ modelruns/detect/train/weights/best.pt \ sourcetest_samples/ \ conf0.4 \ saveTrue把conf从0.25提到0.4通常能压掉一部分低置信度误检但分拣场景里漏检一台矸石的损失大于多一次空抓阈值不宜设太高。跑两轮predict分别看conf0.3和conf0.5输出的现场样例图对比漏检与误检的数量再决定部署阈值。如果两轮结果差异不大说明模型在固定工位场景下已经收敛问题不在阈值而在训练数据的边界框质量。本文还有配套的精品资源点击获取
