☰
CNN煤矸石分选全流程:从机器视觉到模型部署实战
2026/9/30 8:29:22 网站建设 项目流程

简介:这份PDF资料是一篇基于CNN卷积神经网络的煤矸石自动分选研究论文,发表于《江苏建筑职业技术学院学报》2019年第4期。内容针对传统灰度信息分选煤矸石的局限,提出利用卷积神经网络对煤块与矸石图像进行多层次纹理特征提取,实现自动识别,测试准确率达到92%,并详细阐述CNN卷积层、池化层、全连接层工作原理及实验对比,适用于深度学习、计算机视觉、工业智能分选等领域研究者与相关专业学生阅读参考。资源为单一PDF文件,大小1.98MB,包含论文全文、图表、数据与参考文献。目前已有338人学习,对于开展神经网络图像分类课题或毕业设计具有参考价值,可以有效帮助读者理解CNN建模思路、特征提取方法及工业场景落地流程。

1. 煤矸石自动分选为什么非要用CNN:从手选工位到机器视觉的转变

去过选煤厂的人都知道,手选矸石是整个流程里最苦也最不可控的环节。工人在皮带旁一站八个小时,靠眼睛从煤流里把灰白色的矸石捡出来,这种岗位招人越来越难,而且人眼疲劳后漏捡率会明显上升。煤矸石自动分选的研究方向这些年其实一直有,但真正让它从实验室走向车间的,是CNN卷积神经网络在图像识别上的稳定表现。传统机器视觉靠颜色阈值、形状规则去判断,遇到不同煤种、不同井下环境就失效;而CNN能自己从像素里学出煤和矸石在纹理、光泽、边缘上的深层差异。这篇文章我把从采集图像、做标注、训练模型到现场部署的完整路径拆开讲,尤其要说说那些论文里不会写的坑。读者如果是选煤厂的技术员、做机器视觉的工程师,或者正在调研这个项目的决策者,可以照着这个思路去评估和落地。

2. 从成像到识别:煤矸石分选里的CNN原理与选型

2.1 煤和矸石在图像上到底差在哪:CNN能学到什么

煤和矸石在肉眼看来,最直观的差异是颜色:煤是黑色或深灰色,矸石往往是灰白、浅黄甚至带点红。但如果只是在RGB空间里设定一个灰度阈值,现场会立刻翻车。因为不同矿区的煤质差异很大,无烟煤表面是亮黑的,褐煤偏棕,矸石表面的煤粉又会让它发黑。传统视觉算法把这些差异归结为“光照不均”“煤粉污染”,本质上是因为它只能提取表层特征。

CNN卷积神经网络的不同之处在于,它通过卷积核在图像局部滑动,逐层抽象出边缘、纹理、角点、局部形状这样的特征。煤块在外力破碎后,断面通常是参差的、有光泽的,而矸石断面更致密、缺乏玻璃光泽。这些特征不是靠程序员定义,而是由网络在训练中自动归纳。对我来说,CNN真正解决的是“特征说不清”的问题。过去做选型,要跟机械厂家解释为什么不能用颜色传感器,现在直接用图片训练一个网络,让数据说话。

卷积神经网络原理里有个核心概念叫“感受野”。浅层卷积核只能看到很小区域,比如3x3的像素,提取的是边缘;深层卷积核能覆盖更大范围,提取的是“这一块是煤还是矸石”这样的语义信息。煤矸石分选任务并不复杂,不需要识别上千类物体,但需要网络对局部纹理足够敏感,同时不受大面积反光影响。所以网络不用太深,ResNet18或者自建一个四层的CNN就够用,盲目上ResNet152反而容易过拟合,而且推理速度跟不上皮带速度。

2.2 为什么不用SVM或传统机器视觉:CNN和RNN的边界

在CNN之前,煤矸石识别的主流做法是提取手工特征再加SVM分类器。常用特征包括灰度直方图、局部二值模式LBP、灰度共生矩阵GLCM。这些特征在某些矿区能跑到95%以上的准确率,但换一个矿,煤质一变,准确率可能掉到80%以下。问题出在手工特征的可迁移性太差。你针对“亮黑煤”设计的特征,遇到“粉煤”就失效。

SVM和CNN原理的对比,本质是“人工定义特征”和“数据驱动特征”的差别。SVM需要先做特征工程,而且对图像的空间结构不敏感;CNN直接吃原始像素,通过卷积操作天然保留了空间邻域关系。在煤矸石分选这种缺陷样本差异微妙、环境多变的场景下,CNN的鲁棒性更好。

