☰
岩石薄片自动鉴定:机器学习与语义分割的工程实践
2026/9/30 17:26:19 网站建设 项目流程

简介:机器学习在岩石薄片自动鉴定中的应用已成为地质学研究的新趋势,本题材项目包面向地质学及相关专业学生的毕业设计、课程设计与期末大作业,聚焦利用卷积神经网络等算法替代传统人工镜下鉴定,提升岩矿识别效率与准确性。压缩包共包含28个文件,主要为15个Python脚本、4个Markdown说明文档、3张图片、2个文本说明及1个xlsx结果表格,整体大小758KB;Python脚本覆盖数据处理、特征提取(颜色统计、GLCM纹理、LBP局部二值模式)、模型训练、评估与可视化等完整流程,同时涉及随机森林、支持向量机等经典方法,配置灵活。已有42人学习浏览,适合对机器学习图像分类感兴趣的入门至中级学习者参考。通过该资源可快速搭建岩石薄片自动鉴定实验,理解从图像预处理、特征工程到模型调优的各个环节,并结合result.xlsx输出结果进行对比分析,为课程汇报或论文写作提供可复现的实践依据。目录结构清晰,附有README与依赖说明,便于上手。

1. 从一张偏光薄片到一份定名报告:岩石薄片自动鉴定在做什么

一个地质实验室的日常是:磨片、偏光显微镜下数颗粒、查光学性质表、写出矿物含量和岩石定名。熟练的鉴定人员一天能出三五十片,但疲劳带来的误判、颗粒统计时的主观偏差,几乎无法避免。基于机器学习的岩石薄片自动鉴定,本质是把“镜下认矿物”这个高度依赖经验的过程,转译成图像分割与分类问题:输入一张薄片图像,输出每个矿物类别的像素级分布、面积百分比和定名建议。它要解决的不是“能不能识别出来”,而是“识别结果能不能被人复核、能不能替代重复劳动、能不能在跨批次数据上稳定复现”。这套方案适合现阶段的岩矿鉴定中心、油气储层研究团队,以及需要批量复查历史薄片的实验室。它的下限是帮你看片,上限是直接驱动自动扫描平台的每一项任务。

2. 岩石薄片里到底有什么特征可学:从光学性质到像素映射

2.1 偏光图像上真正有用的三类视觉信号

先回答一个常被问的问题:机器学习模型到底在看薄片里的什么?人工鉴定靠的是光学性质组合,单偏光下发色、吸收性、解理、突起;正交偏光下看干涉色、双折射、消光角和延性符号;加石膏试板后看补色。这些性质落到图像上,其实是三组视觉信号:颜色分布、纹理结构、边缘梯度。颜色分布对应矿物吸收性和干涉色,纹理结构对应解理纹和双晶纹,边缘梯度对应突起和解理缝。模型不需要你告诉它“这个矿物是正高突起”,但它需要你给它足够多的视觉证据去拟合这个规律。

这里有一个很多项目第一次就翻车的设计点:只给模型单张正交偏光照片。单偏光下石英和钾长石颜色相近,正交偏光下干涉色又受切片方向和矿物厚度影响,任何一个单一偏振态的信息都不完整。我一般会选择同一个薄片视域下拍摄单偏光和正交偏光两张照片,在通道维度拼成一个多通道输入,让模型同时吸收两种光学状态的信息。如果条件允许,再加一张加了石膏试板的正交偏光图,对识别干涉色接近的矿物非常关键。锥光下的干涉图暂时不建议放进第一版,它对拍摄位置要求极高,且能提供的类别区分度有限。

你不需要在训练前手工提取颜色矩、纹理特征、边缘直方图这些传统特征,现代分割模型的卷积核会自动组合它们。但你需要理解:模型天然的归纳偏置会优先利用颜色统计,其次是纹理,边缘信息最容易被忽略。所以当模型在某种矿物上表现差时,先去看它的干涉色是否稳定、切片厚度是否一致,而不是急着换网络结构。一个常见做法是把切片厚度控制在标准0.03mm,厚度偏大会让干涉色整体升高,模型学到的颜色映射会被带偏。

