CT岩心裂缝语义分割实战:从数据预处理到U-Net训练避坑指南
简介面向Python计算机视觉与地质图像分析领域的课程设计、期末大作业场景这份资源围绕岩石裂缝与CT岩心裂缝的语义分割问题提供了一套可运行的Python源码及配套数据集。语义分割需对图像逐像素分类项目涉及PIL、OpenCV、TensorFlow/PyTorch等常用工具并包含CT岩心无损检测图像的裂缝识别与量化思路便于学习者将计算机视觉方法落地到实际岩土工程场景。压缩包共14个文件以JPG图像样本、Python脚本、备份文件及Markdown说明文档为主整体大小约1.13MB其中图像样本涵盖岩石、混凝土、CT扫描原始图及对应的标注图脚本则覆盖数据增强、均值计算等预处理环节辅助完成从数据准备到模型训练的分割流程。目前已有171人学习下载适合需要快速上手语义分割项目、完成课程设计或期末作业的Python及深度学习初学者参考复用。1. 岩心CT裂缝分割为什么通用分割模型直接跑会翻车拿到一份“Python岩石裂缝与CT岩心裂缝语义分割源码及数据集”本质上就是给你一套能跑的Python分割工程输入是工业CT扫描得到的岩心切片输出是逐像素的裂缝掩膜。真正动手后你会发现这和你平时在自然照片上做的语义分割完全不是一个难度量级。CT切片是16位灰度图裂缝往往只有几个像素宽裂缝面积可能占不到整张图的1%直接用现成的DeepLabV3权重去推理常常整张图预测成背景裂缝全部漏掉。这套资源适合两类人一类是拿它做课程设计或期末大作业的学生另一类是做岩石物理、地质分析但刚接触分割模型的从业者。想复现出能用的效果需要按顺序过数据关、训练关和推理关。2. 数据集整理与标注转换从原始CT切片到可训练样本2.1 数据集目录结构与命名约定拿到源码包之后第一件事不是急着读模型代码而是先清点数据集目录。CT岩心裂缝数据集的常见组织方式是图像和掩膜分开放命名一一对应。比如data/ images/ sample_001.tif sample_002.tif masks/ sample_001.png sample_002.png这里最容易踩的第一个坑是图像格式。images目录里的CT切片经常是16位TIFF甚至有些原始数据是DICOM格式而masks目录里的标签是8位PNG。如果直接用cv2.imread默认参数读图16位灰度会被直接截断成8位裂缝的灰度细节丢失后面训练效果会打折扣。我习惯先用一段脚本把数据集的真实情况摸清楚import cv2 import glob image_paths sorted(glob.glob(data/images/*.tif)) mask_paths sorted(glob.glob(data/masks/*.png)) for img_p, mask_p in zip(image_paths[:5], mask_paths[:5]): img cv2.imread(img_p, cv2.IMREAD_UNCHANGED) mask cv2.imread(mask_p, cv2.IMREAD_GRAYSCALE) print(img, img_p, img.shape, img.dtype, img.min(), img.max()) print(mask, mask_p, mask.shape, mask.dtype, mask.min(), mask.max())这段代码有两个关键点读图像时用cv2.IMREAD_UNCHANGED这样才能把16位TIFF的原始灰度范围保留下来读掩膜时用cv2.IMREAD_GRAYSCALE确保标签是单通道。输出里如果mask的最大值是255而不是1说明标注脚本保存时用了255代表前景后面在计算损失函数时需要统一除以255否则二分类标签不是[0,1]Dice Loss计算会出错。命名对应关系同样需要确认。sorted只能保证文件名按字符串排序但如果有某个文件缺失或者命名后缀不一致用zip对齐时会悄悄错位。建议在正式训练前随机取5组数据用下面这段代码把图像和掩膜叠加起来保存成预览图肉眼确认裂缝位置是否对齐import cv2 import numpy as np def check_alignment(img_path, mask_path, out_path): img cv2.imread(img_path, cv2.IMREAD_UNCHANGED) mask cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) # 16位转8位预览 img_8u ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8) img_bgr cv2.cvtColor(img_8u, cv2.COLOR_GRAY2BGR) img_bgr[mask 0] (0, 0, 255) cv2.imwrite(out_path, img_bgr) check_alignment(data/images/sample_001.tif, data/masks/sample_001.png, check_001.png)这一步虽然简单但能省掉后面排查标签错位的几个小时。CT岩心切片有个特殊性相邻切片的空间相关性很强同一块岩心连续切出来的几十张图内容非常相似。如果按图像文件随机划分训练集和验证集验证集会间接混入训练集的信息导致验证Dice虚高。正确做法是按岩心块ID划分数据后面避坑章节会详细展开。2.2 标注格式确认与转换JSON Polygon 转 PNG Masks如果源码包里的标签已经是PNG掩膜这步可以直接跳过。但很多课程设计和论文复现场景标注是用LabelMe这类工具画的导出的是JSON文件。JSON里存的是每个裂缝的多边形坐标训练前必须转成稠密的像素级掩膜。下面这段是我常用的转换脚本import json import cv2 import numpy as np def labelme_json_to_mask(json_path, img_shape, out_mask_path): with open(json_path, r, encodingutf-8) as f: data json.load(f) # img_shape 取原图的高和宽 mask np.zeros((img_shape[0], img_shape[1]), dtypenp.uint8) for shape in data[shapes]: # 有的标注会把裂缝标成 polygon有的标成 line if shape[label] in (fracture, crack, 裂缝): points np.array(shape[points], dtypenp.int32) # 用多边形填充保证裂缝内部全部是前景 cv2.fillPoly(mask, [points], 1) cv2.imwrite(out_mask_path, mask) with open(label.json, r, encodingutf-8) as f: data json.load(f) img_shape (data[imageHeight], data[imageWidth]) labelme_json_to_mask(label.json, img_shape, mask.png)这里有两个容易翻车的细节。第一个是points的坐标类型必须是np.int32fillPoly不接受浮点数很多人直接传原始坐标导致报错。第二个是标签值统一填充为1这样后面训练时mask就是[0,1]二值图不用再在损失函数里做一次除法。如果你拿到的JSON是COCO格式而不是LabelMe格式思路一样先从annotations里取出多边形坐标再fillPoly只是字段名不同。转换完成后还要检查一次mask的完整性。裂缝是细长条状偶尔标注时一个裂缝被分成了多个多边形中间留了缝隙fillPoly之后会出现断裂。这种断裂会在训练时给模型输出不一致的监督信号建议转换后用形态学闭运算把距离很近的裂缝段连起来但这一步要克制kernel用3x3就够了。2.3 灰度统计与预处理归一化、CLAHE 与窗宽窗位工业CT岩心切片和自然图像最大的区别是动态范围。CT数据通常以16位存储灰度值范围可能是0到几万但有效信息集中在很窄的一段。比如裂缝和孔隙表现为低密度区域灰度值偏低而岩石基质和矿物晶体灰度值很高。如果直接把16位数据除以65535归一化到[0,1]裂缝区域会被压缩到几个灰度级模型根本学不到裂缝和背景的差异。常见做法是先做百分位裁剪模仿CT诊断里的窗宽窗位操作import numpy as np def normalize_ct_window(img, low_percent2, high_percent98): lo np.percentile(img, low_percent) hi np.percentile(img, high_percent) img np.clip(img, lo, hi) img (img - lo) / (hi - lo) return (img * 255).astype(np.uint8)low_percent2和high_percent98的意思是去掉灰度值最低和最高的2%像素这些通常是CT扫描引入的极值噪声。裁剪后再线性映射到0-255裂缝的对比度才能被拉开。如果你做的是低剂量CT或者工业CT噪声更大百分位可以再收紧到1和99具体要根据直方图观察。裁剪后的图片虽然对比度正常了但裂缝边缘和岩石基质的灰度差异仍然可能很弱。下一步我一般会叠加CLAHE局部对比度增强import cv2 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) img clahe.apply(img)CLAHE的原理是在tileGridSize划分的局部区块内做直方图均衡化clipLimit2.0控制对比度放大上限。裂缝在CT切片里往往是低密度黑线周边是灰度较高的基质局部增强后这条黑线会更明显。这个参数不要调得过大clipLimit超过4.0会把噪点也放大成伪裂缝。预处理函数要作为训练流程的一部分而不是保存成图片文件。原因很简单如果用同样的CLAHE参数在训练前把整批数据全部增强并保存数据增强的随机性就丢了而且不同实验之间想对比不同的预处理参数还得重新生成一份数据。2.4 数据增强针对低对比度裂缝的增强组合语义分割的数据增强有个硬性要求图像和掩膜必须做完全相同的空间变换。手写实现很麻烦我一般直接用albumentations库。裂缝分割场景随机旋转、翻转、弹性变形都有必要因为岩心切片的裂缝方向不一定和图像坐标系对齐。import albumentations as A train_transform A.Compose([ A.RandomRotate90(p0.5), A.HorizontalFlip(p0.5), A.VerticalFlip(p0.5), A.CLAHE(clip_limit2.0, tile_grid_size(8, 8), p0.3), A.ElasticTransform(alpha1.0, sigma50.0, p0.2), A.RandomCrop(height256, width256, p1.0), ]) valid_transform A.Compose([ A.CenterCrop(height256, width256, p1.0), ])注意RandomCrop我放在变换列表的最后因为前面的旋转、翻转、弹性变换都需要在全尺寸图像上进行先裁剪再做这些变换会损失有效信息。ElasticTransform的alpha1.0是变形强度上限sigma50.0是平滑程度。岩心内部如果有细微的层理弯曲这个变换能模拟出来但经验上alpha不要超过2否则细裂缝会被拉伸成断断续续的虚线相当于给训练数据增加错误标注。验证集只用CenterCrop不要混入随机增强。一个容易被忽略的细节是albumentations的随机增强会影响标签值如果用双线性插值的方式旋转图像mask也会被线性插值产生位于0和1之间的中间值。上面这套变换里RandomRotate90和翻转不会引起插值但ElasticTransform底层用了插值如果发现训练时mask出现了非二值像素需要给变换额外传mask参数时保持interpolationcv2.INTER_NEAREST或者在损失函数里加一个mask.round()操作。比较省事的做法是在A.Compose的构建参数里加additional_targets并显式指定mask的插值方式。做完数据增强数据关基本就打通了。整个阶段的目标是确认图像和mask一一对应、灰度范围没有截断、裂缝信息没有被预处理抹掉。这三件事只要有一件出问题后面模型训练再怎么调参都是白费。3. 分割模型选型与训练配置U-Net 小样本与 DeepLabV3 的取舍3.1 模型选型为什么先试 U-Net 而不是 TransformerCT岩心裂缝数据集通常不会太大几十张到几百张之间而裂缝本身的像素占比又非常低。在这种数据规模下直接上Vision Transformer或者Swin Transformer大概率会过拟合因为它们需要大量数据来学习全局注意力模式。最稳妥的起点是U-Net或者它的变体。U-Net的编码器逐层下采样提取多尺度特征解码器通过跳跃连接把编码器每一层的空间细节融合回来这种设计特别适合裂缝这种“边界细节比语义更重要”的任务。搭建模型我一般用segmentation_models_pytorch这个库。它封装了U-Net、DeepLabV3、FPN等主流结构而且支持不同的编码器backboneimport segmentation_models_pytorch as smp model smp.Unet( encoder_nameresnet34, encoder_weightsimagenet, in_channels3, classes1, activationNone, )关键参数是in_channels3。你的原始输入是单通道灰度图但resnet34的ImageNet预训练权重是在三通道RGB图上训出来的直接改成in_channels1就加载不了预训练权重。常见做法是在预处理阶段把灰度图原地复制三次变成三通道。训练和推理都要走同一个转换逻辑这一点必须统一否则到推理阶段会出现通道数不匹配的诡异错误。classes1表示只分割裂缝一个类别输出是单通道的logits图。activationNone是因为后面损失函数用的是BCEWithLogitsLoss它在内部做了sigmoid不需要在模型末尾手动加激活层。对比一下U-Net和DeepLabV3的选择逻辑DeepLabV3的空洞卷积带来更大的感受野对“整条裂缝跨度很大、贯穿整个视野”的场景有优势但它需要更多的样本量来校准空洞卷积的权重而且对小目标的边界恢复不如U-Net的直接跳跃连接。实际项目里我一般先用U-Net跑通基线如果裂缝出现大量断片而模型又学不会长距离关联再切到DeepLabV3。3.2 损失函数Dice Loss 与加权交叉熵裂缝分割最容易出现的训练现象是模型快速收敛到一个“全部预测为背景”的状态验证集Dice为0但交叉熵损失仍然在下降。原因是裂缝占整张图的比例可能不足1%普通BCELoss计算时背景类贡献了绝大部分loss模型发现预测背景就能把loss压得足够低。解决办法是使用Dice Loss。Dice系数本身衡量预测和真实标注在像素集合上的重叠程度对类别不平衡不敏感。单独使用Dice Loss也有问题梯度在预测概率接近0或1时会变得很小训练初期收敛慢。最稳的组合是“交叉熵 Dice”两个损失加权求和import torch import torch.nn as nn class CombinedLoss(nn.Module): def __init__(self, bce_weight0.5, dice_weight0.5): super().__init__() self.bce_weight bce_weight self.dice_weight dice_weight self.bce nn.BCEWithLogitsLoss() def dice_loss(self, logits, targets): probs torch.sigmoid(logits) smooth 1.0 intersection (probs * targets).sum() union probs.sum() targets.sum() return 1 - (2.0 * intersection smooth) / (union smooth) def forward(self, logits, targets): bce self.bce(logits, targets.float()) dice self.dice_loss(logits, targets) return self.bce_weight * bce self.dice_weight * dice代码里的smooth1.0是平滑项防止overlap为0时除法出问题也能让训练初期梯度更稳定。targets必须是浮点型的[0,1]张量如果原始mask是(B, 1, H, W)的uint8需要先.float()。两个权重默认各取0.5实际使用中如果发现Dice提升很慢可以尝试bce_weight0.3, dice_weight0.7让模型更快关注裂缝区域。还有一个更直接的方案是BCEWithLogitsLoss的pos_weight参数给正样本一个加权系数。比如裂缝像素占比约0.5%那pos_weight可以取50到100。但经验是这类极端不平衡任务里Dice Loss的收敛稳定性更好pos_weight调不好反而会带来震荡。3.3 训练超参数batch size、学习率与早停训练配置里batch size、输入分辨率、初始学习率四者互相制约。CT大图的原始分辨率经常是1024×1024甚至更高如果直接整图输入显存根本扛不住。我一般先把图像裁剪到256×256或512×512再做模型输入。crop_size 256 batch_size 8 initial_lr 1e-4 epochs 120 accumulate_steps 2这个配置的含义是每批次加载8张256×256的图accumulate_steps2表示每2次反向传播才更新一次参数等效batch size是16。在U-Net resnet34的规模下这个组合在8GB显存的卡上能跑得动。如果你只有6GB显存把batch_size降到4accumulate_steps提到4效果接近但训练时间会变长。学习率用Adam优化器配1e-4是分割任务里比较稳的起点。注意从头训练和加载预训练权重的学习率策略不同加载ImageNet权重时编码器部分已经学到了通用特征1e-4能让它在原有特征上做小幅调整如果是完全随机初始化这个学习率偏大可能收敛不稳需要降到3e-5或5e-5。学习率调度我习惯用ReduceLROnPlateaufrom torch.optim.lr_scheduler import ReduceLROnPlateau scheduler ReduceLROnPlateau( optimizer, modemax, factor0.5, patience8, min_lr1e-6, )modemax表示监控目标是验证集DiceDice提升才认定模型在变好。patience8是连续8轮Dice没有突破后学习率减半。整个训练过程里最关键的决策是只保存验证集Dice最高的权重文件不要用训练loss作为保存依据因为训练loss下降可能只说明背景类拟合得好裂缝类可能仍然一塌糊涂。3.4 训练主流程代码训练循环本身不复杂把数据加载、损失计算、梯度累积、模型保存串起来就行best_dice 0.0 patience 0 early_stop_patience 15 for epoch in range(epochs): model.train() train_loss_sum 0.0 optimizer.zero_grad() for step, (imgs, masks) in enumerate(train_loader): imgs imgs.cuda() masks masks.cuda() logits model(imgs) loss criterion(logits, masks) loss.backward() if (step 1) % accumulate_steps 0: optimizer.step() optimizer.zero_grad() train_loss_sum loss.item() val_dice evaluate_dice(model, valid_loader) scheduler.step(val_dice) if val_dice best_dice: best_dice val_dice torch.save(model.state_dict(), best_unet_ct.pth) patience 0 else: patience 1 if patience early_stop_patience: print(fearly stop at epoch {epoch}) break代码里有两个细节值得说明。第一optimizer.zero_grad()放在了epoch循环开头配合梯度累积时只在满足accumulate_steps条件才调用optimizer.step()否则梯度会持续累加到下一轮。第二evaluate_dice是验证集Dice的汇总函数计算时模型必须处于model.eval()状态并关闭梯度。如果你显存还是不够可以考虑在训练循环外包装一层torch.cuda.ampscaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): logits model(imgs) loss criterion(logits, masks) scaler.scale(loss).backward()半精度训练能显著降低显存占用代价是约10%的精度损失在裂缝分割这种任务里通常可以接受。需要注意半精度模式下Dice Loss中的torch.sigmoid计算也可能变成半精度必要时在损失函数里显式加torch.float32转换避免数值精度不足导致的Dice震荡。4. 评估与推理部署IoU 计算、滑动窗口与预测结果可视化4.1 评估指标实现的坑IoU与Dice的类别维度训练结束后的第一个问题通常不是“效果好不好”而是“指标是怎么算出来的”。很多人直接把sklearn的jaccard_score拿来用然后在二分类标签上得到异常高的IoU原因在于average参数默认是binary只关注正样本类别背景类被排除在计算外。裂缝占比极低时这种IoU会虚高到90%以上实际上模型只预测出了几条裂缝边缘。正确的做法是自己手写像素级别的二分类IoU和Dicedef compute_iou_dice(pred_mask, true_mask): pred_mask (pred_mask 0.5).astype(int) true_mask (true_mask 0.5).astype(int) intersection (pred_mask * true_mask).sum() union pred_mask.sum() true_mask.sum() - intersection iou intersection / (union 1e-7) pred_sum pred_mask.sum() true_sum true_mask.sum() dice (2 * intersection) / (pred_sum true_sum 1e-7) return iou, dice加1e-7是防止除零同时不影响指标值。这个函数返回的是全局二类指标的近似本质是把裂缝视为正类、背景视为负类的全局像素统计。对于裂缝这种类别极度不平衡的任务它比类平均IoU更悲观也更真实。如果你要写论文建议同时报全局IoU和Dice审稿人更认可这种方法。4.2 推理阶段的滑动窗口与重叠策略训练时输入是256×256的裁剪块但推理时我们面对的是一整张原始CT切片可能有1024×1024甚至更大。直接把全图放进去模型会OOM即便不OOM全图推理的结果也经常在裂缝边界处出现奇怪伪影。原因是模型在固定分辨率下学习到的空间模式在更大视野下不一定能保持。滑动窗口推理是分割任务的标准做法重叠区域预测结果取平均能有效消除patch边缘的接缝效应import numpy as np import torch def predict_full_scan(model, image_3ch, patch_size256, overlap32, devicecuda): model.eval() h, w image_3ch.shape[:2] stride patch_size - overlap pad_h (stride - h % stride) % stride pad_w (stride - w % stride) % stride padded np.pad( image_3ch, ((0, pad_h), (0, pad_w), (0, 0)), modereflect, ) heatmap np.zeros((padded.shape[0], padded.shape[1]), dtypenp.float32) weight_map np.zeros_like(heatmap) for y in range(0, padded.shape[0] - patch_size 1, stride): for x in range(0, padded.shape[1] - patch_size 1, stride): patch padded[y:ypatch_size, x:xpatch_size] patch_tensor torch.from_numpy(patch.transpose(2, 0, 1)) patch_tensor patch_tensor.unsqueeze(0).float().to(device) with torch.no_grad(): logits model(patch_tensor) prob torch.sigmoid(logits).squeeze().cpu().numpy() heatmap[y:ypatch_size, x:xpatch_size] prob weight_map[y:ypatch_size, x:xpatch_size] 1.0 heatmap / np.maximum(weight_map, 1.0) return heatmap[:h, :w]参数patch_size256必须和训练时的输入尺寸一致overlap32控制相邻采样块的重叠宽度。重叠区域被多次预测并求平均理论上重叠越大结果越平滑但推理时间也成倍增长。对256的patch32像素重叠是性价比比较高的折中。np.pad的reflect模式是给原图边界补边用的避免裂缝贴边时滑动窗口覆盖不到。返回前把heatmap裁剪回原始高度和宽度。4.3 预测结果后处理去假阳性与裂缝连通域修补模型输出的是0到1之间的概率图先选阈值二值化。很多人直接用0.5但裂缝边缘模糊时概率值经常在0.4附近浮动。我通常先用连通域分析过滤小面积噪声再把阈值降低到0.45或者0.4因为噪声块面积小通过连通域过滤能去掉大部分。from scipy import ndimage import numpy as np binary (prob_map 0.45).astype(np.uint8) labeled, num_features ndimage.label(binary) min_area 30 for label_id in range(1, num_features 1): area (labeled label_id).sum() if area min_area: binary[labeled label_id] 0min_area30的意思是小于30个像素的前景对象全部视为假阳性置为背景。这个参数取决于图像分辨率256×256的patch下30像素大约是1%的patch面积岩心CT里单块噪声这样的大小很常见如果是1024分辨率的全图min_area要放到100以上。裂缝断裂是指同一条裂缝在预测结果中被分成了几段中间隔着几个像素的间隙。形态学闭运算可以桥接细小断裂from skimage.morphology import binary_closing, disk connected binary_closing(binary, disk(2))disk(2)是半径2的圆形结构元能连接间距在4像素以内的断裂。这个操作要非常克制用disk(3)或更大就会把相邻但实际不相交的裂缝错误连通导致后续裂缝长度统计严重失真。我自己的习惯是先连通域过滤后闭运算这样噪声在闭运算之前已经被清除避免了把噪声和真实裂缝桥接成一个对象。5. 避坑指南灰度图三通道、标签错位、显存炸了怎么办5.1 灰度图被三通道读入标签值乱套现象训练时loss下降很快但预测结果全是灰色噪点或者mask中出现了0、1、255之外的奇怪值。原因cv2.imread默认用IMREAD_COLOR读取图片一张单通道灰度图和单通道掩膜会被读成三通道BGR。如果mask也被读成三通道模型输出的单通道概率图和它计算损失时会发生广播标签语义完全错乱。还有一种情况是刻意做了灰度转三通道但训练时复制加通道的顺序和推理时不一致。解决读图像和mask分别使用cv2.IMREAD_UNCHANGED与cv2.IMREAD_GRAYSCALE。如果为了加载ImageNet预训练权重而复制成三通道写一个统一的grayscale_to_3ch函数在训练和推理的预处理里都必须调用保证通道变换逻辑唯一。5.2 验证集按张随机划分裂缝信息泄漏现象验证集Dice高达0.92但拿同一份权重去预测一块新岩心的CT切片效果立刻掉到0.4以下。原因CT岩心是连续切片相邻两张图的裂缝形态高度相似。按文件随机划分训练集和验证集很可能同一块岩心的相邻切片分布到了两侧验证集没有真正测试模型对未知样本的泛化能力。解决按岩心块ID划分数据集。先统计文件名里属于哪块岩心把同一岩心ID的所有切片放在同一个集合里再按岩心ID比例划分训练、验证。比如有10块岩心取8块做训练2块做验证。宁可训练数据少一些也不能让验证集泄漏。5.3 归一化后裂缝对比度反而消失现象模型训练了十几个epoch裂缝仍然基本漏掉可视化输入图像发现裂缝区域和背景灰度特别接近。原因对16位CT图直接用img / 65535归一化。CT值动态范围很大高密度矿物区占用了大部分灰度区间裂缝区域被挤压到最后几个灰度级。或者用百分位裁剪时参数选得太宽比如设置low_percent0, high_percent100等价于没裁剪极值噪声继续拉宽灰度范围。解决先画直方图观察像素集中分布在哪个区间再把low_percent调到2、high_percent到98之后叠加CLAHE增强。验证预处理效果的方法是保存几张处理后的图肉眼看裂缝是否清晰可见数值上可以检查裂缝区域灰度均值与周边区域的差值是否大于20。5.4 整张图预测成全黑Dice为0现象训练过程loss正常下降但验证Dice始终接近0模型输出全部是背景。原因绝大多数情况是损失函数里只有交叉熵没有Dice Loss或正样本加权。裂缝像素占比太低模型只要预测全部背景交叉熵loss也能保持很低于是训练陷入了局部最优。解决改用前面写的CombinedLoss让交叉熵和Dice各占一半。换损失函数后头几个epoch可能看到Dice波动变大这是正常的说明模型开始尝试预测裂缝区域。另外检查mask是否有问题——如果某张mask因为标注转换失败全是0这个样本会进一步加剧全背景预测。5.5 显存不足batch size调小后训练效果变差现象设置batch_size8训练到一半 CUDA OOM改成batch_size2后能跑但验证Dice明显下降。原因batch size减小到2后梯度的随机性变大BN层的统计量也不稳定效果自然会受影响。这不是模型问题是工程配置问题。解决用梯度累积恢复等效batch sizebatch_size2accumulate_steps8等效于batch_size16。同时开启torch.cuda.amp半精度显存占用能下降约30%。如果显存还是不够把输入从512降成448或者关闭cudnn.benchmark虽然慢一点但更稳定。6. 进阶裂缝连通域拆解与骨架提取分割出裂缝掩膜其实只完成了一半工作很多课程设计和论文还需要从掩膜里提取裂缝的长度、开度、方向这些定量参数。我常用的流程是先连通域标记再提取单像素骨架最后对骨架做几何统计。6.1 裂缝长度与开度粗估from skimage.morphology import skeletonize from scipy import ndimage import numpy as np # binary 是后处理过的裂缝掩膜 skeleton skeletonize(binary).astype(np.uint8) labeled, num ndimage.label(skeleton) for label_id in range(1, num 1): mask_i (labeled label_id) length mask_i.sum() area binary[mask_i].sum() width area / length print(裂缝ID, label_id, 长度(像素), length, 平均开度(像素), width)骨架提取后裂缝像素的个数近似等于裂缝的几何长度原始掩膜面积除以骨架长度就是平均开度。这个数值受分辨率影响写报告时要除以CT图像的单像素物理尺寸换算成毫米。6.2 批量可视化原图、标注、预测三拼图手动一盘盘查看结果太费时间我一般在预测之后直接生成三拼图原图、真实mask、预测mask各占一列按文件名批量保存。之后写期末大作业时直接把图粘贴进报告一眼就能看出模型哪里漏了、哪里误检不用反复打开数组翻看。从那以后我每次训练完都会强制走一遍“检查预处理直方图、检查验证集划分、确认mask值和预测阈值”这三件事再往下游做定量分析。裂缝分割很容易被表面指标骗过去只有把每一步的中间结果都打开看一眼才能在翻车前拦住问题。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

VitePress 自定义主题实战指南:从零构建 Theme、Layout 与运行时 API

VitePress 自定义主题实战指南:从零构建 Theme、Layout 与运行时 API

AI 技能人工智能 【免费下载链接】skills Anthony Fus curated collection of agent skills. 项目地址: https://gitcode.com/gh_mirrors/skills11/skills 点击查看 免费下载 当 VitePress 默认主题的导航、侧边栏、首页版式无法满足项目需要时,不必修改…

2026/10/11 11:46:15 阅读更多 →
第三方软件测评:独立视角下的软件质量保障

第三方软件测评:独立视角下的软件质量保障

最近接手了一个很有意思的项目,是以第三方视角给一套企业级管理软件做全面测评。这个项目让我不得不重新思考一个老问题:为什么软件行业需要"裁判员"?我们常说"既当运动员又当裁判员"是比赛的大忌,但放到软件…

2026/10/11 11:45:15 阅读更多 →
用Anaconda管理Python多环境:解决版本兼容与依赖冲突的完整指南

用Anaconda管理Python多环境:解决版本兼容与依赖冲突的完整指南

先说一个最磨人的场景:你手上同时维护着两个AI相关的小项目,一个图像处理Demo指定要PyTorch配Python 3.8,另一个文本分类脚本又要用Python 3.10。两个项目只要凑在同一个解释器里,必然有一个先坏掉。我以前被这种版本兼容问题折腾…

2026/10/11 11:45:15 阅读更多 →

最新新闻

MySQL进程与操作系统内核的亲密接触:从启动到崩溃恢复的全程拆解

MySQL进程与操作系统内核的亲密接触:从启动到崩溃恢复的全程拆解

你有没有认真想过,一个mysqld进程从被操作系统拉起,到它最终退出,中间到底和内核打了多少次交道?重启一次数据库、做一次主从切换、甚至排查一条慢SQL,背后都藏着一长串系统调用在排队。我最早接触MySQL的时候&#xf…

2026/10/11 12:39:30 阅读更多 →
用Flask构建医院挂号就诊系统:数据库建模、并发控制与实战解析

用Flask构建医院挂号就诊系统:数据库建模、并发控制与实战解析

每年一到毕业设计和面试季节,总有人问我同一个问题:“想做一个带点实际业务逻辑的Web项目,用什么技术栈最划算?”我的回答一般都很固定:Flask配Python。如果再追问一句具体做什么,我会直接抛出一个练手与实…

2026/10/11 12:39:30 阅读更多 →
自动化流程中的表格列操作:增删改查与注解全指南

自动化流程中的表格列操作:增删改查与注解全指南

屠龙刀法这个系列写到第33期了,前32篇讲了各种流程搭建、数据清洗、自动化分支的套路,但一直没有专门把"表格列的增删改查"单独拎出来讲。原因是我之前觉得这玩意儿太基础,谁还不会右键添加一列?直到上个月,…

2026/10/11 12:39:30 阅读更多 →
Oracle 11gR2 Gateways异构数据库连接实战指南

Oracle 11gR2 Gateways异构数据库连接实战指南

简介:本资源是Oracle Database 11gR2 Gateways(11.2.0.1.0)官方安装包,专为Linux x86-64平台设计,面向数据库管理员、中间件工程师及企业级异构系统集成开发者,用于构建Oracle与非Oracle数据库(…

2026/10/11 12:39:30 阅读更多 →
fish-shell 文档本地化实战:Sphinx + sphinx-intl 自动翻译工作流

fish-shell 文档本地化实战:Sphinx + sphinx-intl 自动翻译工作流

1. 先把背景交代清楚:fish-shell-docs-l10n 到底在做什么最近我把一个叫 fish-shell-docs-l10n 的项目从想法推进到了基本可用状态,过程比想象中曲折,也踩了不少坑。这篇文章不打算写什么宏大叙事,就是把我在做 fish-shell 文档本…

2026/10/11 12:39:30 阅读更多 →
汉字简繁转换映射表:数据建模、SQL导入与避坑指南

汉字简繁转换映射表:数据建模、SQL导入与避坑指南

简介:这是一套提供约4792条汉字简体与繁体映射关系的参照数据库,覆盖常见高频用字,面向需要实现简繁转换、多语言支持或汉字文本处理的开发人员、数据挖掘与语言研究者,可直接用于软件界面切换、语料预处理及汉字演变研究。压缩包…

2026/10/11 12:38:30 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →