简介这份资源面向计算机、人工智能、自动化等专业的在校学生与教师提供一套基于ResNet卷积神经网络的煤矸石识别分类系统完整Python实现可用于毕业设计、课程设计或项目立项演示。压缩包共45个文件约4.37MB包含11个py源码、10个csv数据表、7张jpg样本图、4个ipynb实验笔记及若干模型与缓存文件覆盖数据预处理、图像分割、GLCM特征提取、CNN与VGG16对比实验等模块并配有GUI界面与训练好的模型。资源已通过测试运行附带数据集与操作教程目录结构清晰便于按模块检索学习。目前已有182人学习下载适合希望快速复现深度学习图像分类流程、理解ResNet在工业煤矸识别场景中应用的读者参考与二次开发。1. 煤矸石识别为什么值得用 ResNet 做一遍从人工选矸到推理只需 40ms在煤矿地面生产系统里原煤经振动筛分级后块煤与矸石混在一起过去靠工人站在皮带旁手选一个班 8 小时盯着同一条皮带眼睛和腰椎都吃不消而且分选效率受情绪和疲劳影响极大。基于深度学习 ResNet 卷积神经网络实现煤矸石识别分类系统本质上就是把这套“人眼判断 手动拨料”的流程替换成“工业相机采集 ResNet 推理 气阀/机械臂执行”的自动化链路。它解决的核心问题不是“能不能识别”而是“在粉尘、水雾、光照不均的工况下能不能稳定地把煤和矸石分开并且单帧推理时间压到产线节拍以内”。这套方案适合两类人一是做矿山智能化改造的工程师需要一套能跑通、能演示、能继续迭代的基线系统二是深度学习入门者想找一个真实工业场景把 ResNet 从论文落到 GUI 界面上。Python 源码、数据集、模型权重和操作教程打包在一起省去了自己从零标注和调参的起步成本但真正决定成败的是数据质量和推理管线的工程细节。2. 煤矸石图像数据集怎么准备从现场采集到 ResNet 输入张量2.1 煤矸石图像与普通 ImageNet 的差异在哪ResNet 在 ImageNet 上预训练时见过的是猫狗、汽车、家具而煤矸石图像的特点是目标类别只有两类煤、矸石类间差异在颜色和纹理上并不总是显著——尤其是经过水洗后的块煤和深色矸石在灰度图上直方图可能高度重叠。这意味着直接拿 ResNet50 的 ImageNet 权重做迁移顶层特征仍然需要重新学习。常见做法是保留 ResNet 的卷积主干替换最后的全连接层为 2 输出然后分两阶段训练先冻结主干只训分类头再解冻最后两个 stage 做小学习率微调。这样做的原因是浅层卷积学到的边缘和纹理特征对煤矸石仍然有效而深层语义特征需要适配。另一个差异是图像采集条件。现场相机通常固定在皮带上方 0.81.2 米处镜头焦距 8mm 或 12mm光源多为条形 LED 白光。如果数据集里混入了实验室环境下拍摄的干净样本模型上线后遇到粉尘和水雾就会翻车。我一般会要求采集数据时至少覆盖三种工况干燥、喷雾降尘、夜间补光每种工况不少于 500 张。数据量方面煤和矸石各 20003000 张是一个比较稳妥的起点低于 1000 张时 ResNet18 比 ResNet50 更不容易过拟合。2.2 用 Python 脚本把原始图像整理成训练集拿到现场采集的原始图像后第一步不是直接丢进网络而是做去重、裁剪和划分。下面这段脚本假设原始图像按raw/coal/和raw/gangue/两个文件夹存放输出到dataset/train、dataset/val、dataset/test三个目录划分比例 7:2:1。import os import shutil import random from pathlib import Path random.seed(42) # 固定随机种子保证每次划分一致 RAW_DIR Path(raw) OUT_DIR Path(dataset) CLASSES [coal, gangue] SPLIT {train: 0.7, val: 0.2, test: 0.1} for cls in CLASSES: src RAW_DIR / cls images [f for f in os.listdir(src) if f.lower().endswith((.jpg, .png, .bmp))] random.shuffle(images) n len(images) n_train int(n * SPLIT[train]) n_val int(n * SPLIT[val]) splits { train: images[:n_train], val: images[n_train:n_train n_val], test: images[n_train n_val:] } for split_name, files in splits.items(): dst OUT_DIR / split_name / cls dst.mkdir(parentsTrue, exist_okTrue) for f in files: shutil.copy2(src / f, dst / f) print(f{cls}: total{n}, train{len(splits[train])}, val{len(splits[val])}, test{len(splits[test])})这段代码的逻辑很直接按类别分别读取文件列表用固定种子打乱后按比例切分再复制到对应目录。参数方面random.seed(42)是为了让每次运行结果可复现如果你希望每次划分不同可以去掉SPLIT字典里的比例可以根据数据量调整数据少于 1500 张时建议改成 8:1:1把更多样本留给训练。复制而不是移动是为了保留原始数据方便后续重新划分。运行完成后dataset目录下就是标准的 ImageFolder 结构可以直接被torchvision.datasets.ImageFolder读取。2.3 数据增强与归一化参数怎么定煤矸石图像在训练时不能只做随机翻转因为现场相机角度固定垂直翻转会产生现实中不存在的姿态。我一般用以下增强组合随机水平翻转p0.5、随机旋转 ±10°、颜色抖动亮度 0.2、对比度 0.2、饱和度 0.1、随机裁剪并缩放到 224×224。归一化参数用 ImageNet 的均值[0.485, 0.456, 0.406]和标准差[0.229, 0.224, 0.225]因为主干是预训练权重输入分布保持一致能加速收敛。注意如果现场图像偏暗颜色抖动里的亮度范围可以适当加大到 0.3但不要用直方图均衡化做预处理因为那会改变矸石表面纹理的统计特性反而让模型学到虚假特征。3. ResNet 模型选型与训练18、34、50 在煤矸石任务上差多少3.1 ResNet18 和 ResNet50 的取舍依据在煤矸石二分类任务上ResNet18 和 ResNet50 的精度差距通常只有 0.51.5 个百分点但推理速度差 23 倍。如果你的部署环境是工控机加 CPU 推理ResNet18 单帧约 1525msResNet50 约 4060ms如果用 NVIDIA Jetson 或带 GPU 的工控机两者都能压到 10ms 以内。我的建议是数据量低于 3000 张、产线节拍要求单帧低于 30ms优先 ResNet18数据量超过 5000 张、允许 50ms 以上推理时间再考虑 ResNet50。ResNet34 处在一个尴尬位置精度提升不明显速度又不如 18除非你有特定的算力预算限制否则不推荐。另一个选型维度是输入分辨率。ResNet 标准输入是 224×224但煤矸石在皮带上的成像尺寸可能只占画面 1/4直接缩放到 224 会丢失纹理细节。常见做法是把原图裁剪到目标区域后再缩放或者把输入分辨率提高到 320×320。提高分辨率会增加计算量ResNet18 在 320×320 下单帧约 3040ms需要根据产线节拍权衡。3.2 训练脚本与关键超参数下面是一个基于 PyTorch 的最小训练脚本包含数据加载、模型构建、两阶段微调和验证。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models # 数据增强与归一化 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(10), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [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) # 构建 ResNet18替换全连接层 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 2) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() # 第一阶段只训全连接层 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) def train_one_epoch(loader): model.train() total_loss, correct, total 0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total for epoch in range(5): loss, acc train_one_epoch(train_loader) print(fStage1 Epoch {epoch1}: loss{loss:.4f}, acc{acc:.4f}) # 第二阶段解冻 layer3 和 layer4小学习率微调 for name, param in model.named_parameters(): if name.startswith(layer3) or name.startswith(layer4): param.requires_grad True optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-4) for epoch in range(10): loss, acc train_one_epoch(train_loader) print(fStage2 Epoch {epoch1}: loss{loss:.4f}, acc{acc:.4f})这段脚本的关键点有三个。第一weightsmodels.ResNet18_Weights.IMAGENET1K_V1加载预训练权重如果环境无法联网需要提前下载权重文件并指定本地路径。第二两阶段训练第一阶段冻结主干只训fc学习率 1e-3跑 5 个 epoch 让分类头先收敛第二阶段解冻layer3和layer4学习率降到 1e-4跑 10 个 epoch 做精细调整。第三batch_size32在 8GB 显存下比较安全如果显存不足可以降到 16同时把学习率按比例降到 5e-4。num_workers4在 Windows 上如果报错改成 0 即可。3.3 训练过程中看什么指标除了 loss 和 accuracy煤矸石任务要特别关注混淆矩阵。因为两类样本可能不均衡比如煤的样本远多于矸石模型容易偏向多数类。如果发现矸石召回率低于 0.85说明模型对矸石特征学得不够需要检查矸石样本是否太少或者增强是否过度导致矸石纹理被破坏。另一个指标是验证集 loss 是否在第二阶段开始上升如果是说明过拟合应该减少解冻层数或增加 dropout。我一般会在fc前加一个nn.Dropout(0.3)对煤矸石任务有稳定提升。4. GUI 界面与推理管线从模型文件到可点击的分类系统4.1 用 Tkinter 搭一个能用的煤矸石识别界面GUI 不需要花哨核心功能就三个选择图片、显示图片、输出分类结果和置信度。Tkinter 是 Python 自带库打包成 exe 后不依赖额外运行时适合工控机部署。下面是一个最小可运行界面。import tkinter as tk from tkinter import filedialog from PIL import Image, ImageTk import torch import torch.nn as nn from torchvision import transforms, models class CoalGangueApp: def __init__(self, root): self.root root self.root.title(煤矸石识别分类系统) self.root.geometry(600x500) self.model self.load_model() self.tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) self.label tk.Label(root, text请选择一张煤矸石图片, font(微软雅黑, 14)) self.label.pack(pady10) self.img_label tk.Label(root) self.img_label.pack() self.btn tk.Button(root, text选择图片, commandself.select_image, font(微软雅黑, 12)) self.btn.pack(pady10) self.result tk.Label(root, text, font(微软雅黑, 16), fgblue) self.result.pack(pady10) def load_model(self): model models.resnet18(weightsNone) model.fc nn.Linear(model.fc.in_features, 2) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() return model def select_image(self): path filedialog.askopenfilename(filetypes[(Image, *.jpg *.png *.bmp)]) if not path: return img Image.open(path).convert(RGB) img_resized img.resize((300, 300)) self.tk_img ImageTk.PhotoImage(img_resized) self.img_label.config(imageself.tk_img) tensor self.tf(img).unsqueeze(0) with torch.no_grad(): output self.model(tensor) prob torch.softmax(output, dim1)[0] pred prob.argmax().item() classes [煤, 矸石] self.result.config(textf识别结果{classes[pred]}置信度{prob[pred]:.2%}) if __name__ __main__: root tk.Tk() app CoalGangueApp(root) root.mainloop()这段代码的逻辑是初始化时加载训练好的best_model.pth选择图片后用 PIL 读取并显示缩略图同时把原图做同样的预处理后送入模型最后用 softmax 输出置信度。参数方面map_locationcpu保证在没有 GPU 的机器上也能加载Resize((224, 224))必须和训练时一致否则精度会掉。如果打包成 exe用pyinstaller -F -w main.py注意把best_model.pth一起放到 exe 同目录否则会报文件找不到。4.2 推理速度优化与批量处理单张推理在 CPU 上约 2030ms但如果产线需要连续处理视频流逐帧调用会累积延迟。常见做法是用torch.jit.trace把模型转成 TorchScript推理速度能提升 20%30%。另外如果相机帧率是 25fps可以每 2 帧取 1 帧做推理中间帧用上一帧结果这样能把 CPU 占用降下来。对于批量图片测试可以把DataLoader的batch_size设成 8 或 16一次性推理多张吞吐量比单张循环高得多。提示如果 GUI 在推理时卡顿是因为主线程被模型计算阻塞。可以把推理放到threading.Thread里计算完成后用root.after更新界面这样按钮不会卡死。5. 避坑与排查煤矸石识别系统上线前必须过的 5 道坎5.1 现象训练集准确率 99%现场测试只有 70%原因通常是数据集和现场工况不匹配。实验室或晴天采集的样本煤表面反光强、矸石纹理清晰而现场喷雾降尘后镜头前有水雾图像整体对比度下降模型学到的颜色特征失效。解决办法是在训练集中加入至少 20% 的带水雾、低照度样本并在推理前加一个简单的图像去雾或对比度拉伸预处理。如果现场无法补采可以用albumentations库做模拟水雾增强但效果不如真实样本。5.2 现象模型把深色煤误判成矸石这是煤矸石任务最典型的翻车场景。原因是煤和矸石在灰度上重叠模型可能只学到了亮度阈值。解决思路是引入纹理特征比如在 ResNet 主干前加一个局部二值模式LBP分支或者用 ResNet 的中间层特征做多尺度融合。更简单的做法是检查数据集中是否有大量“深色煤”和“浅色矸石”样本如果有把它们单独拎出来做难例挖掘在训练时给这些样本更高的损失权重。5.3 现象GUI 打包后提示找不到模型文件PyInstaller 打包时best_model.pth不会自动打进 exe需要手动放到 exe 同目录或者在代码里用sys._MEIPASS获取临时解压路径。更稳妥的做法是把模型文件放到用户目录下首次运行时从 exe 同目录复制过去。另外torch.load在 PyTorch 2.6 之后默认weights_onlyTrue如果保存的模型包含自定义类需要显式设置weights_onlyFalse否则会报错。5.4 现象训练 loss 震荡不下降先检查学习率是否过大。两阶段训练中第一阶段 1e-3 对 Adam 是合适的但如果用 SGD需要降到 1e-2 并加 momentum。其次检查数据增强是否过强比如旋转角度超过 ±15° 会让煤矸石图像出现大量黑边模型学到的是黑边而不是目标。最后检查 batch size 是否太小小于 16 时 BatchNorm 的统计量不稳定loss 容易震荡。5.5 现象推理结果置信度普遍偏低如果所有图片的置信度都在 0.50.6 之间说明模型没有学到区分性特征。常见原因是归一化参数用错比如训练时用了 ImageNet 均值推理时忘了做同样的归一化。另一个原因是模型没有完全收敛可以检查验证集 loss 是否还在下降。如果训练轮数不够增加 510 个 epoch 通常能改善。6. 把煤矸石识别做成可迭代系统的几个习惯这套系统跑通之后真正决定它能不能在产线上活下来的不是模型精度又涨了 0.5 个点而是你有没有留好数据回流的通道。我一般会在 GUI 里加一个“保存当前图片”的按钮现场工人发现误判时点一下图片就存到feedback/目录每周把新增的误判样本重新标注后加入训练集跑一次增量训练。这样模型会随着现场工况变化慢慢适应而不是上线三个月后精度一路下滑。另一个习惯是固定推理预处理。训练时用什么Resize、什么归一化推理时必须一模一样最好把预处理参数写进一个config.yaml训练和推理都读同一个文件。我见过太多因为推理时Resize从 224 改成 256 导致精度掉 10 个点的案例这种问题排查起来非常费时间。最后如果你打算把这套方案用到实际产线建议先用 ResNet18 加 3000 张样本跑一个基线记录准确率、召回率和单帧耗时然后再决定要不要换 ResNet50 或加多尺度特征。不要一上来就追求 SOTA煤矸石识别这个场景数据质量和预处理一致性比模型结构重要得多。希望帮到你。本文还有配套的精品资源点击获取