☰
基于机器学习的岩石薄片自动鉴定:从数据标注到模型部署的完整链路
2026/10/6 5:45:50 网站建设 项目流程

简介:这份资源面向地质学、计算机相关专业的毕业设计、课程设计与期末大作业场景,提供一套基于机器学习的岩石薄片自动鉴定完整项目。内容围绕卷积神经网络、随机森林、支持向量机等算法展开,涵盖图像预处理、特征提取、模型训练与评估等环节,帮助读者理解岩石纹理、结构与矿物组合的自动分类流程。压缩包共28个文件,约758KB,以15个Python脚本为核心,配套4个Markdown说明文档、3张示例图片、2个文本文件及1个Excel结果表,另含配置文件与忽略规则,便于快速运行与二次开发。目录中划分了数据统计、模型测试、训练器、数据集与网络定义等模块,结构清晰,适合作为可复用的工程模板。目前已有46人学习,读者可据此掌握从数据准备到模型验证的完整思路,并在此基础上完成自己的课题实践。

1. 岩石薄片自动鉴定:从一块石头到一张标签的完整链路

地质队里最磨人的活,不是出野外,是回到室内对着偏光显微镜一张一张看薄片。一块岩石切成 0.03 毫米厚的薄片,放在正交偏光下,石英、长石、方解石、角闪石各有各的消光行为和干涉色,有经验的人一眼能认,没经验的人盯一下午也分不清微斜长石和正长石。基于机器学习的岩石薄片自动鉴定,要解决的就是这件事:把显微镜下拍到的薄片图像,交给模型输出矿物类别甚至岩石定名。它适合两类人——一类是地质专业想把手里的薄片数据批量处理掉的研究者,另一类是懂机器学习但没碰过岩石图像的算法工程师。整条链路不复杂:采图、标注、训练、推理、复核,难的是每一步都有地质学的坑,不是调个参就能糊弄过去的。

2. 先搞清楚模型要认什么:矿物颗粒还是整张薄片

2.1 两种任务定义,决定了后面所有数据工作

岩石薄片自动鉴定在落地时,第一个要拍板的问题不是选什么网络,而是鉴定粒度。常见做法有两种:一是矿物颗粒级分类,把图像里每个颗粒分割出来,判断它是石英、钾长石、斜长石还是黑云母;二是岩石整片定名,直接给整张薄片一个标签,比如“花岗闪长岩”“石英砂岩”。这两条路的数据标注方式、模型结构、评价指标完全不同。

颗粒级分类本质是语义分割或实例分割任务,标注时要用多边形把每个颗粒轮廓勾出来,工作量大但信息密度高,后续还能统计矿物含量、粒度分布、颗粒接触关系。整片定名本质是图像分类任务,标注成本低,但一张薄片里矿物组合复杂,单标签会丢掉大量信息,而且类别不平衡极严重——某些岩性在数据集里可能只有几十张。

我一般建议:如果手头薄片数量在几百张以内,先做整片定名,快速验证可行性;如果超过一千张且有地质人员愿意配合标注,直接上颗粒级分割,因为颗粒级结果可以聚合出整片定名,反过来不行。这里有个容易被忽略的点:同一岩石名称在不同地区的矿物组合可能有差异,比如同样是“花岗岩”,有的地方钾长石多,有的地方斜长石多,模型如果只学了单一来源的样本,换一个地区准确率会断崖式下跌。

2.2 偏光图像的特殊性:为什么直接套 ImageNet 预训练模型会翻车

自然图像里物体有纹理、有阴影、有透视,偏光显微镜下的矿物颗粒却是另一套视觉逻辑。单偏光下看颜色和形态,正交偏光下看干涉色和消光位,同一颗粒旋转载物台会呈现完全不同的颜色。这意味着同一矿物在不同偏光条件下的像素分布差异极大,而不同矿物在某一特定条件下又可能颜色相近。

常见做法是采集多模态输入:同一视域分别拍单偏光、正交偏光、甚至加石膏试板的图像,把三张图叠成多通道输入模型。如果只拍一种,模型很容易学到“颜色=类别”的捷径,换一台显微镜或换一个曝光参数就失效。另一个坑是白平衡和曝光,不同显微镜、不同相机、不同光源老化程度都会让同一矿物的 RGB 值漂移,所以数据增强里必须包含颜色抖动,而且抖动幅度要比自然图像任务大。

提示:如果条件允许,采集时固定显微镜型号、光源亮度和相机参数,并在每批采集前拍一张标准色卡。后期做颜色归一化时,色卡就是后悔药。