2.2 语义分割和目标检测:选错基线模型会推倒重来

岩石薄片自动鉴定的产物决定了模型选型。如果你要的是“这块岩石叫什么名字”,你需要各矿物的面积占比,这对应的就是像素级分类,即语义分割。如果只是要“这张片子里有几个斑晶、裂缝走向如何”,目标检测就更合适。我见过不止一个团队一开始用目标检测框矿物颗粒,结果框住之后拿不到精确面积,最后还是回到分割方案,等于重做了一套标注。

任务模型方向输出粒度训练成本适用场景
矿物含量估算、岩石定名语义分割(U-Net类)每个像素的类别中全岩定量分析、批量复查
斑晶计数、裂缝识别目标检测(YOLO类)对象边界框低结构统计、铸体薄片孔隙分析
颗粒边界精细刻画实例分割(Mask R-CNN类)每个颗粒单独实例高晶粒尺寸分布统计

我的建议是第一次做就直接上语义分割,以U-Net作为baseline。U-Net对小样本友好,结构稳定,解码器出来的边界也比较完整,后续换成SegFormer、TransUNet也只需要改编码器部分,数据加载和损失函数都能复用。U-Net输出的像素类别图可以直接累加得到面积百分比,这一步是从图像到地质结论最短的路径。只有当某两类矿物在边界上始终混淆、单靠分割怎么调都分不开时,再去考虑实例分割或更重的Transformer结构。

2.3 标注规范:把岩石学语言翻译成模型能学的标签

标注是这类项目里最容易被低估的环节。岩石薄片不是自然图像,一张图里可能同时有斑晶、基质、蚀变区、孔隙、裂缝,不同矿物还可能互相包裹。如果不先把标注体系定清楚,后面积累的三百张图全部要返工。

我的标注规范通常按两级组织。一级是矿物类别,采用岩石学名称的英文简写,比如石英(qtz)、钾长石(kfs)、斜长石(pl)、黑云母(bt)、角闪石(hbl)、辉石(cpx)、方解石(cal)、不透明矿物(opq)、孔隙(pore)、基质(matrix)。二级是结构标注,单独把蚀变区、包裹体、待定区域标成独立标签,而不是强行塞进某个矿物类。这样模型学到的不是“石英长什么样”,而是“这个视域里哪块区域证据充足、哪块证据不足”,对后续置信度评估非常有帮助。

标注工具上用Labelme或AnyLabeling都可以,但导出格式最好转成统一的单通道PNG掩码。下面是把Labelme的JSON多边形批量转成掩码的一段脚本,项目里所有标注统一走这一步,避免多个人标注出来的格式不一致。

import json import numpy as np import cv2 from pathlib import Path def labelme_json_to_mask(json_path, label_map, mask_size=(2048, 2048), boundary_shrink=2): # label_map: {"qtz": 1, "kfs": 2, "pl": 3, "bt": 4, ...} # 输出: 单通道uint8掩码,背景为0,矿物类别从1开始连续编号 with open(json_path, "r", encoding="utf-8") as f: label_data = json.load(f) mask = np.zeros(mask_size, dtype=np.uint8) for shape in label_data["shapes"]: label = shape["label"].lower() if label not in label_map: continue # 不在类别表里的标注直接忽略,避免污染训练集 points = np.array(shape["points"], dtype=np.int32) class_id = label_map[label] # 对多边形外轮廓做少量腐蚀,让标注边界离开矿物真实边缘 if boundary_shrink > 0 and len(points) > 2: kernel = np.ones((boundary_shrink, boundary_shrink), np.uint8) temp = np.zeros(mask_size[:2], dtype=np.uint8) cv2.fillPoly(temp, [points], 1) temp = cv2.erode(temp, kernel, iterations=1) mask[temp > 0] = class_id else: cv2.fillPoly(mask, [points], class_id) return mask