那CNN和RNN的边界在哪?RNN擅长处理序列数据,比如一段文本、一段语音,或者输送带上的连续帧——如果你想把一个煤块的视频序列送进去,用RNN也许合理。但煤矸石分选的工业相机通常在皮带上方垂直拍摄,每个物料块只需要单帧静态图就能判断。单个图像是二维网格结构,RNN会破坏空间关系,而CNN的卷积结构恰恰匹配图像。所以常见的做法是:如果现场有高速相机的连续帧,仍用CNN对每一帧独立识别,再用后处理逻辑做跟踪,而不是直接上RNN。

2.3 CNN的基本结构:卷积层、池化层、全连接层怎么搭

一个用于煤矸石二分类(煤/矸石)的CNN基本结构,我一般这样设计:

  • 输入层:固定图像尺寸,常见是224x224x3,也可以压缩到128x128减少计算量。煤矸石分选本质是二分类,图像细节要求不高,128x128对现场部署更友好。
  • 卷积层:前两层用32和64个3x3卷积核,步长1,padding为1。每层后面接ReLU和2x2最大池化。这个小结构足够提取煤块的纹理和边缘。
  • 第三层:用128个3x3卷积核,再接全局平均池化,而不是直接拉平。全局平均池化能显著减少参数,防止过拟合。
  • 输出层:一个全连接层,输出2个节点,接Softmax。

注意不要把网络搭太深。煤矸石图像结构简单,类别少,深网络不但训练慢,还容易出现梯度消失。我见过有人直接搬VGG16,训练集准确率99%,测试集准确率92%,但推理速度只有20fps,根本跟不上1.5m/s的皮带速度。用上面这个浅层CNN,用GPU推理可以到200fps以上。

选型阶段还应该考虑部署硬件。如果打算用PLC控制喷吹阀,那视觉系统推理延迟必须小于物料通过相机视场的时间。比如皮带速度1.5m/s,相机视场宽度40cm,一个煤块从被拍摄到被吹掉的可用时间约260ms,扣除相机曝光、传输和PLC动作时间,留给模型推理的时间往往只有50ms以内。因此CNN模型在部署阶段要做的量化、剪枝,从选型号那一天就要考虑进去。不要先训一个大模型再想着压缩,应该一开始就定目标推理时间。

3. 做数据集是分选项目的头号工程:采集、标注与增强

3.1 现场图像采集的硬性要求

没有干净的数据集,CNN再厉害也白搭。煤矸石分选的数据集必须从现场实际工况里采,不能用实验室模拟图。我在推进项目时,第一个月基本全在搞采集。

相机安装位置要选在皮带平稳段,避开溜槽口和转载点。因为转载点物料飞溅,图像模糊,而且粉尘大。光源要用白色LED线性光,从斜上方45度照射,避免煤块表面的镜面反射直接打入镜头形成高光斑。高光区域会让卷积神经网络误判为“亮色”,非常致命。

帧率与皮带速度要匹配。比如皮带速度1.5m/s,相机视场宽度40cm,一对COB LED频闪能冻结运动模糊,但快门时间要小于1/2000s。我建议用千兆网工业相机,分辨率不需要太高,500万像素足够,但帧率至少要30fps。实际采集时不是连续录像,而是用光电传感器触发,物料进入视场时拍一张,这样每帧都是清晰的单个料堆。

采集样本量方面,煤和矸石各至少5000张原始图。这是底线。如果矿区煤质多变,最好按不同煤层、不同湿度分批采样。我见过一个项目只采集了晴天白天的图,结果阴雨天现场光照一变,识别率直接掉到及格线以下。所以采集时要有意识地把光照强度、物料湿度、煤粉浓度这三个变量都覆盖进来。

3.2 标注格式与工具选择

标注就是给每张图里的每个物料块画框或者画轮廓。煤矸石分选常用的标注格式是YOLO风格的txt文件,每行一个目标,依次是类别、中心点x、中心点y、宽、高,坐标都用相对于图宽高的比例表示。

我一般用LabelImg做画框标注,虽然它老,但稳定、学习成本低。如果要做像素级分割,用Labelme。煤矸石分选大多数场景只需要检测“矸石在哪”,所以画矩形框就够了。但注意一个细节:煤块之间往往挤在一起,一个框里可能同时框到小煤块和矸石。标注时框要紧贴目标边界,不要把相邻的无关物料包进去。标准是“宁缺毋滥”——框大一点可以,但绝不能框进另一块截然不同的物料。

标注完了别急着训练,先做一轮交叉检查。让两个人分别标一百张图,计算IoU一致性,低于0.8就说明标注规范没定清楚,需要重新开会对齐。

# 把LabelImg导出的VOC XML转成YOLO txt格式 import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, class_list): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size').find('width').text) img_h = int(root.find('size').find('height').text) txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' with open(os.path.join(out_dir, txt_name), 'w') as f: for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_list: continue cls_id = class_list.index(cls) box = obj.find('bndbox') x1, y1 = int(box.find('xmin').text), int(box.find('ymin').text) x2, y2 = int(box.find('xmax').text), int(box.find('ymax').text) x_center = (x1 + x2) / 2 / img_w y_center = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")

这段脚本做的事情是把标注工具导出的XML文件解析出来,读取每张图的宽高和每个目标的类别与边界框,然后换算成YOLO需要的归一化坐标。为什么要归一化?因为网络输入尺寸固定,训练时会做缩放,如果坐标是绝对像素值,缩放后就全乱套了。

转换后一定要抽查几个txt文件,打开原图对比一下框的位置。常见错误是有一个类别的名字拼写不一致,导致那个类别的目标全部被跳过,但程序不报错,训练集里矸石样本悄悄变少了。

3.3 数据增强的适度使用

数据增强是CNN训练里绕不开的一环,煤矸石图像尤其需要。但要注意,增强操作不能破坏煤矸石的物理本质。

我常用的增强组合是:

  • 随机水平翻转(概率0.5)。煤块的形状虽然不规则,但翻转不影响判断,而且能增加样本多样性。
  • 随机亮度调整(范围0.8~1.2)。因为不同光照下煤面亮度差异大,让模型适应亮度变化很有必要。但过大的亮度调整会把黑色煤块变成灰色,破坏类别内的一致性。
  • 随机对比度调整(范围0.9~1.1)。增强边缘锐度,帮助模型学习纹理。
  • 随机裁剪(裁剪比例0.8~1.0)。模拟物料被局部遮挡的情况。
  • 禁用垂直翻转。煤块从皮带上看,顶部和底部的纹理分布有方向性,垂直翻转会引入错误的学习信号。
  • 禁用旋转超过90度。矸石长条状时,旋转90度就变成了立着的,不符合实际落料姿态。

增强的数量一般把每张原始图扩到2~4倍。扩太多会让模型过度适应“人造样本”,在真实图像上反而变差。我常用torchvision里的transforms,不用自己写:

import torch from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((128, 128)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.2, contrast=0.1), transforms.RandomResizedCrop(size=128, scale=(0.8, 1.0)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

这里用了ImageNet的标准化参数,其实煤矸石图像是灰度偏黑的,更合理的做法是根据自己数据集的统计值计算均值和标准差。你可以先加载所有训练图片,用代码算出来,然后替换掉上面两个数组。我在早期的项目里偷懒直接用了ImageNet的参数,结果发现训练收敛变慢,后来改成自算均值/方差,损失下降明显快了。

4. 训练一个能进车间的煤矸石识别模型:PyTorch落地代码与参数

4.1 最小可训练代码

选定了PyTorch作为训练框架,下面的代码足够跑通一个基础CNN训练流程。代码里我把重点放在结构和主循环,完整的数据加载部分用伪代码表示,因为每个项目的文件目录不一样。

import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader class CoalGangueCNN(nn.Module): def __init__(self): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d(1) ) self.classifier = nn.Linear(128, 2) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) return self.classifier(x) model = CoalGangueCNN() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # train_loader 返回 (images, labels) for epoch in range(30): model.train() running_loss = 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print(f'Epoch {epoch+1} loss: {running_loss/len(train_loader):.4f}')

这个网络的features部分就是上一章说的结构:三层卷积不断把通道数从32加宽到128,同时用池化把空间尺寸缩小,最后用自适应平均池化把特征图压成1x1,再交给全连接层分类。

为什么用AdaptiveAvgPool2d(1)?它不管输入特征图是多大,都输出1x1,这样就能把全连接层固定下来,即使训练时改了输入分辨率也不用改网络结构。煤矸石分选模型部署时,有些时候会为了提速把推理图像从128改到96,这个设计能避免改代码。

上面代码里没有验证集,实际训练时必须留出10%~15%的数据做验证。我习惯在每个epoch结束后计算验证集准确率,并保存验证准确率最高的一次模型参数,而不是保存最后一个epoch的。

4.2 三个必调参数

第一个是学习率。用Adam时我一般默认0.001,但煤矸石数据集小,0.001经常让模型在头几个epoch快速收敛,然后验证准确率就卡住了。我通常把学习率降到0.0003,训练更稳定。另外加一个学习率衰减策略,比如每10个epoch乘以0.1,能有效提升收敛精度。