3. 从原始图像到训练集:标注、增强与划分的实操细节

3.1 用 Labelme 标注颗粒并转成掩码的完整脚本

假设我们走颗粒级分割路线,标注工具用 Labelme,每张图对应一个 JSON 文件,里面是多边形坐标和标签。下面这段脚本把 Labelme 的 JSON 批量转成训练用的掩码图,同时生成类别索引文件。

import json import os import numpy as np import cv2 from glob import glob # 类别定义:背景为 0,其余从 1 开始 CLASSES = ["quartz", "k_feldspar", "plagioclase", "biotite", "amphibole", "calcite"] CLASS_TO_ID = {name: idx + 1 for idx, name in enumerate(CLASSES)} def json_to_mask(json_path, output_dir): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_h = data["imageHeight"] img_w = data["imageWidth"] mask = np.zeros((img_h, img_w), dtype=np.uint8) for shape in data["shapes"]: label = shape["label"] if label not in CLASS_TO_ID: continue # 跳过未定义类别,避免训练时索引越界 points = np.array(shape["points"], dtype=np.int32) # 填充多边形,颜色值即类别 ID cv2.fillPoly(mask, [points], color=CLASS_TO_ID[label]) base = os.path.splitext(os.path.basename(json_path))[0] out_path = os.path.join(output_dir, base + ".png") cv2.imwrite(out_path, mask) return out_path if __name__ == "__main__": json_files = glob("./raw_labelme/*.json") os.makedirs("./masks", exist_ok=True) for jf in json_files: json_to_mask(jf, "./masks") print(f"done, {len(json_files)} masks generated")

这段代码的逻辑很直接:读 JSON、建全零掩码、按多边形填充类别 ID、存 PNG。关键参数在CLASS_TO_ID,它决定了训练时每个像素的标签值,必须和后面数据集类的类别数严格一致,否则会出现“模型输出 7 类但标签最大是 6”这种低级错误。另一个细节是cv2.fillPoly的填充顺序,如果两个颗粒有重叠标注,后画的会覆盖先画的,标注时要提醒地质人员不要重复勾同一区域。

3.2 数据增强不能照搬自然图像那一套

偏光图像做增强时,几何变换和颜色变换要分开对待。几何变换(翻转、旋转、缩放)可以大胆用,因为矿物颗粒的形态不依赖方向,旋转 90 度不会改变它是石英的事实。但要注意:旋转后图像边缘会出现黑边,如果直接送进网络,模型可能学到“黑边=某类”的伪相关。常见做法是旋转后做中心裁剪,或者用镜像填充。

颜色变换要谨慎。前面说过同一矿物在不同偏光条件下颜色差异大,所以适度的亮度、对比度、饱和度抖动是必要的,但色相抖动幅度不能太大,否则石英的干涉色可能被抖成方解石的干涉色,模型学到的就是噪声。我一般把色相抖动控制在 ±10 度以内,亮度 ±20%,对比度 ±15%。

还有一个地质学特有的增强手段:模拟不同旋转角度的消光变化。正交偏光下旋转载物台,矿物会周期性地变黑(消光)。可以在训练时随机对图像做不同角度的“消光模拟”——把某些区域亮度压暗,模拟消光位。这个操作能显著提升模型对消光行为的鲁棒性,但实现起来需要知道每个颗粒的消光角,标注成本高,适合后期优化阶段做。

3.3 训练集、验证集、测试集怎么分才不骗自己

随机划分是最省事的,也是最容易骗自己的。岩石薄片往往来自不同钻孔、不同深度、不同手标本,如果同一块手标本切出的多张薄片被随机分到训练集和测试集,模型在测试集上的表现会虚高,因为它见过同一块石头的“兄弟”图像。

正确做法是按手标本或按钻孔分组划分:同一块手标本的所有薄片只能出现在一个集合里。这样测试集才能真正反映模型面对新样本时的能力。如果数据量允许,还可以做跨区域验证:用 A 地区的薄片训练,用 B 地区的薄片测试,这个指标才是模型能不能推广到新工区的关键。

注意:如果分组划分后测试集准确率比随机划分低很多,不要急着调模型,先检查是不是数据泄漏。这个差距本身就是有价值的信息。

4. 模型选型与训练:U-Net、DeepLab 还是自己搭

4.1 分割网络的三个必调参数