这段脚本的核心逻辑是:从JSON里读出多边形顶点,按label_map映射成类别编号,再用OpenCV的fillPoly填充到掩码上。这里的boundary_shrink参数值得单独解释,它会让每个标注区域向内收缩2个像素。岩石薄片矿物边缘通常有渐变过渡带,标注员对边界位置的主观偏差也集中在这里,把边界退让掉,模型训练时就不会被这些不确定性误导,推理结果反而更干净。如果你发现预测的矿物边界有锯齿状碎条纹,优先检查是不是没有做这一步收缩。

3. 跑通最小闭环:数据准备、训练与含量换算

3.1 薄片库怎么拍才值得入模

数据从哪里来,决定了模型能走多远。以偏光显微镜为基准的采集流程,最怕的是每张图曝光时间和光源强度不统一。很多实验室的显微镜自动曝光是默认开启的,这会让视域亮度随着矿物颜色深浅跳动,模型被迫去学习“亮度歧视”,而不是矿物本身的特征。拍摄规范应该在项目启动第一天定死。

参数建议值说明
薄片厚度0.03mm标准薄片厚度影响干涉色级序,不能随意取
放大倍数5x起步,10x备选低倍覆盖更多矿物颗粒,高倍用于细粒
偏振通道单偏光 + 正交偏光成对采集同一视域、同一对焦平面
曝光模式手动固定曝光关闭自动曝光,固定光强和快门
图像格式不压缩TIFF或高质量PNGJPEG压缩会破坏干涉色边缘
每样视域数至少3-5个视域单视域打点无法代表全岩

图像命名也要统一,我常用的规则是Sample_Field_PolType.tif,比如S025_F03_XPL.tif表示25号样品第3视域正交偏光图,S025_F03_PPL.tif是同一个视域的单偏光图。训练数据加载时靠文件名前缀自动配对,不要靠目录结构手动维护,后面加数据会省掉大量返工。配对失败是这类项目第一周最常见的报错来源。

3.2 训练脚本里的几个关键设计

模型选择U-Net之后,真正拉开差距的是数据增强和损失函数。岩石薄片属于小样本场景,起步阶段往往只有几十张到一两百张图,增强策略直接决定模型是学到矿物学规律还是背下训练集。

增强方式作用注意点
旋转90°/180°/270°打破方向偏好石英干涉色不随旋转改变,但解理方向会变
左右/上下翻折增加样本多样性地质结构无左右语义,安全
色阶扰动模拟光源色温漂移幅度控制在正负15%以内,过大引入噪声
弹性形变模拟薄片轻微变形仅用于细粒矿物,粗粒相不建议

损失函数我第一版会同时用Dice损失和交叉熵的加权和。用交叉熵保证逐像素分类稳定,用Dice缓解背景占比过大带来的不平衡。岩石薄片里往往“基质”占了一半像素,而稀有矿物只有几个颗粒,如果直接用交叉熵,稀有矿物的梯度会被稀释掉。

import torch import torch.nn.functional as F class WeightedDiceCE(torch.nn.Module): def __init__(self, class_weights, dice_weight=0.5, ignore_index=0): super().__init__() # class_weights: 每个类别一个float,稀有矿物给更大的权重 self.class_weights = torch.tensor(class_weights, dtype=torch.float32) self.dice_weight = dice_weight self.ignore_index = ignore_index def forward(self, logits, targets): # logits: [N, C, H, W] 未过softmax的原始输出 # targets: [N, H, W] 类别索引张量 num_classes = logits.shape[1] ce = F.cross_entropy( logits, targets, weight=self.class_weights.to(logits.device), ignore_index=self.ignore_index ) probs = F.softmax(logits, dim=1) dice = 0.0 for c in range(num_classes): if c == self.ignore_index: continue pred_c = probs[:, c, :, :] target_c = (targets == c).float() intersection = (pred_c * target_c).sum(dim=(1, 2)) union = pred_c.sum(dim=(1, 2)) + target_c.sum(dim=(1, 2)) dice += (2.0 * intersection + 1e-6) / (union + 1e-6) dice /= (num_classes - 1) return ce + self.dice_weight * (1.0 - dice.mean())

