ARTICLE DETAIL

资讯详情

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

Python实现CT岩心裂缝语义分割:从数据准备到定量分析全流程

Python实现CT岩心裂缝语义分割:从数据准备到定量分析全流程 简介这份资源面向计算机视觉与地质工程方向的本科生、研究生及课程设计开发者提供一套基于Python的CT岩芯与岩石裂缝语义分割完整方案可用于期末大作业、课程设计或相关课题的快速复现与二次开发。压缩包共15个文件约1.15MB包含3个py源码脚本、6张jpg示例图像与标注图、若干zbak备份文件及README说明文档覆盖数据增强、均值计算与训练推理等环节并附带岩石、混凝土、CT岩芯等多组样本便于直接跑通流程。目前已有73人学习下载。读者可从中获得端到端的像素级分割实现思路理解Pillow、OpenCV与TensorFlow/PyTorch在图像预处理和网络构建中的分工掌握CT断层扫描图像的裂隙识别与量化分析方法为油气勘探、地质构造研究等场景的定量分析提供可借鉴的技术支撑。1. CT岩心裂缝语义分割从灰度切片到可训练掩码的完整链路拿到一批工业 CT 扫描的岩心切片想自动把裂缝、孔隙、基质分开这件事在石油地质和岩石力学圈子里需求很实在。CT 岩心图像的特点是灰度对比弱、裂缝细且走向随机、还常伴随环状伪影和束硬化人眼标注一张 1024×1024 的切片动辄十几分钟几百张就是灾难。语义分割模型能把这件事从逐像素手描变成训练一次、批量推理而 Python 生态里的 PyTorch 分割网络正好是当前最成熟的落地路径。这个标题讲的就是用 Python 搭一套从数据准备、模型训练到裂缝掩码输出的语义分割系统配套岩心 CT 数据集。适合做岩石物理实验、数字岩心分析、地质图像处理的工程师也适合刚学完 Python 想找一个真实分割项目练手的人。下面按数据、模型、训练、避坑、进阶的顺序把整条链路拆开。2. 岩心 CT 数据集怎么准备从原始切片到可训练掩码2.1 先搞清楚你的 CT 数据是什么形态工业 CT 输出的常见格式有 DICOM、TIFF 序列、RAW 体数据还有部分设备直接给 PNG 切片。做语义分割前必须确认三件事位深、灰度范围、切片间距。位深决定你要不要做归一化8 位图直接除以 25516 位图得先看直方图再决定窗宽窗位。灰度范围影响裂缝和基质的可分性很多岩心 CT 里裂缝灰度只比基质低 10 到 30 个灰度级直接送进网络会被当成噪声。切片间距决定你能不能做三维上下文间距大于 1mm 时层间相关性弱强行上 3D 网络收益不大不如老老实实做 2D 分割再堆叠。我一般会先写个小脚本把一批切片的统计量打出来确认没有异常切片混进来。这一步不做后面训练 loss 不降你会怀疑人生。import numpy as np from pathlib import Path from PIL import Image def scan_slices(folder, sample_n20): files sorted(Path(folder).glob(*.png))[:sample_n] for f in files: img np.array(Image.open(f)) print(f{f.name} | shape{img.shape} | dtype{img.dtype} f| min{img.min()} max{img.max()} mean{img.mean():.1f} f| p1{np.percentile(img,1):.0f} p99{np.percentile(img,99):.0f}) scan_slices(./ct_slices)这段代码做的是抽样统计sample_n控制抽样数量先看 20 张足够判断整体分布。重点看p1和p99如果两者差距很小说明对比度极低需要做 CLAHE 或者直方图拉伸。dtype是 uint16 的话后面归一化不能直接除 255要按实际位深处理。2.2 标注格式选型PNG 掩码还是 COCO JSON语义分割的标注格式主流有两种单通道 PNG 掩码像素值即类别 id和 COCO JSON多边形坐标。岩心裂缝这种细长目标我强烈建议用 PNG 掩码。原因是裂缝宽度经常只有 1 到 3 个像素多边形标注在细长区域容易产生自交和漏点转成掩码后边缘锯齿严重。PNG 掩码用 LabelMe 或者 CVAT 画完多边形导出即可也可以直接用 ITK-SNAP 在体数据上刷。类别定义建议从简0 背景基质、1 裂缝、2 孔隙。类别超过 4 个时岩心 CT 的标注一致性会急剧下降不同标注员对微裂隙和孔隙的边界理解不一致训练出来的模型在验证集上看着好换一批岩心就崩。常见做法是先做二分类裂缝 vs 非裂缝跑通后再考虑细分。格式适用场景优点缺点PNG 掩码细长目标、像素级精度无损、直接对应类别 id需要统一尺寸COCO JSON目标检测分割联合生态工具多细长目标多边形易自交NPY 数组科研快速迭代加载快、支持 float不通用、难可视化2.3 数据增强岩心 CT 不能随便翻转通用分割增强里的随机旋转、翻转在岩心 CT 上要谨慎。岩心是有层理方向的水平翻转会破坏沉积构造的物理意义垂直翻转更离谱。我一般只开三类增强小角度旋转±10 度以内、亮度对比度扰动、弹性形变幅度调小。弹性形变对裂缝这种细结构有帮助但alpha和sigma设大了会把裂缝拉断反而制造错误标签。import albumentations as A train_tf A.Compose([ A.RandomRotate90(p0.0), # 岩心禁用90度旋转 A.Rotate(limit10, border_mode0, p0.5), # 小角度旋转 A.RandomBrightnessContrast(0.2, 0.2, p0.5), # 灰度扰动 A.ElasticTransform(alpha30, sigma5, p0.3), # 小幅弹性形变 A.Resize(512, 512), ]) val_tf A.Compose([A.Resize(512, 512)])limit10是旋转角度上限超过 15 度岩心纹理方向就失真了。alpha30, sigma5是弹性形变的位移幅度和光滑度这两个值是我在几批砂岩 CT 上试出来的再大裂缝会被扯断。border_mode0表示旋转后边界填 0和背景类别一致避免引入假边缘。3. 分割模型选型与训练U-Net、DeepLabV3 还是 SegFormer3.1 岩心裂缝分割为什么 U-Net 仍然是首选裂缝分割的核心难点是细结构保持。Transformer 类模型SegFormer、Swin-UNet在全局上下文上强但下采样倍率高1 到 2 像素宽的裂缝在 1/16 特征图上直接消失。U-Net 的跳跃连接把编码器的高分辨率特征直接送到解码器对细结构友好这是它在医学和地质分割里长期占位的根本原因。DeepLabV3 的空洞卷积能扩大感受野又不丢分辨率理论上也适合但 ASPP 模块参数量大小数据集上容易过拟合。我的选型逻辑是数据量小于 500 张用 U-Net ResNet34 编码器500 到 2000 张可以上 U-Net 或者 DeepLabV3超过 2000 张且算力充足再考虑 SegFormer。岩心 CT 公开数据集普遍不大所以 U-Net 系是稳妥起点。3.2 用 segmentation_models_pytorch 搭一个最小可训练版本不重复造轮子segmentation_models_pytorchsmp把主流编码器和解码头都封装好了改几行就能换骨干。下面是一个完整的最小训练脚本骨架。import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import segmentation_models_pytorch as smp import albumentations as A from PIL import Image import numpy as np class CoreDataset(Dataset): def __init__(self, img_dir, mask_dir, tf): self.imgs sorted(Path(img_dir).glob(*.png)) self.mask_dir Path(mask_dir) self.tf tf def __len__(self): return len(self.imgs) def __getitem__(self, idx): img np.array(Image.open(self.imgs[idx]).convert(L)) img np.stack([img]*3, axis-1) # 单通道转3通道喂给预训练编码器 mask np.array(Image.open(self.mask_dir / self.imgs[idx].name)) aug self.tf(imageimg, maskmask) img aug[image].transpose(2, 0, 1).astype(np.float32) / 255.0 mask aug[mask].astype(np.int64) return torch.from_numpy(img), torch.from_numpy(mask) model smp.Unet( encoder_nameresnet34, encoder_weightsimagenet, in_channels3, classes2, # 背景 裂缝 )encoder_weightsimagenet用预训练权重小数据集上比从头训收敛快得多。classes2对应二分类如果做背景/裂缝/孔隙三分类就改成 3。单通道 CT 转 3 通道是为了适配 ImageNet 预训练编码器的输入要求这是常见做法不是必须但省事。3.3 损失函数与评估指标裂缝分割别只看 IoU裂缝像素占比通常不到 5%交叉熵会被背景主导模型学会全预测背景就能拿到 95% 准确率。必须用组合损失Dice BCE或者 Focal Dice。Dice 直接优化重叠度对类别不平衡鲁棒。dice_loss smp.losses.DiceLoss(modemulticlass) ce_loss nn.CrossEntropyLoss() def criterion(pred, target): return 0.5 * dice_loss(pred, target) 0.5 * ce_loss(pred, target) # 评估用 IoU 和 F1按类别算 iou_metric smp.metrics.get_stats评估时一定要分类别看 IoU。裂缝类的 IoU 才是真正反映模型能不能用的指标背景 IoU 再高也没意义。我见过背景 IoU 0.98、裂缝 IoU 0.15 的模型报告里只写平均 IoU 0.56实际完全不能用。3.4 训练参数怎么设学习率、batch size、迭代轮数学习率用 1e-4 起步配合 CosineAnnealing 或者 ReduceLROnPlateau。batch size 受显存限制512×512 输入下 8GB 显存大概能跑 batch 4 到 8。迭代轮数看数据量500 张图训 100 到 150 epoch 通常够早停 patience 设 20。优化器 AdamW 比 SGD 在小数据集上更稳weight decay 设 1e-4。optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max100)T_max100是余弦退火的周期和总 epoch 对齐。如果中途发现验证集裂缝 IoU 连续 20 轮不涨直接停继续训只会过拟合。4. 裂缝分割避坑那些让模型看着能跑其实没用的坑4.1 现象训练 loss 正常下降裂缝 IoU 始终低于 0.2原因通常是类别极度不平衡加上损失函数选错。纯交叉熵下模型倾向于预测背景loss 看着降其实是背景拟合好了。解决方法是换 Dice CE 组合损失同时在数据采样时对含裂缝的切片做加权让每个 batch 里裂缝样本占比不低于 30%。4.2 现象验证集效果好换一批岩心就崩这是域偏移问题。不同设备、不同扫描参数、不同岩性砂岩 vs 碳酸盐岩的 CT 灰度分布差异很大。解决方法是训练时加入强灰度增强Gamma 变换、CLAHE推理前对输入做直方图匹配把目标域图像的灰度分布对齐到训练域。如果条件允许在新岩心上标注 20 到 30 张做微调效果立竿见影。4.3 现象裂缝断断续续细的地方直接消失原因是下采样丢信息。U-Net 编码器一般下采样 5 次1 像素宽的裂缝在 1/32 特征图上就没了。解决办法有三个把输入分辨率从 256 提到 512 或 1024减少下采样次数用 ResNet18 代替 ResNet34在损失里加边界加权让模型更关注裂缝边缘像素。我一般先提分辨率成本最低。4.4 现象推理结果有大量孤立小斑点这是后处理没做。模型输出的概率图直接取 argmax 会产生很多假阳性小区域。用连通域分析过滤掉面积小于阈值的区域阈值按实际裂缝最小面积设一般 20 到 50 像素。import cv2 def remove_small(mask, min_area30): n, labels, stats, _ cv2.connectedComponentsWithStats(mask, connectivity8) out np.zeros_like(mask) for i in range(1, n): if stats[i, cv2.CC_STAT_AREA] min_area: out[labels i] 1 return outmin_area30是经验值岩心 CT 里真实裂缝连通域通常大于这个数小于的基本是噪声。这个值要根据你的像素分辨率调分辨率高就调大。4.5 现象显存够但训练速度极慢常见原因是数据加载成了瓶颈。num_workers设 0 时数据在主进程加载GPU 大量时间在等数据。设成 4 到 8配合pin_memoryTrue。另外检查增强里有没有特别慢的操作弹性形变在大图上很吃 CPU可以降低触发概率。5. 从掩码到定量分析裂缝参数提取与模型验证5.1 用骨架化提取裂缝长度和走向分割出掩码只是第一步地质分析要的是裂缝长度、开度、走向玫瑰图。用skimage.morphology.skeletonize把裂缝掩码细化成单像素骨架再统计骨架像素数得到总长度用连通域的主成分方向得到走向。from skimage.morphology import skeletonize from skimage.measure import label, regionprops import numpy as np def crack_stats(mask, pixel_size_mm0.05): skel skeletonize(mask 0) length_px skel.sum() length_mm length_px * pixel_size_mm lbl label(mask 0) props regionprops(lbl) angles [] for p in props: if p.area 30: continue # 用区域协方差矩阵的主方向近似走向 y, x np.where(lbl p.label) coords np.stack([x - x.mean(), y - y.mean()]) cov np.cov(coords) eigvals, eigvecs np.linalg.eigh(cov) v eigvecs[:, -1] angles.append(np.degrees(np.arctan2(v[1], v[0])) % 180) return length_mm, anglespixel_size_mm是 CT 体素的实际物理尺寸必须从扫描参数里拿到否则长度没有物理意义。angles是每个裂缝连通域的走向角画成玫瑰图就是地质上要的裂缝方位分布。area 30过滤掉噪声连通域和前面后处理的阈值保持一致。5.2 验证模型是否可信三个必做检查第一拿几张训练集里没见过的切片人工标注后和模型输出做像素级对比算裂缝类 IoU低于 0.5 就别急着上生产。第二检查模型在裂缝交叉点和端点处的表现这些位置最容易断。第三把模型输出和原始 CT 叠加显示看有没有系统性偏移比如整体往一个方向偏一两个像素那可能是数据增强里旋转引入的插值误差。我自己的习惯是每训完一个模型先挑 5 张最难的切片裂缝最细、对比度最低的单独跑一遍肉眼过一遍再决定要不要继续调。这个习惯帮我省了很多次指标好看但实际不能用的返工。裂缝分割这件事指标是参考肉眼验证才是后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表