拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
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 和 torchvision:

import cv2 import numpy as np from torchvision import transforms from torch.utils.data import Dataset class ThinSectionDataset(Dataset): def __init__(self, root_dir, img_size=224, augment=True): self.root = root_dir self.img_size = img_size # 训练时增强,验证时只做归一化 if augment: self.transform = transforms.Compose([ transforms.ToPILImage(), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomVerticalFlip(p=0.5), transforms.RandomRotation(15), # 薄片无方向性,旋转安全 transforms.ColorJitter(brightness=0.2, contrast=0.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_backbone=True): model = models.resnet18(weights=models.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_backbone=True时只训练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_weight='balanced', classes=np.unique(labels), y=labels ) class_weights = torch.tensor(class_weights, dtype=torch.float).to(device) criterion = nn.CrossEntropyLoss(weight=class_weights)

参数说明:class_weight='balanced'会让权重反比于类别频率,少数类权重自然变大。注意这个权重是在训练集上算的,别用验证集。如果加了权重后多数类准确率掉得厉害,说明权重给过头了,可以手动把最大权重截断到 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_size=224): 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, dim=1) conf, pred = probs.max(1) return class_names[pred.item()], conf.item(), probs.cpu().numpy() # 使用示例 # model = build_model(num_classes=5, freeze_backbone=False) # model.load_state_dict(torch.load('best_model.pth', map_location='cpu')) # 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 批量鉴定与结果导出

实际做课程设计演示时,老师往往希望看到批量处理能力。你可以写一个循环遍历测试文件夹,把每张图的预测结果写进 CSV:

import csv import os def batch_predict(folder, model, class_names, device, out_csv='results.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='', encoding='utf-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_version=11 )

逻辑说明:dynamic_axes让 batch 维度可变,推理时能一次处理多张。opset_version=11兼容性较好,别盲目追新。导出后用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(model=model, target_layers=target_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_tensor=input_tensor)[0] visualization = show_cam_on_image(rgb_img.astype(np.float32), grayscale_cam, use_rgb=True) cv2.imwrite('cam_output.jpg', cv2.cvtColor(visualization, cv2.COLOR_RGB2BGR))

参数说明:target_layers选layer4[-1]是因为它感受野最大,对应高层语义。如果热力图太粗糙,可以换layer3[-1],分辨率更高但语义性弱一些。show_cam_on_image的use_rgb=True别漏,否则颜色通道会反。生成的热力图建议按类别各挑几张,放进答辩 PPT 里,比单纯列准确率有说服力得多。

还有一个实用技巧是测试时增强(TTA):对同一张测试图做水平翻转、垂直翻转、小角度旋转,分别推理后把 softmax 概率平均。这个操作不增加训练成本,通常能涨 1 到 3 个点。代码就是在predict外面套一层循环,把多次probs累加再除。注意 TTA 用的增强必须和训练增强同分布,训练时没用的增强别在 TTA 里加。

从那以后我每次拿到一个新的图像分类资源包,都强制先跑一遍「按组划分 + 混淆矩阵 + Grad-CAM」这三件套,确认模型看的是对的地方,再去看准确率数字。希望帮到你。

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

返回列表