颗粒级分割最常用的基线是 U-Net 和 DeepLabV3+。U-Net 结构简单、对小数据集友好,适合薄片数量不多的情况;DeepLabV3+ 带空洞卷积和多尺度特征,对颗粒边界模糊的图像更稳,但需要更多数据才能训起来。不管选哪个,有三个参数必须根据岩石图像的特点调整。

第一个是输入尺寸。偏光图像里颗粒尺度差异大,有的薄片里颗粒占几百像素,有的只有几十像素。如果统一缩放到 512×512,小颗粒可能只剩几个像素,分割精度直接崩。常见做法是多尺度训练:随机从原图裁剪 512×512、768×768、1024×1024 的块,让模型见过不同尺度的颗粒。推理时用滑动窗口加重叠,最后做投票融合。

第二个是损失函数。岩石薄片里背景和某些矿物(比如石英)占比极高,交叉熵损失会让模型倾向于全预测成石英。换成Dice Loss + Focal Loss 的组合更稳:Dice 管类别不平衡,Focal 管难分样本。我一般用0.5 * Dice + 0.5 * Focal,Focal 的 gamma 设 2.0。

第三个是学习率策略。分割任务对学习率敏感,初始学习率太大容易在早期把编码器权重带偏。用余弦退火 + 热重启,初始 lr 设 1e-4,每 20 个 epoch 重启一次,配合 AdamW 优化器,权重衰减 1e-4。这个组合在薄片数据上比固定学习率稳定得多。

4.2 一个可复现的训练循环骨架

下面这段代码用 PyTorch 搭一个最小训练循环,重点看数据加载和损失计算部分。

import torch import torch.nn as nn from torch.utils.data import DataLoader, Dataset import segmentation_models_pytorch as smp class ThinSectionDataset(Dataset): def __init__(self, img_paths, mask_paths, transform=None): self.img_paths = img_paths self.mask_paths = mask_paths self.transform = transform def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img = cv2.imread(self.img_paths[idx]) # BGR img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask = cv2.imread(self.mask_paths[idx], cv2.IMREAD_GRAYSCALE) if self.transform: augmented = self.transform(image=img, mask=mask) img, mask = augmented["image"], augmented["mask"] img = torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 mask = torch.from_numpy(mask).long() return img, mask # 模型:U-Net + EfficientNet-B3 编码器,类别数含背景 model = smp.Unet( encoder_name="efficientnet-b3", encoder_weights="imagenet", in_channels=3, classes=len(CLASSES) + 1 # 背景 + 矿物类别 ) dice_loss = smp.losses.DiceLoss(mode="multiclass") focal_loss = smp.losses.FocalLoss(mode="multiclass", gamma=2.0) def criterion(pred, target): return 0.5 * dice_loss(pred, target) + 0.5 * focal_loss(pred, target) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=20) for epoch in range(100): model.train() for img, mask in train_loader: img, mask = img.cuda(), mask.cuda() pred = model(img) loss = criterion(pred, mask) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()

这段代码里,classes=len(CLASSES) + 1是背景类,如果忘了加 1,训练时标签里的 0 会被当成无效值忽略,模型永远学不会背景。encoder_weights="imagenet"用了预训练权重,虽然偏光图像和自然图像差异大,但底层边缘和纹理特征仍然可迁移,比从零训快很多。损失函数组合里 Dice 和 Focal 各占一半,实际调的时候可以先跑一轮看哪个 loss 下降更慢,适当调权重。

4.3 训练过程中看什么指标才不盲目

分割任务不能只看准确率。岩石薄片里石英可能占 60% 像素,模型全预测石英也有 60% 准确率,但毫无意义。要盯三个指标:各类别 IoU、边界 F1、混淆矩阵。

各类别 IoU 能看出哪个矿物分得差,通常长石类(钾长石和斜长石)混淆最严重,因为它们的干涉色和双晶特征在低倍镜下很接近。边界 F1 衡量颗粒轮廓的贴合程度,如果边界 F1 低但 IoU 还行,说明模型把颗粒内部认对了但边缘糊了,这时候要检查损失函数里边界权重够不够。混淆矩阵直接看谁被错认成谁,如果黑云母大量被认成角闪石,可能是颜色增强把深色矿物都抖成一样了。

提示:每训完一个 epoch 存一次验证集上的混淆矩阵图,训练结束后翻一遍,比看 loss 曲线有用得多。

5. 避坑与排查:薄片自动鉴定里最容易翻车的五件事

5.1 现象:验证集准确率 95%,换一批薄片掉到 60%

