☰
基于CNN卷积神经网络的煤矸石自动分选:从数据集构建到产线部署全流程
2026/10/7 11:57:13 网站建设 项目流程

简介:这份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_ok=True) 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 在大多数场景下已经够用,再深反而容易过拟合,而且推理速度会拖累产线节拍。

如果部署环境是工控机加 GPU,ResNet-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(brightness=0.2, contrast=0.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", transform=train_tf) val_ds = datasets.ImageFolder("dataset/val", transform=val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) # 加载预训练 ResNet-18,替换最后的全连接层 model = models.resnet18(weights=models.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(weight=class_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}, ], momentum=0.9, weight_decay=1e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) 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(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) acc = correct / total print(f"Epoch {epoch+1}, Val Acc: {acc:.4f}") if acc > best_acc: best_acc = acc torch.save(model.state_dict(), "best_gangue_cnn.pth") print(f"Best 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(weights=None) 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_version=11 ) # 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(f"Prediction: {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(weights=None) 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()), lr=1e-5, momentum=0.9, weight_decay=1e-4 ) # 后续训练循环与初始训练一致,但 epoch 数减少到 10 左右

这个策略的关键在于:冻结浅层保护通用特征,微调深层适应新工况,小学习率防止震荡。增量训练的 epoch 数不需要多,通常 5 到 10 个就够了,因为新数据量不大,训太多反而过拟合。

还有一个实用技巧是模型集成。如果产线算力允许,可以同时跑两个模型——一个用原始数据训练,一个用增量数据训练,推理时取平均或投票。这样即使增量模型在某些场景下退化,原始模型还能兜底。我做过对比,双模型集成的矸石召回率比单模型平均高 1.5 到 2 个百分点,代价是推理时间翻倍。如果算力紧张,可以只对低置信度样本启用第二个模型,兼顾精度和速度。

最后说一个我自己的习惯:每次现场调试,我都会带一个笔记本,记录当天的煤质情况、光照条件、模型表现和调整参数。时间长了你会发现,煤矸石分选模型的精度波动,往往和这些现场变量强相关,而不是模型本身的问题。把这些变量记录下来,下次遇到类似工况就能快速定位。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询