第二个是batch size。这个参数受显存限制,但煤矸石图像小,128x128输入,batch size可以放到64甚至128。注意batch size太小时BatchNorm层会抖动,loss曲线上下跳;太大则模型容易陷入尖锐极小值,泛化差。我用32和64做过对比,64在准确率和训练速度上比较均衡。

第三个是输入图像尺寸。很多人在这个问题上欠考虑。煤矸石分选的物料大小从几厘米到二十厘米不等,相机拍摄后,如果图像缩到224,小块矸石在图上只有几十个像素,卷积核根本看不清纹理。我建议针对物料尺寸设置一个最小像素约束,即矸石的最小边在图像上至少占32个像素。如果相机安装高度高导致矸石在图像上太小,就要减小视场或者裁剪。

4.3 训练结果怎么看:准确率、召回率与误捡率

分类准了不等于分选成功。在煤矸石分选场景里,必须把“矸石被误判成煤”和“煤被误判成矸石”分开计算。我用的是检测任务的术语:

  • 召回率(true positive rate):真实矸石里被检出的比例。这个指标关系到“矸石带走率”,如果偏低,说明还有矸石混入精煤。
  • 准确率(precision):在模型判为矸石的目标里,真正是矸石的比例。这个指标关系到“精煤损失率”,如果偏低,说明很多煤被当矸石吹掉了。

实际生产中,矸石被吹掉无所谓,但煤被误吹掉就是钱。所以模型往往需要在精度和召回率之间取一个偏向精度的阈值。默认Softmax输出时取0.5作为分类阈值,但训练完我会在验证集上画ROC曲线,找那个“误捡率低于5%时召回率最高”的阈值。比如模型输出矸石概率大于0.65才触发喷吹,这样虽然漏掉一点点矸石,但保住精煤产量。这个阈值是要配合现场喷吹系统联调的。

训练曲线怎么看?如果训练loss下降,验证loss先降后升,就是过拟合。煤矸石数据集通常不大,过拟合很常见,对策是增加增强强度或者把网络中的Dropout加上。我一般在全连接层前加一个Dropout(0.5),效果立竿见影。如果训练loss一直在高位降不下去,检查标注数据——大概率是坐标没归一化或者标签错位了。

5. 煤矸石分选落地避坑指南:从标注脏数据到现场干扰的5个常见问题

5.1 问题1:标注时煤矸石边界模糊,模型学不到边缘

现象:训练出的模型在测试集上准确率能到93%,但把单块矸石的图片单独送进去,经常分错。检查标注,发现矸石表面有煤粉覆盖,边界和背景煤流混在一起,标注员画框时把边界画大了,把旁边的煤也框了进来。

原因:煤矸石并非黑白分明,特别是井下刚采出来的矸石表面会沾一层煤粉,视觉上呈灰黑色,人眼都容易混淆。标注框里混入背景煤块后,CNN同时学习了煤和矸石的特征,提取出的特征就不纯粹。

解决:标注前先把矸石用高压水冲洗后拍照,作为标准参照图给标注员看。标注时要求沿矸石与煤流的明暗交界处画框,宁小勿大。另外可以在标注完成后用“分割+检测”结合的方式验证——先用颜色阈值把明显黑色的像素剔除掉,把剩下区域的框收紧。我们后期直接用图像处理软件把标注框向内收缩2~3个像素,效果改善很明显。

5.2 问题2:训练集准确率95%,现场一上输送带就翻车

现象:模型在实验室的验证集上表现很好,准确率95%。安装到车间后,识别率降到70%左右。最后发现,现场的皮带上面有震动,相机拍摄到的图像有运动模糊,而训练集里的图像都是静止状态下拍的。

原因:数据集与现场部署的分布不一致。工业现场的皮带速度、震动、曝光时间都会让图像模糊度发生变化。CNN对模糊非常敏感,因为卷积核学到的是清晰边缘纹理,一旦边缘在图像上拖影,特征就匹配不上了。

解决:采集阶段把相机架到皮带上拍真实视频,然后从视频里截帧作为训练图像,不要单独用手持相机拍静态图。如果已经训完了,可以加入随机高斯模糊和运动模糊的数据增强来弥补。另外现场相机曝光时间要尽量短,很多项目把曝光时间调到200微秒,配合频闪光源,基本能消除运动模糊。

5.3 问题3:煤粉、水雾、灰尘导致图像退化

现象:现场试运行前几周效果很好,后来准确率越来越差。检查发现相机镜头和窗口玻璃上落了一层煤粉,图像对比度严重下降。