原因:数据泄漏或域偏移。前者是同一手标本的薄片被分到了训练和验证集,后者是训练集和验证集来自不同显微镜或不同曝光参数。地质样本的采集批次往往和岩性相关,比如某次集中采了一批花岗岩,如果按随机划分,模型可能只是记住了这批图像的色调。

解决:按手标本或钻孔分组划分,并在训练时加入强颜色增强。如果换批后掉得厉害,先做颜色归一化——用标准色卡或直方图匹配把新图像的颜色分布对齐到训练集。

5.2 现象:模型把石英和斜长石大量混淆

原因:这两种矿物在单偏光下都是无色透明,正交偏光下石英是灰白干涉色,斜长石有聚片双晶但低倍镜下不明显。如果只输入单偏光图像,模型几乎没有区分依据。

解决:加入正交偏光图像作为额外通道,或者用双分支网络分别处理单偏光和正交偏光,在特征层融合。如果只能拍一种,至少要在正交偏光下拍,因为干涉色信息量更大。

5.3 现象:颗粒边界分割得支离破碎,一个颗粒被切成好几块

原因:损失函数里边界权重不足,或者输入分辨率太低导致小颗粒信息丢失。另外,标注时如果颗粒边界勾得不准,模型学到的边界就是模糊的。

解决:在损失函数里加边界加权,用形态学梯度提取标注边界,给边界像素更高权重。输入分辨率至少保持 512×512,小颗粒多的薄片用 1024×1024。标注规范要统一,颗粒边界以实际接触线为准,不要留空隙也不要重叠。

5.4 现象:训练 loss 震荡剧烈,偶尔跳到 NaN

原因:学习率太大,或者数据里有异常样本(比如全黑图、标注全零的图)。偏光图像里如果某张图曝光失败,像素值全在 0 附近,归一化后梯度会爆炸。

解决:训练前做数据清洗,统计每张图的均值和方差,剔除异常样本。学习率用 warmup,前 5 个 epoch 从 1e-6 线性升到 1e-4。加梯度裁剪,torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)。

5.5 现象:推理时单张图要好几秒,批量处理等不起

原因:滑动窗口重叠太大,或者模型参数量太大。U-Net + EfficientNet-B3 在 1024×1024 输入下,单张推理在 GPU 上大概 0.5 秒,如果重叠 50% 做滑窗,一张大图要跑几十次。

解决:推理时用 TensorRT 或 ONNX Runtime 加速,重叠率降到 25%,或者把模型换成轻量编码器(MobileNetV3、EfficientNet-B0)。如果精度允许,先下采样再推理,后处理时上采样回原尺寸。

6. 让模型真正能用:后处理、置信度与人工复核的配合

模型输出掩码只是第一步,直接拿掩码去定名还会翻车。我一般会在推理后加三层后处理。第一层是形态学清理:用开运算去掉小面积噪声区域,用闭运算填补颗粒内部空洞,面积阈值根据薄片放大倍数定,通常小于 50 像素的连通域直接丢掉。第二层是类别投票:对每个颗粒区域,统计模型预测的类别概率均值,如果最高概率低于 0.6,标记为“待复核”,不强行给标签。第三层是矿物含量聚合:统计各类别像素占比,和岩石定名标准做匹配,比如石英占比超过 60% 且长石以钾长石为主,倾向定为花岗岩。

置信度阈值不是拍脑袋定的。我会在验证集上画置信度-准确率曲线:把预测按置信度分桶,看每个桶里的实际准确率。如果置信度 0.5 到 0.6 的桶里准确率只有 0.3,说明模型在这个区间不可信,阈值就要往上提。通常分割任务里,置信度 0.7 以上的区域准确率能到 0.9,0.5 以下的基本是噪声。

人工复核环节不能省。地质定名本身就有主观性,不同的人对同一薄片可能给出不同名称,模型的作用是把 90% 的常规样本自动处理掉,把 10% 的疑难样本挑出来给人看。复核界面里要同时显示原图、模型掩码、置信度热力图,让地质人员一眼看出模型哪里不确定。我习惯把置信度低于阈值的区域用红色边框标出,复核人员只需要看这些区域,效率比从头看一遍高得多。

最后说一个我自己的教训:早期做这个方案时,我花了两周调模型结构,准确率从 82% 提到 85%,后来花了一天做颜色归一化和分组划分,准确率直接跳到 91%。岩石薄片自动鉴定里,数据质量比模型结构重要得多。如果你刚开始做,先把采集规范、标注规范、划分策略定死,再动模型。希望帮到你。

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

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

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

立即咨询