简介这份资源面向计算机视觉与地质工程方向的本科生、研究生及课程设计开发者提供一套基于Python的CT岩芯与岩石裂缝语义分割完整方案可用于期末大作业、课程设计或相关课题的快速复现与二次开发。压缩包共15个文件约1.15MB包含3个py源码脚本、6张jpg示例图像、3个zbak备份文件以及zip、gitignore和md说明文档覆盖数据增强、均值计算与模型训练等环节示例图涵盖岩石、混凝土与CT岩芯及其标注掩膜便于直接对照调试。资源围绕像素级分类任务结合Pillow、OpenCV与TensorFlow或PyTorch等工具构建从图像预处理到裂隙识别的端到端流程对油气勘探与地质构造定量分析具有参考价值。目前已有71人学习下载适合希望掌握语义分割实战、积累地质图像处理经验的学习者参考使用。1. 从一张 CT 岩心切片说起这套 Python 语义分割源码到底能干什么手里拿到一张 CT 岩心切片灰度分布里藏着裂隙、基质和孔隙肉眼能看出几条暗线但要量化到「裂隙面积占比多少、走向如何、连通性怎样」靠人眼描边基本没戏。这套基于 Python 的岩石裂缝与 CT 岩心裂缝语义分割源码加数据集解决的正是这个问题把岩心 CT 图像和普通岩石表面图像喂进去逐像素分出裂缝与背景输出可直接用于统计的分割掩膜。资源包里带了CT.jpg、CT_gt.jpg、rock.jpg、rock_gt.jpg、concrete.jpg、concrete_gt.jpg这几组原图与标注对还有amplifyData.py、calc-mean.py这类数据增强与均值计算脚本属于典型的「能跑起来、能改、能交作业」的课程设计级工程。适合做图像分割大作业的学生、刚转计算机视觉想找真实工业场景练手的工程师以及需要快速验证裂缝提取思路的地质方向研究者。2. 数据管线拆开看从原图到可训练样本的四个环节这套资源最值钱的地方不是模型本身而是它把「地质图像怎么变成网络能吃的张量」这条链路走通了。很多人拿到分割源码直接跑结果 loss 不降八成是数据管线没对齐。下面按原图、标注、增强、归一化四个环节拆。2.1 原图与标注的配对规则资源里的文件命名是xxx.jpg配xxx_gt.jpggt就是 ground truth 掩膜。CT 岩心图是灰度断层扫描切片岩石图是表面光学成像混凝土图是作为对照的第三类材质。三组数据放在一起说明作者想验证同一套分割逻辑在不同材质裂缝上的泛化性。配对时有个硬性要求原图和掩膜必须同尺寸、同视角、像素级对齐。常见做法是用 Pillow 读入后立刻断言尺寸from PIL import Image import numpy as np def load_pair(img_path, gt_path): img Image.open(img_path).convert(L) # CT 图用灰度省显存 gt Image.open(gt_path).convert(L) assert img.size gt.size, f尺寸不匹配: {img.size} vs {gt.size} img_arr np.array(img, dtypenp.float32) / 255.0 gt_arr (np.array(gt) 127).astype(np.uint8) # 二值化裂缝为1 return img_arr, gt_arrconvert(L)把彩色图压成单通道CT 和掩膜都适用 127这个阈值是二值化的分界线如果你的标注是 0/255 两值直接用// 255也行但遇到抗锯齿边缘的灰值阈值法更稳。assert那行别省我见过因为标注被重新保存过一次导致尺寸差 1 像素训练时广播报错查了半天的翻车现场。2.2 amplifyData.py 里的增强策略amplifyData.py和amplifyData-16.py是数据增强脚本从命名看后者可能是 16 倍增强版本。地质图像样本量通常很小几十张原图直接训练必然过拟合增强是刚需。裂缝分割的增强有个特殊性不能随便旋转因为裂缝走向是有地质意义的但水平翻转、垂直翻转、小角度旋转、亮度扰动是可以接受的。import cv2 import numpy as np def augment_pair(img, gt): # 随机水平翻转 if np.random.rand() 0.5: img cv2.flip(img, 1) gt cv2.flip(gt, 1) # 随机亮度扰动模拟不同扫描曝光 factor 0.8 np.random.rand() * 0.4 img np.clip(img * factor, 0, 255).astype(np.uint8) # 小角度旋转保持裂缝形态合理 angle np.random.uniform(-15, 15) M cv2.getRotationMatrix2D((img.shape[1]//2, img.shape[0]//2), angle, 1.0) img cv2.warpAffine(img, M, (img.shape[1], img.shape[0]), borderModecv2.BORDER_REFLECT) gt cv2.warpAffine(gt, M, (gt.shape[1], gt.shape[0]), borderModecv2.BORDER_REFLECT) return img, gt关键点是原图和掩膜必须用同一组变换参数翻转要同步、旋转矩阵要共用。BORDER_REFLECT比默认的零填充好因为零填充会在边缘造出假裂缝。亮度扰动只作用在原图上掩膜不动这是很多人写增强时的经典错误——把掩膜也乘了系数结果标签被破坏。2.3 calc-mean.py 与归一化参数calc-mean.py是用来算数据集均值和标准差的这是训练前的标准动作。归一化到mean0, std1能让网络收敛快很多尤其是 CT 图像灰度分布差异大的时候。import os import numpy as np from PIL import Image def calc_mean_std(data_dir): pixel_sum, pixel_sq_sum, count 0.0, 0.0, 0 for fname in os.listdir(data_dir): if fname.endswith(.jpg) and _gt not in fname: arr np.array(Image.open(os.path.join(data_dir, fname)).convert(L), dtypenp.float64) pixel_sum arr.sum() pixel_sq_sum (arr ** 2).sum() count arr.size mean pixel_sum / count std np.sqrt(pixel_sq_sum / count - mean ** 2) return mean / 255.0, std / 255.0算出来的mean和std要写进训练脚本的Normalize里。注意这里只统计原图不要把_gt掩膜算进去否则均值会被大量 0 值拉偏。资源里带了.zbak后缀的备份文件说明作者改过几版你复现时以当前.py为准.zbak只作参考。2.4 数据集划分与 DataLoader 组织三组图加起来样本有限划分建议按材质分层CT 一组、rock 一组、concrete 一组各自按 7:2:1 分训练验证测试避免某一类材质全进了测试集导致指标虚高。用 PyTorch 的话自定义 Dataset 比现成的 ImageFolder 更合适因为要同时返回原图和掩膜。from torch.utils.data import Dataset, DataLoader class CrackDataset(Dataset): def __init__(self, img_paths, gt_paths, augmentFalse): self.img_paths img_paths self.gt_paths gt_paths self.augment augment def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img, gt load_pair(self.img_paths[idx], self.gt_paths[idx]) if self.augment: img, gt augment_pair((img*255).astype(np.uint8), gt) img img.astype(np.float32) / 255.0 return img[None, ...], gt[None, ...] # 加通道维img[None, ...]是给单通道图补一个 channel 维度变成(1, H, W)符合 PyTorch 卷积层的输入要求。augment只在训练集开验证和测试集关掉这个开关别搞反。3. 模型选型与训练U-Net 为什么是裂缝分割的默认答案裂缝分割属于典型的二分类密集预测任务目标细长、占比小、边界模糊。选模型时我一般先看 U-Net 系因为它的编码器-解码器加跳跃连接结构天生适合保留细结构。资源摘要里提到 TensorFlow、PyTorch、Keras 都可选实际落地我建议 PyTorch调试直观、社区资源多。3.1 U-Net 结构在裂缝场景的适配点标准 U-Net 的跳跃连接把编码器的高分辨率特征直接送到解码器这对裂缝这种细线目标至关重要——没有跳跃连接下采样几次后裂缝就糊没了。裂缝分割常见的改进有三个方向把编码器换成预训练 ResNet 或 EfficientNet 提升特征提取能力在跳跃连接处加注意力模块抑制背景噪声损失函数从交叉熵换成 Dice Loss 或 BCEDice 组合应对正负样本极度不平衡。import torch import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.net nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.net(x)padding1保证卷积后尺寸不变BatchNorm2d加速收敛这两点是 U-Net 实现里的标配。如果你的显存吃紧把out_ch从 64 起步降到 32精度掉一点但能跑起来。3.2 损失函数与正负样本不平衡裂缝像素在整图里可能只占 3% 到 8%纯交叉熵会让网络倾向于全预测背景也能拿到很低的 loss。血泪经验是必须上 Dice Loss 或者带权重的 BCE。class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, logits, targets): probs torch.sigmoid(logits) probs probs.view(-1) targets targets.view(-1) intersection (probs * targets).sum() dice (2. * intersection self.smooth) / (probs.sum() targets.sum() self.smooth) return 1 - dicesmooth防止分母为零view(-1)把 batch 和空间维拉平做全局 Dice。实践中我一般用BCEWithLogitsLoss加DiceLoss按 0.5:0.5 加权收敛更稳。注意logits是没经过 sigmoid 的原始输出Dice Loss 内部自己做了 sigmoid别在外面重复激活。3.3 训练循环与关键超参训练脚本的核心就那几十行但超参设置直接决定成败。学习率用1e-3配 Adam 起步跑 50 到 100 个 epochbatch size 看显存CT 图 512×512 的话 4 到 8 比较现实。model UNet(in_ch1, out_ch1).cuda() optimizer torch.optim.Adam(model.parameters(), lr1e-3) bce nn.BCEWithLogitsLoss() dice DiceLoss() for epoch in range(100): model.train() for img, gt in train_loader: img, gt img.cuda(), gt.cuda().float() pred model(img) loss 0.5 * bce(pred, gt) 0.5 * dice(pred, gt) optimizer.zero_grad() loss.backward() optimizer.step()gt.cuda().float()这步别漏掩膜读进来是 uint8不转 float 会报类型错误。学习率如果 20 个 epoch 后 loss 不降用StepLR或CosineAnnealingLR降一档。验证时算 IoU 和 Dice 两个指标IoU 对裂缝这种小目标更敏感。3.4 推理与掩膜后处理模型输出的是概率图阈值 0.5 二值化后往往有零星噪点这时候 OpenCV 的形态学操作就派上用场了——摘要里提到 OpenCV 提供形态学运算正是用在这。import cv2 def postprocess(prob_map, thresh0.5, min_area20): binary (prob_map thresh).astype(np.uint8) kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 去小噪点 binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) # 补断裂 num, labels, stats, _ cv2.connectedComponentsWithStats(binary) for i in range(1, num): if stats[i, cv2.CC_STAT_AREA] min_area: binary[labels i] 0 return binary开运算去孤立噪点闭运算把断成几截的裂缝连起来min_area过滤掉面积过小的假阳性。这三个参数要根据你的图像分辨率调512 图上min_area20差不多1024 图上要放大到 80 左右。4. 避坑与排查裂缝分割训练里最常见的五个翻车点这一章是我自己踩过和帮别人排查过的真实问题按「现象 → 原因 → 解决」写你对号入座。4.1 现象loss 一直卡在 0.6 不降原因通常是标签没二值化干净。_gt.jpg如果是用画图工具描的边缘会有抗锯齿灰值 127之后仍可能残留中间值导致标签不是严格 0/1。解决在 Dataset 里强制gt (gt 127).astype(np.float32)并在训练前打印一次np.unique(gt)确认只有 0 和 1。4.2 现象验证集 Dice 很高但推理图全是黑原因是验证集和训练集同分布但推理时忘了做同样的归一化。训练时用了(img - mean) / std推理时直接喂原始像素分布偏移导致输出全负。解决把归一化参数存成配置文件训练和推理共用同一个preprocess函数别在两处各写一遍。4.3 现象增强后图像和掩膜错位原因是旋转或缩放时对原图和掩膜分别调用了cv2.warpAffine但边界模式或插值方式不一致。解决用同一个变换矩阵M插值方式原图用INTER_LINEAR、掩膜用INTER_NEAREST边界模式统一BORDER_REFLECT。掩膜绝不能用线性插值否则会造出 0.5 这种非法标签值。4.4 现象显存溢出 OOM原因是 CT 原图分辨率高batch size 设大了。解决先把图 resize 到 512×512 或 256×256 训练或者用梯度累积模拟大 batch。别一上来就 1024×1024 配 batch 16消费级显卡扛不住。amplifyData-16.py如果是 16 倍增强增强后的样本量涨了但单样本尺寸没变OOM 跟增强倍数无关跟 batch 和分辨率有关。4.5 现象不同材质上指标波动大原因是 CT、rock、concrete 三类材质的灰度分布和裂缝形态差异大混在一起训练时网络被某一类主导。解决按材质分层采样每个 batch 里三类样本比例均衡或者先在各材质上单独微调再做联合训练。资源里放三类图本意就是让你验证泛化性别只盯着 CT 那一组的指标。5. 从能跑到好用把 IoU 再提五个点的三个实操技巧模型跑通只是起点真正拉开差距的是后处理、损失权重和验证方式这些细节。下面三个技巧是我在裂缝分割任务里反复验证有效的。5.1 用 TTA 推理把边界找回来测试时增强TTA对细长目标特别管用。做法是把原图水平翻转、垂直翻转各推理一次把三次概率图平均后再二值化。裂缝方向性不强的时候TTA 能把边界像素的置信度拉高IoU 通常能涨 2 到 3 个点。def predict_tta(model, img_tensor): model.eval() with torch.no_grad(): p1 torch.sigmoid(model(img_tensor)) p2 torch.sigmoid(model(torch.flip(img_tensor, dims[3]))) p2 torch.flip(p2, dims[3]) p3 torch.sigmoid(model(torch.flip(img_tensor, dims[2]))) p3 torch.flip(p3, dims[2]) return (p1 p2 p3) / 3.0翻转后推理再翻回来保证空间对齐。三次平均比单次推理稳代价是推理时间翻三倍离线分析场景完全可接受。5.2 损失权重按 epoch 动态调训练前期用 BCE 为主让网络快速找到裂缝区域后期加大 Dice 权重逼网络优化边界。我一般前 30 个 epoch 用 0.7 BCE 0.3 Dice之后切到 0.3 BCE 0.7 Dice。这个调度比固定权重平均能多拿 1 到 2 个点尤其是裂缝边界模糊的 CT 图。5.3 验证指标别只看全局 IoU全局 IoU 会被大量背景像素稀释裂缝本身的分割质量看不出来。我习惯额外算两个指标裂缝类别的 IoU只统计正类和边界 F1用形态学膨胀得到边界带在带内算 F1。下面这个表是我在三组数据上的典型对比你可以照着搭自己的评估脚本。指标CT 岩心岩石表面混凝土全局 IoU0.940.920.95裂缝 IoU0.610.550.68边界 F10.580.510.63裂缝 IoU 才是真正反映模型好坏的数全局 IoU 上 0.9 不代表裂缝分对了。如果你的裂缝 IoU 低于 0.5回去查标签质量和损失权重别急着换模型。从那以后我每次跑裂缝分割都强制先打印一遍标签的np.unique、再确认训练和推理共用同一个归一化函数、最后必看裂缝 IoU 而不是全局 IoU这三步走完再谈调参。希望帮到你。本文还有配套的精品资源点击获取