这段代码把两类损失合在一起。cross_entropy的weight参数直接吃类别权重,稀有矿物类别给8到12的权重值,常见矿物给1就行。Dice部分对每个类别独立计算后取平均,避免被基质类别主导。ignore_index设为0,也就是背景或待定区域不参与损失计算,这样标注时涂成黑色的区域不会反向教会模型去预测任何矿物。第一版训练时dice_weight建议0.5,后期如果边界碎条纹多,可以提到0.8。

训练脚本按标准PyTorch训练循环走就可以,batch size尽量设到8以上,但受限于薄片图像的尺寸,会先用512×512的crop作为输入。这里有个经验值:输入分辨率不要低于256,否则细小的解理纹和双晶纹在降采样时直接消失,模型的分类能力会明显变差。显存不够就减少batch size,不要降分辨率。

3.3 从掩码到矿物含量和定名

模型推理得到的是每个像素的类别索引,离“这块岩石叫什么”还差两步。第一步是把像素数换算成面积百分比,第二步是按岩石分类方案落点。面积百分比在尺度不变的前提下等价于像素百分比,方法是从类别掩码里统计每个类别的出现频率。

import numpy as np def mask_to_mineral_content(mask, class_names): # mask: 单通道预测掩码,值为类别索引 # class_names: 类别名称列表,索引与训练时label_map保持一致 total_pixels = mask.size content = {} for class_id, name in enumerate(class_names): if class_id == 0: continue # 背景不计入含量 content[name] = round(float((mask == class_id).sum()) / total_pixels * 100.0, 2) return content # 使用示例:一张512x512的预测掩码 pred_mask = np.array([ [1, 1, 1, 2], [1, 2, 2, 2], [0, 3, 3, 3], [0, 0, 3, 3] ]) classes = ["background", "qtz", "kfs", "pl"] print(mask_to_mineral_content(pred_mask, classes)) # 结果示例: {'qtz': 18.75, 'kfs': 25.0, 'pl': 31.25}

这个函数本身不复杂,但有几个使用边界要注意。第一,多视域推断时要按“同一个样品所有视域合并”来统计含量,而不是每个视域单独出报告,单视域会因矿物分布不均导致含量漂移。第二,含量表里要保留“背景/待定区”的占比,如果待定区超过5%,说明这个视域的成像质量或模型置信度不达标,报告里要打上复核标记。第三,岩石定名不能只靠含量百分比硬套QAPF图解,还要结合结构信息,这一步目前没有统一标准方案,常规做法是把含量表给地质师做最终定名,模型只给建议名称和置信度。

4. 岩石薄片自动鉴定避坑指南:五个高频问题

4.1 正交偏光下“同矿不同色”导致边界闪烁

现象:同一个石英颗粒内部,一部分区域被预测成石英,另一部分被预测成钾长石,预测结果在颗粒内部出现大块色斑,边界不稳定。

原因:正交偏光下干涉色随矿物结晶方向变化,同一颗粒不同部分的光程差不同,呈现不同颜色的干涉色。模型看到的颜色证据不一致,就把一个颗粒切成两半。

解决:标注阶段按完整颗粒边界标注,不让切割面影响颗粒整体归属;推理阶段对预测掩码做连通域分析,单个连通域内部做类别众数替换,也就是把连通域里占比最高的类别赋给整个连通域。这个后处理在矿物颗粒粒度明显大于像素尺度时非常有效。

4.2 不同批次薄片颜色漂移导致误判

现象:换了一台显微镜,或者偏振片使用时间长了之后,同一矿物在新图上被预测成另外一类。实验室复测时准确率从91%掉到70%左右。

原因:光源色温、偏振片透过率、相机色彩响应发生了轻微变化,模型学到的颜色映射不适用了。

解决:拍摄阶段用固定色温光源和手动曝光;训练阶段在数据增强里加入色阶扰动,模拟光源老化带来的偏移。如果批量推理时还出问题,可以在推理管线里加一个直方图匹配步骤,把当前图像的直方图对齐到训练集参考图的平均直方图。这一步只做色彩校正,不做几何变换。

