
简介这份PDF文档围绕基于CNN卷积神经网络的煤矸石自动分选研究展开面向从事深度学习、机器学习与计算机视觉方向的研究生、算法工程师及工业智能化应用开发者。内容针对传统煤矸石分选依赖灰度信息、易受色差与光照条件限制的问题提出利用卷积神经网络对煤块与矸石图像纹理特征进行多层次提取并分类输出测试准确率达到92%涉及卷积层、池化层、全连接层等核心结构及图像处理、数据建模相关知识。资源包共1个PDF文件大小约1.98MB便于直接阅读与文献引用。目前已有338人学习浏览适合希望了解CNN在工业分选场景落地思路、借鉴图像分类建模流程与实验设计的读者参考。1. 煤矸石自动分选为什么绕不开 CNN从人工手选到卷积神经网络的落地现场在选煤厂待过的人都知道煤矸石分选这个环节有多熬人。皮带机高速跑着矸石和块煤混在一起靠人工在拣矸皮带两侧盯着一个班下来眼睛酸得流泪分选准确率还随疲劳程度直线下降。这几年不少厂子开始上机器视觉方案核心思路就是用工业相机拍下皮带上的物料流让算法逐块判断是煤还是矸石再驱动气阀把矸石吹走。而在这类方案里CNN卷积神经网络几乎是绕不开的选择——它不需要你手工设计煤块和矸石的纹理特征直接从图像里学。这篇笔记就围绕「基于CNN卷积神经网络的煤矸石自动分选」这个方向把从数据集构建、模型选型、训练调参到现场部署的完整路径拆开讲清楚适合正在做选煤厂智能化改造的工程师、做煤矸识别课题的研究生以及想把这套方案落到实际产线上的技术负责人。2. 煤矸石图像数据集怎么建从现场采样到可训练样本2.1 为什么煤矸石识别对数据集格外挑剔煤和矸石在图像上的差异远没有想象中那么泾渭分明。块煤表面有亮煤、暗煤、镜煤之分反光强弱不一矸石里也有炭质泥岩、砂质泥岩、砂岩等不同类型颜色从灰黑到灰白跨度很大。更麻烦的是选煤厂皮带上的物料是湿的喷淋降尘之后表面挂水膜反光会直接改变图像灰度分布。如果数据集只采了某一种煤种、某一种光照条件下的样本模型到了现场就会大面积翻车。常见做法是在皮带机上方固定工业相机配合条形光源分别在空载、满载、不同煤种、不同湿度条件下采集原始视频再从视频里抽帧。抽帧时要注意相邻帧之间差异极小如果全部拿来当训练样本会造成严重的数据泄漏——训练集和验证集里出现几乎相同的图片验证准确率虚高上线就露馅。我一般会按时间间隔抽帧比如每 30 帧取一张保证样本之间的独立性。2.2 数据采集与标注的实操步骤现场采集建议用全局快门工业相机卷帘快门在皮带运动时会产生果冻效应煤块边缘拖影会严重影响标注质量。分辨率不用追求极致但单块物料的像素面积至少要保证在 64×64 以上否则 CNN 学不到有效纹理。光源用高亮条形光从两侧打减少阴影干扰。采集完之后是标注。煤矸石分选通常是分类任务也可以做成检测任务。如果只判断整幅图里有没有矸石分类就够了如果要定位每一块矸石的位置再驱动气阀就需要目标检测。下面是一个用 Python 做数据增强和划分的脚本示例import os import shutil import random from PIL import Image, ImageEnhance, ImageFilter # 原始数据目录结构raw/coal/ 和 raw/gangue/ RAW_DIR raw OUT_DIR dataset SPLIT_RATIO {train: 0.7, val: 0.15, test: 0.15} def augment_image(img): 对单张图像做随机增强模拟现场光照和湿度变化 # 随机调整亮度模拟不同光照条件 img ImageEnhance.Brightness(img).enhance(random.uniform(0.7, 1.3)) # 随机调整对比度模拟干湿煤表面差异 img ImageEnhance.Contrast(img).enhance(random.uniform(0.8, 1.2)) # 随机旋转小角度模拟物料在皮带上的姿态变化 angle random.uniform(-15, 15) img img.rotate(angle, fillcolor(128, 128, 128)) return img def split_dataset(): for cls in [coal, gangue]: src os.path.join(RAW_DIR, cls) files [f for f in os.listdir(src) if f.endswith((.jpg, .png))] random.shuffle(files) n len(files) n_train int(n * SPLIT_RATIO[train]) n_val int(n * SPLIT_RATIO[val]) splits { train: files[:n_train], val: files[n_train:n_train n_val], test: files[n_train n_val:] } for split, flist in splits.items(): dst os.path.join(OUT_DIR, split, cls) os.makedirs(dst, exist_okTrue) for fname in flist: img Image.open(os.path.join(src, fname)).convert(RGB) img.save(os.path.join(dst, fname)) # 仅对训练集做增强验证集和测试集保持原始分布 if split train: aug augment_image(img) aug.save(os.path.join(dst, aug_ fname)) if __name__ __main__: split_dataset()这段脚本做了三件事按 7:1.5:1.5 划分训练、验证、测试集只对训练集做增强避免验证集分布被污染增强手段包括亮度、对比度扰动和小角度旋转模拟现场光照波动和物料姿态变化。参数方面亮度范围 0.7 到 1.3 是我在多个选煤厂现场试出来的经验区间再大就会出现过曝或欠曝的无效样本旋转角度控制在正负 15 度以内因为皮带上的物料不会出现大角度翻转。注意数据增强只对训练集做验证集和测试集必须保持原始分布否则你评估出来的指标没有参考意义。2.3 数据集规模与类别平衡的边界煤矸石数据集不需要像 ImageNet 那样动辄百万张。实际项目中每类 2000 到 5000 张原始样本配合增强后达到每类 1 万张左右就足够训练一个效果不错的 CNN。但类别平衡很关键——实际皮带上煤块远多于矸石如果按真实比例采样矸石样本可能只占 5%模型会倾向于全部预测为煤。解决办法有两种一是对矸石类做过采样二是在损失函数里加类别权重。我一般倾向后者因为过采样容易导致矸石类过拟合。3. CNN 模型选型与训练从 ResNet 到轻量化网络的取舍3.1 煤矸石分选该选什么骨干网络煤矸石识别本质上是一个细粒度二分类问题煤和矸石的差异体现在纹理和局部反光特性上不需要检测 ImageNet 里那些语义级别的差异。所以骨干网络不需要太深。ResNet-18 或 ResNet-34 在大多数场景下已经够用再深反而容易过拟合而且推理速度会拖累产线节拍。如果部署环境是工控机加 GPUResNet-18 是稳妥选择。如果要用边缘设备比如 Jetson 系列或者瑞芯微 NPU 做推理MobileNetV3 或 ShuffleNetV2 更合适参数量小、推理快精度损失在煤矸石这个任务上通常不超过 2 个百分点。我做过对比测试在自建的 8000 张煤矸石数据集上ResNet-18 的验证集准确率约 96.5%MobileNetV3-Small 约 94.8%但后者推理速度快了将近 4 倍。产线节拍要求高的话这个精度换速度是划算的。3.2 训练脚本与关键参数设置下面是一个基于 PyTorch 的训练脚本用 ResNet-18 做迁移学习。迁移学习很重要煤矸石数据集规模有限从零训练很难收敛到理想效果。import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, models, transforms from torch.utils.data import DataLoader # 数据预处理训练集做增强验证集只做标准化 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(dataset/train, transformtrain_tf) val_ds datasets.ImageFolder(dataset/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) # 加载预训练 ResNet-18替换最后的全连接层 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 2) # 二分类煤 / 矸石 model model.cuda() # 类别权重矸石样本少时加大其损失权重 class_weights torch.tensor([1.0, 2.0]).cuda() criterion nn.CrossEntropyLoss(weightclass_weights) # 分层学习率骨干网络用小学习率全连接层用大学习率 optimizer optim.SGD([ {params: model.layer1.parameters(), lr: 1e-4}, {params: model.layer2.parameters(), lr: 1e-4}, {params: model.layer3.parameters(), lr: 1e-4}, {params: model.layer4.parameters(), lr: 1e-4}, {params: model.fc.parameters(), lr: 1e-3}, ], momentum0.9, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) best_acc 0.0 for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.cuda(), labels.cuda() optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.cuda(), labels.cuda() outputs model(imgs) preds outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fEpoch {epoch1}, Val Acc: {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_gangue_cnn.pth) print(fBest Val Acc: {best_acc:.4f})几个关键参数需要展开说。类别权重设为 [1.0, 2.0]是因为现场矸石比例通常低于煤加大矸石类的损失权重可以缓解类别不平衡。分层学习率的设计思路是ResNet 的卷积层已经在大规模数据集上学到了通用纹理特征微调时用小学习率保护这些特征全连接层是随机初始化的需要大学习率快速收敛。CosineAnnealingLR 让学习率按余弦曲线衰减比阶梯衰减更平滑在煤矸石任务上通常能多涨 0.5 到 1 个百分点。提示batch_size 设为 32 是显存和梯度稳定性的折中。如果显存不够可以降到 16但要把学习率相应调小否则训练容易震荡。3.3 训练过程中的监控指标与早停策略煤矸石分选任务里准确率不是唯一要看的指标。如果矸石被误判为煤意味着矸石混入精煤产品直接影响煤质和售价如果煤被误判为矸石意味着精煤损失影响回收率。两者代价不同所以要看混淆矩阵特别是矸石类的召回率。我一般要求矸石召回率不低于 95%在这个前提下再优化整体准确率。早停策略方面如果验证集损失连续 5 个 epoch 不下降就停止训练。煤矸石数据集规模不大通常 20 到 30 个 epoch 就能收敛再训下去只会过拟合。学习率方面初始值设 1e-3 对全连接层、1e-4 对卷积层这个比例在多次实验中比较稳定。4. 从训练到产线部署推理加速与气阀联动4.1 模型导出与推理优化训练好的 PyTorch 模型不能直接扔到产线上跑需要先导出成推理友好的格式。如果部署在 NVIDIA GPU 上用 TensorRT 加速如果是 CPU 或国产 NPU用 ONNX 做中间格式再转对应推理引擎。下面是一个导出 ONNX 并做推理的示例import torch import onnxruntime as ort import numpy as np from PIL import Image from torchvision import transforms # 导出 ONNX 模型 model models.resnet18(weightsNone) model.fc nn.Linear(model.fc.in_features, 2) model.load_state_dict(torch.load(best_gangue_cnn.pth)) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, gangue_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version11 ) # ONNX Runtime 推理 session ort.InferenceSession(gangue_cnn.onnx, providers[CUDAExecutionProvider]) preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def predict(image_path): img Image.open(image_path).convert(RGB) tensor preprocess(img).unsqueeze(0).numpy() outputs session.run(None, {input: tensor})[0] prob np.exp(outputs) / np.exp(outputs).sum() # softmax label gangue if prob[0][1] 0.5 else coal return label, prob[0] label, prob predict(test_sample.jpg) print(fPrediction: {label}, Confidence: {prob.max():.4f})导出 ONNX 时把 opset_version 设为 11是因为这个版本对 ResNet 系列算子支持最成熟再高的版本在某些推理引擎上反而会碰到兼容性问题。dynamic_axes 设置让 batch 维度可变方便后续做批量推理。推理阶段用 ONNX Runtime 的 CUDA Provider在 RTX 3060 上单张 224×224 图像的推理时间大约 3 到 5 毫秒完全跟得上皮带速度。4.2 气阀联动与延迟补偿模型输出只是判断结果真正执行分选的是气阀。从相机拍照到气阀动作中间有图像传输、推理、通信、气阀响应等多个环节的延迟。如果物料在皮带上以 2 米每秒运动延迟 50 毫秒就意味着物料已经移动了 10 厘米。所以必须做延迟补偿根据皮带速度和相机到气阀的距离计算出物料到达气阀位置的时间延迟触发气阀。常见做法是在 PLC 里做位置跟踪相机触发拍照的同时记录编码器位置推理完成后根据当前编码器位置和模型结果决定是否喷吹。这个环节的调试往往比模型训练还费时间因为涉及机械、电气、算法的联调。我踩过的坑是相机触发信号和编码器信号没有严格同步导致位置偏差累积跑几个小时之后分选精度就飘了。后来加了硬件同步信号才解决。4.3 现场部署的算力选型参考部署方案典型硬件单帧推理延迟适用场景工控机 独立 GPURTX 3060 / T43-5 ms多相机、高节拍产线边缘计算盒Jetson Xavier NX8-12 ms单相机、中等节拍国产 NPU 方案瑞芯微 RK358810-15 ms成本敏感、低功耗场景纯 CPU 推理i7-1270030-50 ms低速产线或验证阶段选型时不要只看推理延迟还要考虑相机数量、图像分辨率、预处理耗时。如果一条皮带上装两台相机做双面检测算力需求直接翻倍。我一般建议留 50% 的算力余量因为现场工况波动时推理负载会上升。5. 煤矸石 CNN 分选避坑指南那些让模型上线就翻车的细节5.1 训练集准确率 99%现场却频繁误判现象离线评估各项指标都很好部署到现场后矸石误判率明显上升。原因通常是训练集和现场数据分布不一致——训练集用的是晴天干燥煤样现场是喷淋后的湿煤表面反光特性完全不同。解决办法是在数据采集阶段就覆盖湿煤、不同光照、不同煤种的样本或者在推理前加图像预处理做光照归一化。我一般会在现场部署后先跑一周纯记录模式收集误判样本再拿回来做增量训练。5.2 模型把皮带背景当成了分类依据现象模型在测试集上表现正常但换一条皮带就完全失效。原因是训练时相机视野里包含了大量皮带背景模型学到了背景特征而不是煤矸石本身的特征。解决办法是在标注阶段就把物料区域裁剪出来或者在训练时加入背景随机替换的增强策略。更彻底的做法是用目标检测先定位物料再对每个物料块做分类。5.3 气阀响应延迟导致分选精度下降现象模型判断正确但气阀喷吹时机不对矸石没被吹走或者把煤也吹飞了。原因是延迟补偿参数没有标定准确。解决办法是用高速相机拍下气阀喷吹过程测量从信号发出到气流到达物料位置的实际延迟再把这个值写入 PLC 补偿参数。不同气压下延迟不同换喷嘴或调气压后需要重新标定。5.4 类别不平衡导致矸石召回率偏低现象整体准确率很高但矸石漏判严重。原因是训练集中煤样本远多于矸石模型倾向于预测为煤。解决办法是在损失函数里加类别权重或者用 Focal Loss 替代交叉熵。我一般会把矸石类的权重设为煤的 2 到 3 倍具体值根据实际比例调整。评估时重点看矸石召回率不要被整体准确率迷惑。5.5 模型更新后没有做回归测试现象用新数据增量训练后旧场景下的分选效果反而变差了。原因是增量训练导致模型在新数据上过拟合遗忘了旧场景的特征。解决办法是每次更新模型都要在覆盖全部场景的测试集上做回归测试确保新模型在所有已知工况下都不低于旧模型。我习惯保留一个「黄金测试集」包含各个煤种、各种光照条件的样本每次模型更新都必须过这一关。6. 让煤矸石 CNN 分选越跑越准增量学习与现场调优的一个实用技巧模型上线不是终点而是起点。现场工况会变——煤质波动、季节变化导致环境温度湿度不同、设备磨损导致光源衰减这些都会让模型精度慢慢下降。我一般会在产线上部署一个「难例回流」机制推理时如果模型对某张图的置信度低于阈值比如 softmax 输出在 0.4 到 0.6 之间就把这张图自动保存下来每天定时回传到训练服务器。这些难例就是最有价值的增量训练数据。增量训练时不要直接用新数据重新训练整个模型那样容易灾难性遗忘。我常用的做法是冻结骨干网络的前几层只微调后面的层和全连接层学习率设为初始训练的十分之一。这样模型能适应新工况又不会丢掉已经学到的通用特征。下面是一个增量训练的代码片段# 加载已训练模型 model models.resnet18(weightsNone) model.fc nn.Linear(model.fc.in_features, 2) model.load_state_dict(torch.load(best_gangue_cnn.pth)) # 冻结 layer1 和 layer2只微调 layer3、layer4 和 fc for name, param in model.named_parameters(): if name.startswith(layer1) or name.startswith(layer2): param.requires_grad False # 增量训练时用更小的学习率 optimizer optim.SGD( filter(lambda p: p.requires_grad, model.parameters()), lr1e-5, momentum0.9, weight_decay1e-4 ) # 后续训练循环与初始训练一致但 epoch 数减少到 10 左右这个策略的关键在于冻结浅层保护通用特征微调深层适应新工况小学习率防止震荡。增量训练的 epoch 数不需要多通常 5 到 10 个就够了因为新数据量不大训太多反而过拟合。还有一个实用技巧是模型集成。如果产线算力允许可以同时跑两个模型——一个用原始数据训练一个用增量数据训练推理时取平均或投票。这样即使增量模型在某些场景下退化原始模型还能兜底。我做过对比双模型集成的矸石召回率比单模型平均高 1.5 到 2 个百分点代价是推理时间翻倍。如果算力紧张可以只对低置信度样本启用第二个模型兼顾精度和速度。最后说一个我自己的习惯每次现场调试我都会带一个笔记本记录当天的煤质情况、光照条件、模型表现和调整参数。时间长了你会发现煤矸石分选模型的精度波动往往和这些现场变量强相关而不是模型本身的问题。把这些变量记录下来下次遇到类似工况就能快速定位。希望帮到你。本文还有配套的精品资源点击获取