ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于机器学习的岩石薄片自动鉴定:课程设计资源与实战指南

基于机器学习的岩石薄片自动鉴定:课程设计资源与实战指南 简介这份资源面向地质学、计算机相关专业的毕业设计、课程设计与期末大作业场景提供一套基于机器学习的岩石薄片自动鉴定完整项目代码帮助读者理解如何用深度学习与传统算法完成岩石图像分类。压缩包共28个文件约758KB以15个Python脚本为核心覆盖数据加载、特征提取、模型训练与测试等模块另有4个Markdown说明文档、3张png示意图、2个txt配置及1个xlsx结果表格便于快速了解项目结构与运行方式。内容涉及卷积神经网络、随机森林、支持向量机等算法在岩石薄片识别中的应用并包含颜色统计、GLCM、LBP等特征提取脚本以及训练器、网络定义与评估指标实现可作为课程实践与算法复现的参考。目前已有46人学习下载适合希望将理论知识与地质图像分析结合、需要完整项目骨架与排错思路的学生和研究者。1. 岩石薄片自动鉴定一份能跑通的机器学习课程设计资源地质实验室里最磨人的活儿莫过于把一片岩石薄片放到偏光显微镜下靠人眼去数石英、长石、云母各占多少。一个熟练工看一片要十几分钟一天下来眼睛发花不同人判读同一片还可能给出不一样的结论。这份「基于机器学习的岩石薄片自动鉴定」资源包解决的正是这个场景把显微镜下拍到的薄片图像喂给模型让它输出矿物类别甚至含量估计。它适合正在做机器学习课程设计、毕业设计的学生也适合想找一个完整图像分类落地案例的开发者——因为岩石薄片这个题材既有真实行业背景又不像人脸识别那样烂大街答辩时不容易撞车。资源包本身是一套可复现的工程不是只给个模型权重的半成品。2. 从薄片图像到训练集数据组织与预处理管线2.1 为什么岩石薄片不是「随便拍几张就能训」岩石薄片图像和普通自然图像有个本质区别它是在单偏光或正交偏光下拍摄的同一种矿物在不同偏光条件下颜色、干涉色完全不同。石英在正交偏光下可能是灰白到一级黄白长石则常见聚片双晶的条纹。如果你把不同偏光条件的图混在一个类别里模型学到的就是矛盾的纹理特征准确率上不去还找不到原因。常见做法是按矿物类别建文件夹每个类别下再按偏光条件分子目录训练时要么只选一种偏光条件要么把偏光条件作为一个额外标签做多任务学习。资源包里的目录结构一般长这样dataset/ quartz/ xpl/ # 正交偏光 ppl/ # 单偏光 feldspar/ xpl/ ppl/ mica/ ...这个结构的好处是你后面想切换实验设置时不用重新整理数据改一个路径参数就行。2.2 预处理脚本尺寸归一化与数据增强薄片图像分辨率往往很高直接缩到 224×224 会丢掉矿物颗粒的边界细节。我一般会先做中心裁剪再缩放或者用滑动窗口切 patch。下面这段预处理代码是资源包里常见的写法基于 OpenCV 和 torchvisionimport cv2 import numpy as np from torchvision import transforms from torch.utils.data import Dataset class ThinSectionDataset(Dataset): def __init__(self, root_dir, img_size224, augmentTrue): self.root root_dir self.img_size img_size # 训练时增强验证时只做归一化 if augment: self.transform transforms.Compose([ transforms.ToPILImage(), transforms.RandomHorizontalFlip(p0.5), transforms.RandomVerticalFlip(p0.5), transforms.RandomRotation(15), # 薄片无方向性旋转安全 transforms.ColorJitter(brightness0.2, contrast0.2), transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) else: self.transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) self.samples self._scan() def _scan(self): # 遍历类别文件夹返回 (路径, 标签) 列表 import os samples [] classes sorted(os.listdir(self.root)) self.class_to_idx {c: i for i, c in enumerate(classes)} for cls in classes: cls_dir os.path.join(self.root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if fname.lower().endswith((.jpg, .png, .bmp)): samples.append((os.path.join(cls_dir, fname), self.class_to_idx[cls])) return samples def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] img cv2.imread(path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV 默认 BGR img self.transform(img) return img, label逻辑说明_scan方法把类别名映射成整数标签这是 PyTorch 分类任务的标准做法。RandomRotation(15)对薄片是安全的因为矿物颗粒在视野里没有固定朝向但如果你做的是有方向性的结构分析这个增强就要去掉。Normalize用的 ImageNet 均值方差是因为资源包通常基于预训练 ResNet 做迁移学习保持输入分布一致能加快收敛。参数img_size建议先试 224如果颗粒边界模糊导致混淆严重再上 384 或 448代价是显存翻倍。2.3 数据集划分的坑别让同一张薄片的 patch 同时进训练和验证这是图像分类里最隐蔽的翻车点。如果你把一张大薄片切成多个 patch然后随机划分训练验证集同一张薄片的相邻 patch 会同时出现在两边模型相当于「见过」验证集准确率虚高十几个点。正确做法是按薄片编号划分同一张薄片的所有 patch 要么全在训练集要么全在验证集。资源包里如果带了划分脚本先确认它是按文件随机还是按薄片分组。我一般会在_scan里加一个group_id字段用薄片文件名前缀作为组标识再用GroupShuffleSplit来分。3. 模型选型与训练迁移学习为什么比从头训更稳3.1 岩石薄片数据量小ResNet 微调是性价比最高的起点一个课程设计能拿到的标注薄片图像通常几百到几千张按类别分下来每类可能就一两百张。这种量级从头训 CNN 基本没戏过拟合到验证集准确率卡在 60% 上不去。迁移学习是标准解法用 ImageNet 预训练的 ResNet18 或 ResNet50冻结前面的卷积层只训最后的全连接层再逐步解冻。资源包里一般会提供train.py核心逻辑如下import torch import torch.nn as nn from torchvision import models def build_model(num_classes, freeze_backboneTrue): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) if freeze_backbone: for param in model.parameters(): param.requires_grad False # 替换最后的分类头 in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model # 训练循环关键片段 def train_one_epoch(model, loader, optimizer, criterion, device): 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逻辑说明freeze_backboneTrue时只训练model.fc优化器要只传model.fc.parameters()否则会把冻结层的梯度也算进去浪费显存。Dropout(0.3)在小数据集上是必要的正则如果验证 loss 震荡大可以加到 0.5。学习率方面只训分类头时用 1e-3 没问题一旦解冻 backbone学习率要降到 1e-4 甚至 1e-5否则预训练权重会被冲垮。资源包里如果没写解冻策略你可以自己加一个 epoch 阈值比如前 10 个 epoch 冻结之后解冻最后两个 block。3.2 类别不平衡石英多、稀有矿物少怎么办薄片里石英和长石往往占大头某些副矿物可能只有几十个样本。直接训会让模型偏向多数类稀有矿物召回率极低。常见做法有三种一是加权交叉熵给少数类更高权重二是重采样对少数类做 oversampling三是用 Focal Loss。资源包里如果只给了普通 CrossEntropyLoss我建议先算一下每类样本数用sklearn.utils.class_weight生成权重传进去from sklearn.utils.class_weight import compute_class_weight import numpy as np labels [s[1] for s in dataset.samples] class_weights compute_class_weight( class_weightbalanced, classesnp.unique(labels), ylabels ) class_weights torch.tensor(class_weights, dtypetorch.float).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)参数说明class_weightbalanced会让权重反比于类别频率少数类权重自然变大。注意这个权重是在训练集上算的别用验证集。如果加了权重后多数类准确率掉得厉害说明权重给过头了可以手动把最大权重截断到 10 以内。3.3 训练过程监控看什么指标才不被「假高准确率」骗课程设计里最常见的翻车是训练集准确率 99%验证集 70%然后不知道问题出在哪。我一般会同时盯四个数训练 loss、验证 loss、验证准确率、以及混淆矩阵。如果训练 loss 一直降但验证 loss 先降后升就是过拟合加 dropout 或减模型容量。如果两个 loss 都不降是欠拟合或学习率不对。混淆矩阵能告诉你哪两类在互相混——比如石英和长石在单偏光下都是无色透明模型分不清很正常这时候要么加正交偏光数据要么在预处理里强化边缘特征。资源包里如果有tensorboard或matplotlib画曲线的脚本别跳过那几张图比最终准确率数字有用得多。4. 推理与部署把模型变成能用的鉴定工具4.1 单张图像推理脚本训练完拿到best_model.pth之后得有一个能直接跑的推理入口。资源包里通常会有predict.py核心是加载模型、预处理、softmax 输出类别和置信度import torch import cv2 import numpy as np from torchvision import transforms def predict(image_path, model, class_names, device, img_size224): model.eval() tf transforms.Compose([ transforms.ToPILImage(), transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) tensor tf(img).unsqueeze(0).to(device) # 增加 batch 维度 with torch.no_grad(): logits model(tensor) probs torch.softmax(logits, dim1) conf, pred probs.max(1) return class_names[pred.item()], conf.item(), probs.cpu().numpy() # 使用示例 # model build_model(num_classes5, freeze_backboneFalse) # model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) # model.to(device) # label, conf, all_probs predict(test.jpg, model, class_names, device) # print(f预测: {label}, 置信度: {conf:.3f})逻辑说明model.eval()必须调用否则 BatchNorm 和 Dropout 会按训练模式走单张推理结果会飘。torch.no_grad()省显存。unsqueeze(0)是因为模型要求输入是[N, C, H, W]单张图没有 N 维度。置信度低于某个阈值比如 0.6时实际使用中应该提示「不确定建议人工复核」而不是硬给一个类别——这一点在鉴定场景里尤其重要误判比不判代价大。4.2 批量鉴定与结果导出实际做课程设计演示时老师往往希望看到批量处理能力。你可以写一个循环遍历测试文件夹把每张图的预测结果写进 CSVimport csv import os def batch_predict(folder, model, class_names, device, out_csvresults.csv): rows [] for fname in os.listdir(folder): if not fname.lower().endswith((.jpg, .png, .bmp)): continue path os.path.join(folder, fname) label, conf, _ predict(path, model, class_names, device) rows.append([fname, label, f{conf:.4f}]) with open(out_csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([filename, predicted_class, confidence]) writer.writerows(rows) print(f已写入 {len(rows)} 条结果到 {out_csv})参数说明out_csv路径别放在只读目录。如果图片多可以加tqdm进度条。导出的 CSV 可以直接拿去和人工鉴定结果对比算混淆矩阵这也是答辩时展示模型实际价值的材料。4.3 模型导出为 ONNX 或 TorchScript如果想让模型脱离 Python 环境跑比如集成到 C 的显微镜控制软件里导出 ONNX 是常见做法import torch dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy_input, thin_section_model.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}, opset_version11 )逻辑说明dynamic_axes让 batch 维度可变推理时能一次处理多张。opset_version11兼容性较好别盲目追新。导出后用onnxruntime加载验证一遍输出是否和 PyTorch 一致差太多说明有算子不支持。5. 避坑与排查那些让准确率卡住的真实原因5.1 验证集准确率远高于测试集现象训练时验证集 92%换一批新薄片测试只有 65%。原因验证集和训练集来自同一批薄片图像风格、光照、染色条件一致模型学到了「这批图的特征」而不是矿物特征。解决划分数据时按薄片来源分组最好留一整批不同设备或不同人制作的薄片做测试集。如果资源包没做分组划分自己用文件名前缀重新分。5.2 模型把所有样本都预测成石英现象混淆矩阵里石英那一列全是数其他类召回率接近零。原因类别极度不平衡且石英样本的纹理多样性不够模型找到了「偷懒解」。解决先算类别权重加进 loss再检查石英类里是不是混进了其他矿物的误标样本。如果加了权重还不行对少数类做数据增强或者用重采样让每个 batch 里各类别比例接近。5.3 训练 loss 不降准确率在随机水平现象loss 一直在 1.6 附近五分类的随机水平准确率 20%。原因学习率太大把预训练权重冲垮了或者数据标签和文件夹对应错了。解决先把学习率降到 1e-4 试再打印几个 batch 的标签和图像路径核对。我遇到过文件夹名是中文导致class_to_idx排序错乱的情况标签全错位模型当然学不会。5.4 推理时置信度普遍偏低现象每张图最高置信度都在 0.3 到 0.5 之间模型「犹豫不决」。原因训练时用了强增强但推理时没对应或者模型欠拟合。解决检查推理预处理是否和验证时一致尤其是 Normalize 的均值和方差。如果一致还低说明模型没训够回去看验证 loss 是否还在降。5.5 显存不够batch size 只能设 4现象报 CUDA out of memory。原因图像尺寸太大或模型没冻结。解决先把img_size从 448 降到 224再把 backbone 冻结只训分类头。如果还不够用梯度累积模拟大 batch每 4 个小 batch 才optimizer.step()一次。6. 进阶技巧用 Grad-CAM 让模型「说出」它在看哪里课程设计答辩时老师最爱问的一句话是「你这个模型到底学到了什么」光给准确率不够你得能可视化模型的注意力。Grad-CAM 是最容易上手的方案它把最后一层卷积的特征图和梯度加权生成一张热力图叠在原图上红色区域就是模型做决策时重点看的地方。对岩石薄片来说如果热力图集中在矿物颗粒边缘或双晶纹上说明模型学对了如果集中在图像角落或空白区域说明数据有偏或者模型在作弊。实现上用pytorch-grad-cam库最省事from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import numpy as np # 目标层选 ResNet 最后一个卷积 block target_layers [model.layer4[-1]] cam GradCAM(modelmodel, target_layerstarget_layers) # 准备输入 rgb_img cv2.cvtColor(cv2.imread(test.jpg), cv2.COLOR_BGR2RGB) rgb_img cv2.resize(rgb_img, (224, 224)) / 255.0 input_tensor tf(rgb_img).unsqueeze(0).to(device) grayscale_cam cam(input_tensorinput_tensor)[0] visualization show_cam_on_image(rgb_img.astype(np.float32), grayscale_cam, use_rgbTrue) cv2.imwrite(cam_output.jpg, cv2.cvtColor(visualization, cv2.COLOR_RGB2BGR))参数说明target_layers选layer4[-1]是因为它感受野最大对应高层语义。如果热力图太粗糙可以换layer3[-1]分辨率更高但语义性弱一些。show_cam_on_image的use_rgbTrue别漏否则颜色通道会反。生成的热力图建议按类别各挑几张放进答辩 PPT 里比单纯列准确率有说服力得多。还有一个实用技巧是测试时增强TTA对同一张测试图做水平翻转、垂直翻转、小角度旋转分别推理后把 softmax 概率平均。这个操作不增加训练成本通常能涨 1 到 3 个点。代码就是在predict外面套一层循环把多次probs累加再除。注意 TTA 用的增强必须和训练增强同分布训练时没用的增强别在 TTA 里加。从那以后我每次拿到一个新的图像分类资源包都强制先跑一遍「按组划分 混淆矩阵 Grad-CAM」这三件套确认模型看的是对的地方再去看准确率数字。希望帮到你。本文还有配套的精品资源点击获取
返回列表