4.3 斑晶、基质、蚀变三者在掩码层互相污染

现象:斑晶边缘总是被预测成基质,蚀变部分的预测结果在矿物与基质之间反复横跳,含量报告里“基质”占比虚高。

原因:标注员对斑晶和基质的边界认知不统一,导致掩码边界区域存在大量噪声标签。模型学到的是“边缘区域模糊”,而不是“边界处是过渡带”。

解决:标注规范强制要求“斑晶、基质、蚀变区三者不得重叠标注”,如果一个斑晶边缘明显蚀变,就额外标一个独立的蚀变区类,而不是把蚀变部分涂进斑晶或者基质里。训练时对掩码做2像素收缩,让边界过渡带成为一个“无标签地带”,不参与损失计算。

4.4 稀有矿物类别权重配不好,直接消失

现象:训练了50个epoch,验证集上其他矿物的mIOU都在0.7以上,角闪石却从来没被预测出来过,含量表里永远查无此矿物。

原因:角闪石在全部训练像素里占比不到0.5%,交叉熵损失根本分不到足够梯度,模型为了降低整体损失,直接把这一类别放弃。

解决:按像素占比的倒数设置类别权重,比如背景1、石英1、钾长石1,稀有矿物直接给8-12;训练时采样crop优先选包含稀有矿物的位置,如果一块薄片视域里完全没有稀有矿物,就降低它的采样概率。这里用“过采样+类别权重”双保险,单靠一样都容易失效。

4.5 标注边界噪声导致预测掩码出现细碎条纹

现象:预测出来的矿物边界不是平滑曲线,而是锯齿状碎条纹,形态像是被腐蚀过的邮票边缘。对面积含量影响不大,但图一放出来地质师直接质疑结果。

原因:标注员在不同批次的标注中,对矿物边界的勾画位置有2到5个像素的位置偏差,模型学到了一种“不确定的边界抖动”。

解决:标注转换时统一做边界收缩;训练结束后对推理掩码做形态学闭运算,先膨胀再腐蚀,把细碎条纹抹平。如果人工勾画偏差过大,考虑换用模型辅助标注的方式,先让模型出初始掩码,人工只修正明显错误区域,这样标注噪声会大幅降低。

5. 往真实实验室推一步:报告、复核与自动扫描台对接

5.1 交付物应该包含哪几张图和哪几份表

模型跑通只是项目的一半,另一半是让地质师愿意用。地质师不可能逐个检查像素掩码,他们需要一个能直接放进报告里的产物包。我在实际项目里每次交付至少包含三张图:原图叠加掩码图、单矿物高亮图、置信度热力图,以及两张表:矿物含量统计表、逐视域质量检查表。

产物内容使用人
叠加掩码图原图上叠加半透明类别颜色地质师快速判断边界是否正确
单矿物高亮图单独把某类矿物高亮出来针对性研究某一组分
置信度热力图每个像素预测置信度发现模型没有把握的区域
矿物含量表样品级、视域级面积百分比报告直接引用
质量检查表每视域待定区占比、低置信像素占比实验室质检环节

产出的报表我一般用CSV格式,编码用UTF-8-SIG,否则在Windows上打开会出现中文乱码。生成脚本直接挂在推理管线后面,模型跑完一个样品,自动输出一张表和一个文件夹。这里强调一下,地质报告对图表的规范性要求很高,半透明掩码叠加图的透明度建议设置为45%-55%,太淡看不清楚,太浓会盖住干涉色本身。

5.2 置信度与人工复核:什么时候必须请地质师上场

自动鉴定的核心不是替代人,是把人的精力集中到最难的样品上。我按预测置信度和待定区占比把每个视域分为三级:绿色直接出报告,黄色需要地质师扫描一眼,红色必须人工重看。这个分级策略比单纯看准确率更能建立团队信任,因为复核流程有明确依据。

分级判定条件处理方式
自动通过平均置信度>0.85且待定区占比<3%直接进含量统计
人工复核平均置信度0.7-0.85,或待定区3%-8%地质师看一次叠加图
重新采集平均置信度<0.7,或待定区>8%重新拍摄该视域