原因:工业环境粉尘大,尤其是干选车间,煤粉无孔不入。镜头污染呈现渐进性,不容易被及时发现。CNN的输入是干净图像,一旦镜头变脏,图像整体灰度偏移,模型自然就崩了。

解决:必须配置防尘罩和压缩空气吹扫。相机安装在一个带石英玻璃窗口的密封箱内,窗口倾斜安装,并定期用电磁阀控制压缩空气吹扫。同时加一个亮度和模糊监测模块:如果图像平均灰度超过正常范围的10%或者高频分量下降明显,就自动报警提示清洁镜头。这不是模型问题,是运维问题,但决定项目能否长期稳定。

5.4 问题4:正负样本不平衡,模型只会说“都是煤”

现象:训练好的模型在验证集上准确率非常高,但单独把矸石样本拿出来测,准确率只有20%。查看训练集,发现煤块样本8000张,矸石样本只有800张。

原因:矿上煤多矸石少是常态,但这类占比样本不平衡会让CNN偏向多数类。交叉熵损失在类别不平衡时,模型倾向于把不确定性高的样本判为多数类,也就是“都是煤”。准确率虚高掩盖了问题。

解决:不要只看总体准确率。训练时给矸石类更高的类别权重,或者用Focal Loss。最简单的做法是计算每个batch时对不同类别加权,PyTorch的CrossEntropyLoss直接传一个weight参数。另外可以在采集时多挑矸石样本,把矸石和煤的样本比例控制在1:1到1:2之间。

5.5 问题5:标签错位和漏标,loss曲线震荡

现象:训练loss呈周期性尖峰,每训练几步就突然跳高,然后又降下来。验证准确率也忽高忽低,很难收敛。

原因:数据集里混有一些标签和图像不对应的样本。比如采集视频时,某个矸石刚进入视场,但上一帧的位置对应下来已经离开了,标注框和实际目标错位,这个样本会在训练时产生巨大的梯度,拉乱权重。

解决:数据清洗阶段把每张图和对应的标注框在原图上画出来,人工浏览一遍,给明显错位的样本做标记并删除。另一个常见做法是在训练时开启异常样本过滤:当某个样本的loss超过当前batch平均loss的3倍时,把它剔除出该次迭代。虽然这在PyTorch里需要改一点点逻辑,但能显著提升训练稳定性。注意,不要用阈值过滤掉所有高loss样本,那样模型就学不到困难样本了。

6. 从离线模型到在线分选:验证方法、模型导出与部署技巧

6.1 用“留一矿”策略验证泛化能力

如果项目涉及多个矿区的煤质,我建议按矿区划分训练集和验证集:拿一个矿的图做验证,其他矿的图做训练。这样做出来的验证指标才有泛化意义。如果只有一个矿的数据,则按采集时间分片,用前两周的图训练、后一周的图验证,模拟时间泛化。

6.2 模型导出为ONNX并用TensorRT加速

训练完成的模型要部署到工控机,推荐先导出成ONNX格式,再转成TensorRT引擎。下面是导出脚本:

import torch from torchvision import models # 假设 model 已经加载训练好的权重 model.eval() dummy_input = torch.randn(1, 3, 128, 128) torch.onnx.export(model, dummy_input, "coal_gangue.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, opset_version=11)

导出后可以用onnxruntime的GPU模式做一次推理测试,确保输出和PyTorch一致。在NVIDIA工控机上转TensorRT需要用到trtexec工具,转换时注意设FP16精度。煤矸石是二分类,FP16对识别精度影响很小,但推理速度能提升近一倍。转换完的引擎不要直接信任,需要用现场采集的含矸石图跑一遍,和ONNX输出对比,相对误差大于1%的话就说明某些层被过度剪枝了。

6.3 现场联调的三个检查习惯

第一,先在静态皮带停机时放几个标准矸石块测试,确认相机取图触发正常。第二,动态运行时,用一把小铲子随机从皮带上取走被喷吹机构吹掉的物料,分类统计吹掉的矸石和煤的比例。这个“取样验证”比看系统日志可靠。第三,保留最近一周所有触发喷吹的图像,每周抽一天回放,看有没有误喷情况。检查时特别注意模型输出的阈值是不是需要调整,因为白天和夜间的照明可能变化。

我自己的教训是:模型训练完成后,不要急着调阈值,先装上设备空跑一天,统计一下输出概率的分布。如果输出概率大多集中在0.95以上或0.05以下,说明模型很有信心;如果一堆样本集中在0.5附近,说明特征还没学够,需要回炉加数据。煤矸石分选的难点从来不在网络结构,而在数据质量和现场工程习惯。希望这些思路能帮你在自己的项目里少走几段弯路。

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

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

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

立即咨询