ARTICLE DETAIL

资讯详情

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

基于机器学习的岩石薄片自动鉴定:从数据标注到模型部署的完整链路

基于机器学习的岩石薄片自动鉴定:从数据标注到模型部署的完整链路 简介这份资源面向地质学、计算机相关专业的毕业设计、课程设计与期末大作业场景提供一套基于机器学习的岩石薄片自动鉴定完整项目。内容围绕卷积神经网络、随机森林、支持向量机等算法展开涵盖图像预处理、特征提取、模型训练与评估等环节帮助读者理解岩石纹理、结构与矿物组合的自动分类流程。压缩包共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, encodingutf-8) as f: data json.load(f) img_h data[imageHeight] img_w data[imageWidth] mask np.zeros((img_h, img_w), dtypenp.uint8) for shape in data[shapes]: label shape[label] if label not in CLASS_TO_ID: continue # 跳过未定义类别避免训练时索引越界 points np.array(shape[points], dtypenp.int32) # 填充多边形颜色值即类别 ID cv2.fillPoly(mask, [points], colorCLASS_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_okTrue) for jf in json_files: json_to_mask(jf, ./masks) print(fdone, {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 * FocalFocal 的 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, transformNone): 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(imageimg, maskmask) 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_nameefficientnet-b3, encoder_weightsimagenet, in_channels3, classeslen(CLASSES) 1 # 背景 矿物类别 ) dice_loss smp.losses.DiceLoss(modemulticlass) focal_loss smp.losses.FocalLoss(modemulticlass, gamma2.0) def criterion(pred, target): return 0.5 * dice_loss(pred, target) 0.5 * focal_loss(pred, target) optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_020) 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()这段代码里classeslen(CLASSES) 1是背景类如果忘了加 1训练时标签里的 0 会被当成无效值忽略模型永远学不会背景。encoder_weightsimagenet用了预训练权重虽然偏光图像和自然图像差异大但底层边缘和纹理特征仍然可迁移比从零训快很多。损失函数组合里 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_norm1.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.90.5 以下的基本是噪声。人工复核环节不能省。地质定名本身就有主观性不同的人对同一薄片可能给出不同名称模型的作用是把 90% 的常规样本自动处理掉把 10% 的疑难样本挑出来给人看。复核界面里要同时显示原图、模型掩码、置信度热力图让地质人员一眼看出模型哪里不确定。我习惯把置信度低于阈值的区域用红色边框标出复核人员只需要看这些区域效率比从头看一遍高得多。最后说一个我自己的教训早期做这个方案时我花了两周调模型结构准确率从 82% 提到 85%后来花了一天做颜色归一化和分组划分准确率直接跳到 91%。岩石薄片自动鉴定里数据质量比模型结构重要得多。如果你刚开始做先把采集规范、标注规范、划分策略定死再动模型。希望帮到你。本文还有配套的精品资源点击获取
返回列表