置信度从哪里来,很多人误用softmax输出当作置信度,这在岩石薄片场景下并不靠谱,因为不同矿物的类别先验差异太大。我习惯用两个信号组合:softmax概率的平均值,加上掩码里连通域内部的“类别一致性”。如果同一颗粒预测结果里频繁出现孤立小斑块,哪怕概率很高也说明模型不稳定,这个视域就应该降级处理。

5.3 从单视域到整片扫描:数据流怎么扩展

单视域推理只是最小闭环,实验室真正要的是整张薄片自动扫。常见做法是显微镜配电动载物台,按固定步距移动视域,每步拍照后做图像拼接,再对拼接后的全景薄片图做滑窗推理。这个场景下推理速度不是瓶颈,显微镜的机械运动和对焦时间才是。数据流上要注意两点:一是滑窗步长要小于模型输入尺寸的1/4,保证重叠区域足够,否则拼回来时颗粒会被切成两半;二是一次推理多个视域时显存管理要按队列走,推理完一个crop就释放一个crop,不能让GPU内存增长。

偏振通道也可以扩展,从单偏光加正交偏光两通道,升级为四通道:单偏光、正交偏光、加石膏试板、锥光。每增加一个通道,模型输入层的卷积核数不变,但网络能提取的矿物光学证据更丰富。通道配对的版本管理比模型本身更容易出问题,我见过因为一张图配错偏振状态导致整个batch训练数据污染的情况,所以训练前脚本里一定要先做通道完整性校验,检查每个训练样本是否存在且文件名前缀一致,这个检查花10分钟,能省后面两天的排查时间。

6. 我在这类项目上最想验证的三个细节

6.1 训练集和验证集必须按薄片批次切,不能按patch切

薄片图像做训练时会切成大量patch,如果随机切分,同一个薄片的不同patch可能同时出现在训练集和验证集里,验证指标虚高到接近完美,一到新样品立刻崩盘。正确做法是按薄片样品编号分组切片,同一个样品所有patch只能出现在同一个集合中。我的习惯是样品级划分,而不是patch级划分。

6.2 不要只看mIOU,要看矿物含量误差

在岩石薄片场景里,mIOU提升0.05对定名结果可能毫无影响,而石英含量偏差5%就可能让岩石定名从“长石砂岩”变成“岩屑砂岩”。我每次模型迭代后都做一次含量误差评估,直接对比模型输出含量与地质师人工点计结果的绝对误差,这个指标才是业务真正关心的。

import numpy as np def content_mae(pred_contents, manual_contents, class_names): # pred_contents: 模型输出的含量字典列表 # manual_contents: 地质师人工统计的含量字典列表 errors = {name: [] for name in class_names} for pred, manual in zip(pred_contents, manual_contents): for name in class_names: pred_val = pred.get(name, 0.0) manual_val = manual.get(name, 0.0) errors[name].append(abs(pred_val - manual_val)) mae = {name: round(float(np.mean(vals)), 2) for name, vals in errors.items()} return mae

6.3 交付给协作方的不只是权重,而是可回放的完整链条

我第一次交付这类项目时只给了模型权重和推理脚本,结果对方复现不出当时的报告,后来排查发现是数据预处理和通道配对规则没写清楚。之后我改变了习惯,交付内容固定为:推理脚本、模型权重、类别映射表、数据增强参数、以及一份记录了所有超参数和已知局限的技术说明。这样拿到交付包的同事即使没参加训练过程,也能在半天内复现整条推理链路。对一个要持续使用的鉴定工具而言,权重本身是黑匣子,能让全流程回放才是后悔药。

这个方向值不值得投入,我的判断是值得,但前提是把应用边界说清楚:它能稳定解决批量、重复、标准化的矿物含量统计,它还不能解决所有疑难薄片的地质解释。我自己在每次项目里最先做的永远是验证集按批次切分和含量误差,这两个习惯帮我避掉了至少三次模型评估上的翻车,希望帮到你。

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

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

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

立即咨询