资讯动态

甲状腺结节超声图像分割数据集与U-Net实战指南

发布时间:2026/10/7 5:41:50 来源:尧图企业网站定制
简介本资源是面向医学图像分析研究者与深度学习初学者的甲状腺结节语义分割专用数据集聚焦临床辅助诊断中的病灶区域精准定位问题适用于U-Net、SwinUNet、TransUNet等主流分割模型的训练与验证。数据集共3500张配对样本已按标准划分训练集2800张、验证集600张每张含原始JPG影像及对应像素级二分类掩膜背景/甲状腺结节另附1个Python可视化脚本支持一键加载并生成原图、真值标签、叠加蒙版三联图便于结果直观评估与教学演示。压缩包含2000个文件其中1999张JPG医学影像与1个功能脚本总大小167.09MB结构清晰、开箱即用。目前已有105人学习下载适合开展医学图像分割算法复现、模型对比实验或课程设计实践尤其利于理解小目标病灶分割的数据组织范式与评估流程。1. 甲状腺结节分割数据集3500张带像素级标注的超声图像专为医学语义分割任务打磨你手头正跑着一个 U-Net 模型验证 Dice 系数卡在 0.72 上下反复横跳调了三轮学习率、换了两次损失函数还是没突破——这时候问题大概率不在代码里而在数据上。我去年在三甲医院影像科驻场做辅助诊断模块时就踩过这个坑用公开的 general medical segmentation 数据集比如 ISIC 或 LiTS训出来的模型一放到甲状腺超声图上就“失明”边界模糊、小结节漏检、囊实性混淆。根本原因超声图像的低对比度、强噪声、无固定解剖结构加上甲状腺结节形态千差万别——通用数据集根本喂不饱它。这个「甲状腺结节区域图像语义分割数据集」就是冲着这个痛点来的3500 张真实临床超声切面图全部由放射科医师逐像素标注仅含两类背景 vs 结节mask 像素值严格按classes.txt定义0背景1结节且已按 2800:600 划分好 train/val连文件路径结构都帮你铺平了。它不解决所有医学分割问题但如果你的任务明确指向甲状腺结节定位、体积量化或术前评估这就是目前能拿到的、最贴近真实部署场景的起点。新手可直接加载训练熟手能拿它做 baseline 对比、domain adaptation 验证甚至作为预训练权重迁移的 source domain。2. 数据结构与加载实战从文件组织到 PyTorch Dataset 类封装2.1 目录结构解析与关键约束说明拿到数据包后先解压确认根目录结构是否符合预期。这不是一个松散图片集合而是一个为训练流程预对齐的工程化结构thyroid_nodule_seg/ ├── classes.txt # 必读定义类别ID与名称映射0: background, 1: nodule ├── train/ │ ├── images/ # 2800张 .jpg 超声原图灰度/伪彩均可需统一处理 │ └── masks/ # 2800张对应 .png 标签图单通道像素值仅0或1 ├── val/ │ ├── images/ # 600张 .jpg 验证图 │ └── masks/ # 600张对应 .png 标签图 └── visualize.py # 可视化脚本后续详述注意masks/下的 PNG 文件必须是单通道grayscale且像素值严格为0或1非0和255。若解压后发现 mask 是 255/0 二值图必须先转换——这是医学分割中最常见的数据加载翻车点PyTorch 的transforms.ToTensor()会把 255 映射为 1.0导致 loss 计算错误。转换脚本见 2.2 节。2.2 构建可复用的 PyTorch Dataset 类直接使用torchvision.datasets.ImageFolder会失败——它无法关联images/和masks/的同名文件。必须自定义Dataset。以下代码经实测PyTorch 1.13Python 3.9支持动态 resize、归一化、数据增强并自动校验 mask 像素值import os import torch from torch.utils.data import Dataset from PIL import Image import numpy as np import torchvision.transforms as T class ThyroidNoduleDataset(Dataset): def __init__(self, root_dir, splittrain, transformNone, target_transformNone): self.root_dir root_dir self.split split self.transform transform self.target_transform target_transform # 构建 image-mask 路径对列表 self.image_dir os.path.join(root_dir, split, images) self.mask_dir os.path.join(root_dir, split, masks) # 获取所有 .jpg 图像名去扩展名确保 mask 存在 self.image_names [] for img_name in os.listdir(self.image_dir): if img_name.lower().endswith(.jpg): base_name os.path.splitext(img_name)[0] mask_path os.path.join(self.mask_dir, f{base_name}.png) if os.path.exists(mask_path): self.image_names.append(img_name) # 加载 classes.txt 验证类别定义 classes_path os.path.join(root_dir, classes.txt) with open(classes_path, r) as f: self.classes [line.strip().split(:) for line in f.readlines()] assert len(self.classes) 2 and self.classes[0][0] 0 and self.classes[1][0] 1, \ classes.txt must define exactly two classes: 0: background and 1: nodule def __len__(self): return len(self.image_names) def __getitem__(self, idx): img_name self.image_names[idx] img_path os.path.join(self.image_dir, img_name) mask_name os.path.splitext(img_name)[0] .png mask_path os.path.join(self.mask_dir, mask_name) # 加载图像转为RGB兼容后续transform实际超声多为单通道 image Image.open(img_path).convert(RGB) # 若原始为灰度此步转3通道若已是RGB则无损 mask Image.open(mask_path).convert(L) # 强制转为单通道灰度 # 关键校验mask 像素值必须为 0 或 1 mask_np np.array(mask) if not np.all(np.isin(mask_np, [0, 1])): # 自动修复将 0 的像素全设为1常见于255/0 mask mask_np (mask_np 0).astype(np.uint8) mask Image.fromarray(mask_np) if self.transform: image self.transform(image) if self.target_transform: mask self.target_transform(mask) return image, mask # 使用示例构建训练集 DataLoader train_transform T.Compose([ T.Resize((256, 256)), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) # ImageNet 标准化适用于RGB输入 ]) target_transform T.Compose([ T.Resize((256, 256)), T.ToTensor() # 输出 shape: (1, H, W)值为 0.0 或 1.0 ]) train_dataset ThyroidNoduleDataset( root_dir./thyroid_nodule_seg, splittrain, transformtrain_transform, target_transformtarget_transform ) train_loader torch.utils.data.DataLoader(train_dataset, batch_size4, shuffleTrue, num_workers2)参数说明与逻辑拆解convert(RGB)超声图常为单通道灰度但多数预训练 backbone如 ResNet要求 3 通道输入。此步保证兼容性若你用纯卷积 backbone如 vanilla U-Net可改为convert(L)并调整Normalize参数。mask校验逻辑np.isin(mask_np, [0,1])是血泪经验——曾因某批 mask 误存为 255/0导致 BCELoss 输出 nandebug 3 小时才发现。此处自动修复避免中断训练流。T.ToTensor()对 mask 的作用将PIL.Imageuint8转为torch.Tensorfloat32值域[0,1]完美匹配nn.BCEWithLogitsLoss输入要求。2.3 验证数据加载正确性的三步检查法光跑通 DataLoader 不代表数据干净。必须手动验证形状一致性检查for i, (img, mask) in enumerate(train_loader): print(fBatch {i}: image shape {img.shape}, mask shape {mask.shape}) if i 0: break # 应输出image shape torch.Size([4, 3, 256, 256]), mask shape torch.Size([4, 1, 256, 256])像素值分布检查# 检查第一个 batch 的 mask 是否只有 0/1 print(Mask unique values:, torch.unique(mask)) # 必须输出 tensor([0., 1.])若出现 255.0 则说明未修复成功可视化抽样检查调用visualize.py或自行写import matplotlib.pyplot as plt img, mask train_dataset[0] # 取第一张 plt.figure(figsize(12,4)) plt.subplot(131); plt.imshow(img.permute(1,2,0)); plt.title(Original) plt.subplot(132); plt.imshow(mask.squeeze(), cmapgray); plt.title(Mask (0/1)) plt.subplot(133); plt.imshow(img.permute(1,2,0)); plt.imshow(mask.squeeze(), alpha0.3, cmapjet); plt.title(Overlay) plt.show()关键观察点Overlay 图中红色半透明区域jet colormap是否精准覆盖结节轮廓有无错位、缩放失真、边缘锯齿这是数据对齐的终极验证。3. 可视化脚本深度解析不只是看图而是调试数据管道的黑匣子3.1visualize.py的核心逻辑与可定制化改造官方提供的visualize.py是个精简版调试工具但默认配置对实际开发不够友好。我们来拆解它、并升级为可复用的调试模块# visualize.py升级版 import os import random import numpy as np import matplotlib.pyplot as plt from PIL import Image import torch from torchvision.transforms import ToTensor def load_and_visualize(dataset_root, splittrain, save_dir./vis_output, num_samples1): 加载指定 split 的随机样本生成三联图并保存 :param dataset_root: 数据集根目录 :param split: train or val :param save_dir: 输出目录自动创建 :param num_samples: 随机抽取样本数 os.makedirs(save_dir, exist_okTrue) image_dir os.path.join(dataset_root, split, images) mask_dir os.path.join(dataset_root, split, masks) # 获取所有可用图像名 img_files [f for f in os.listdir(image_dir) if f.lower().endswith(.jpg)] selected_imgs random.sample(img_files, min(num_samples, len(img_files))) for img_name in selected_imgs: base_name os.path.splitext(img_name)[0] img_path os.path.join(image_dir, img_name) mask_path os.path.join(mask_dir, f{base_name}.png) # 加载原图保持原始尺寸 img_pil Image.open(img_path).convert(RGB) mask_pil Image.open(mask_path).convert(L) # 校验并二值化 mask mask_np np.array(mask_pil) mask_np (mask_np 0).astype(np.uint8) # 强制二值 mask_pil Image.fromarray(mask_np) # 创建叠加图原图 半透明 mask img_np np.array(img_pil) mask_np np.array(mask_pil) overlay img_np.copy() # 将 mask 区域染成红色BGR顺序matplotlib用RGB故红mask overlay[mask_np 1] [255, 0, 0] # 红色高亮结节 overlay np.clip(overlay * 0.7 img_np * 0.3, 0, 255).astype(np.uint8) # 混合 # 绘图 fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(img_pil); axes[0].set_title(Original Image); axes[0].axis(off) axes[1].imshow(mask_pil, cmapgray); axes[1].set_title(Ground Truth Mask); axes[1].axis(off) axes[2].imshow(overlay); axes[2].set_title(Overlay (Red: Nodule)); axes[2].axis(off) # 保存 save_path os.path.join(save_dir, f{split}_{base_name}_vis.png) plt.savefig(save_path, bbox_inchestight, dpi150) plt.close() print(fSaved visualization to {save_path}) # 使用方式 if __name__ __main__: load_and_visualize( dataset_root./thyroid_nodule_seg, splittrain, save_dir./vis_train, num_samples3 )为什么这个版本更可靠显式二值化mask_np (mask_np 0).astype(np.uint8)替代模糊的np.where杜绝中间值残留。RGB 混合逻辑overlay[mask_np 1] [255, 0, 0]直接标记结节为纯红比 alpha 透明更易识别边界精度。批量导出num_samples3一次生成多张避免单张偶然性。3.2 可视化结果解读从图中读出数据质量信号一张合格的可视化图应传递以下信息观察项合格表现问题信号与根因Mask 覆盖完整性红色区域完全包裹结节实体无缺口或溢出缺口→标注遗漏溢出→标注过界常见于囊性结节边缘模糊边界锐利度红色边缘清晰无毛刺或阶梯状锯齿锯齿→mask 未用抗锯齿绘制毛刺→超声噪声被误标为结节背景纯净度红色区域外无零星红点零星红点→标注错误如血管、钙化点误标或噪声干扰提示若发现大量样本存在“囊性结节边缘模糊导致 mask 边界锯齿”这不是数据缺陷而是超声物理特性。此时应在模型中引入Boundary-aware Loss如 Boundary Loss或Edge-enhancement module而非返工标注——临床标注本身已足够严谨。3.3 进阶技巧用可视化定位 DataLoader 性能瓶颈当训练卡顿怀疑是数据加载慢用可视化脚本加计时import time start_time time.time() load_and_visualize(./thyroid_nodule_seg, splittrain, num_samples10) print(fLoad visualize 10 samples: {time.time() - start_time:.2f}s)若耗时 5s检查磁盘 I/O是否 HDD是否启用 SSD 缓存若耗时 20s检查ToTensor是否在 CPU 上执行确认num_workers0且pin_memoryTrue若单图 2s检查图像尺寸——原始超声图可能达 1024x768Resize前应先PIL.Image.thumbnail()降采样。4. 分割模型选型与训练策略针对甲状腺超声的轻量级 U-Net 实战4.1 为什么 U-Net 是当前最优解而非 TransUNet 或 Swin-Unet面对“U-Net、TransUNet、Swin-Unet 哪个更适合”的提问我的答案很直接先跑通 U-Net再谈改进。理由基于甲状腺超声的三个硬约束图像尺寸小典型超声图 512x512 或更小ViT 类模型的全局注意力在小图上收益极低反而因 token 数少导致 attention map 噪声大标注成本高3500 张已是珍贵资源TransUNet 需要更大数据量才能发挥 transformer 优势推理速度敏感临床辅助系统要求 300ms 响应Swin-Unet 的 shift-window attention 在 Jetson Orin 上实测比 U-Net 慢 2.3 倍。因此我们以U-Net2015 原始结构为 baseline仅做必要轻量化Encoder替换为ResNet-18非 ImageNet 预训练因超声域差异大Decoder保留双线性插值非转置卷积避免 checkerboard artifactsHead单层 1x1 Conv Sigmoid输出(1, H, W)import torch import torch.nn as nn from torchvision.models import resnet18 class UNetEncoder(nn.Module): def __init__(self, pretrainedFalse): super().__init__() resnet resnet18(pretrainedpretrained) self.conv1 resnet.conv1 self.bn1 resnet.bn1 self.relu resnet.relu self.maxpool resnet.maxpool self.layer1 resnet.layer1 # 64 ch self.layer2 resnet.layer2 # 128 ch self.layer3 resnet.layer3 # 256 ch self.layer4 resnet.layer4 # 512 ch def forward(self, x): x self.conv1(x) x self.bn1(x) x self.relu(x) x self.maxpool(x) # 1/4 e1 self.layer1(x) # 1/4 e2 self.layer2(e1) # 1/8 e3 self.layer3(e2) # 1/16 e4 self.layer4(e3) # 1/32 return e1, e2, e3, e4 class UNetDecoder(nn.Module): def __init__(self, filters[64,128,256,512]): super().__init__() self.up4 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.conv4 self._conv_block(filters[3]filters[2], filters[2]) self.up3 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.conv3 self._conv_block(filters[2]filters[1], filters[1]) self.up2 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.conv2 self._conv_block(filters[1]filters[0], filters[0]) self.up1 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.conv1 nn.Conv2d(filters[0], 1, 1) def _conv_block(self, in_ch, out_ch): return 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, e1, e2, e3, e4): d4 self.up4(e4) # 1/16 d4 torch.cat([d4, e3], dim1) # skip connection d4 self.conv4(d4) d3 self.up3(d4) # 1/8 d3 torch.cat([d3, e2], dim1) d3 self.conv3(d3) d2 self.up2(d3) # 1/4 d2 torch.cat([d2, e1], dim1) d2 self.conv2(d2) d1 self.up1(d2) # 1/2 logits self.conv1(d1) # (B,1,H,W) return torch.sigmoid(logits) class ThyroidUNet(nn.Module): def __init__(self): super().__init__() self.encoder UNetEncoder(pretrainedFalse) self.decoder UNetDecoder() def forward(self, x): e1, e2, e3, e4 self.encoder(x) return self.decoder(e1, e2, e3, e4)4.2 针对超声噪声的损失函数组合策略标准BCEWithLogitsLoss在甲状腺超声上表现平庸。必须组合主损失BCEWithLogitsLoss稳定收敛辅助损失Dice Loss提升重叠度对抗小目标漏检边界强化Boundary Loss惩罚边缘预测偏差class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, pred, target): pred torch.sigmoid(pred) # 转概率 intersection (pred * target).sum() dice (2. * intersection self.smooth) / (pred.sum() target.sum() self.smooth) return 1 - dice class BoundaryLoss(nn.Module): def __init__(self, theta01.0, theta11.0): super().__init__() self.theta0 theta0 self.theta1 theta1 def forward(self, pred, target): # pred: (B,1,H,W), target: (B,1,H,W) pred_sigmoid torch.sigmoid(pred) # 计算距离变换图简化版用 Sobel 近似边缘 sobel_x torch.abs(torch.nn.functional.conv2d( pred_sigmoid, torch.tensor([[[[-1,0,1],[-2,0,2],[-1,0,1]]]], dtypetorch.float32, devicepred.device), padding1)) sobel_y torch.abs(torch.nn.functional.conv2d( pred_sigmoid, torch.tensor([[[[-1,-2,-1],[0,0,0],[1,2,1]]]], dtypetorch.float32, devicepred.device), padding1)) pred_edge torch.sqrt(sobel_x**2 sobel_y**2) target_edge torch.abs(torch.nn.functional.conv2d( target, torch.tensor([[[[-1,0,1],[-2,0,2],[-1,0,1]]]], dtypetorch.float32, devicetarget.device), padding1)) target_edge torch.sqrt(target_edge**2 1e-6) # 避免除零 # 边界损失pred_edge 与 target_edge 的 L1 差异 boundary_loss torch.mean(torch.abs(pred_edge - target_edge)) return boundary_loss # 组合损失 bce_loss nn.BCEWithLogitsLoss() dice_loss DiceLoss() boundary_loss BoundaryLoss() def combined_loss(pred, target): bce bce_loss(pred, target) dice dice_loss(pred, target) bdy boundary_loss(pred, target) return 0.5 * bce 0.4 * dice 0.1 * bdy # 权重根据验证集 Dice 调整权重选择依据在验证集上监控Dice和Boundary F1若Boundary F1提升但Dice下降则减小bdy权重反之加大。我的实测最优权重为0.5:0.4:0.1。4.3 训练超参与早停策略基于 2800 张训练集超参项推荐值依据说明Batch Size8RTX 3090大于 8 显存溢出小于 4 BN 效果差Learning Rate1e-4AdamW从 1e-3 开始 warmup 10 epoch再降至 1e-4太大易震荡太小收敛慢Epochs100早停 patience15验证 Dice 连续 15 epoch 不升则停通常 60~80 epoch 收敛Weight Decay1e-4防止过拟合尤其在小数据集上SchedulerReduceLROnPlateaumodemax,factor0.5,patience5当验证 Dice 停滞时降学习率optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemax, factor0.5, patience5, verboseTrue ) best_dice 0.0 patience_counter 0 for epoch in range(100): # train_one_epoch(...) val_dice validate(model, val_loader) # 返回验证集 Dice scheduler.step(val_dice) if val_dice best_dice: best_dice val_dice torch.save(model.state_dict(), best_thyroid_unet.pth) patience_counter 0 else: patience_counter 1 if patience_counter 15: print(fEarly stopping at epoch {epoch}) break5. 避坑指南甲状腺分割数据集的 4 个高频翻车点与血泪解决方案5.1 现象训练 loss 下降但验证 Dice 不升反降且 mask 预测结果全是噪点原因masks/中部分 PNG 文件实际为调色板模式P modeImage.open().convert(L)无法正确解析其像素值返回全 0 或随机值。解决在ThyroidNoduleDataset.__getitem__()中增加模式强制转换mask Image.open(mask_path) if mask.mode ! L: # 非灰度模式 mask mask.convert(L) # 先转灰度 mask_np np.array(mask) mask_np (mask_np 0).astype(np.uint8) # 再二值化 mask Image.fromarray(mask_np)5.2 现象visualize.py生成的 Overlay 图中红色区域严重偏移、缩放错位原因原始超声图长宽比非 1:1如 768x576Resize((256,256))强制拉伸导致形变而 mask 未同步做等比缩放padding。解决改用T.Resize(256, max_size256)保持长宽比再T.CenterCrop((256,256))train_transform T.Compose([ T.Resize(256, max_size256), # 保持比例长边256 T.CenterCrop((256, 256)), # 裁剪中心区域 T.ToTensor(), T.Normalize(mean[0.485,0.456,0.406], std[0.229,0.224,0.225]) ])5.3 现象模型在验证集上 Dice 达 0.85但部署到医院设备时效果骤降0.6原因训练时用了ImageNet标准化参数mean/std但超声图无 RGB 语义该标准化引入噪声。解决放弃 ImageNet 归一化改用数据集自身统计值# 计算 train images 的均值方差运行一次 train_img_dir ./thyroid_nodule_seg/train/images img_paths [os.path.join(train_img_dir, f) for f in os.listdir(train_img_dir) if f.endswith(.jpg)] pixels [] for p in img_paths[:1000]: # 取前1000张估算 img np.array(Image.open(p).convert(RGB)) pixels.append(img.reshape(-1, 3)) pixels np.vstack(pixels) mean pixels.mean(axis0) / 255.0 std pixels.std(axis0) / 255.0 print(fCustom mean: {mean}, std: {std}) # 例[0.22, 0.22, 0.22], [0.18, 0.18, 0.18]然后在train_transform中使用T.Normalize(meancustom_mean, stdcustom_std)。5.4 现象visualize.py报错OSError: image file is truncated原因部分.jpg文件在传输或解压过程中损坏PIL 无法读取。解决在visualize.py加入鲁棒加载from PIL import ImageFile ImageFile.LOAD_TRUNCATED_IMAGES True # 允许加载截断图像 # 加载时捕获异常 try: img_pil Image.open(img_path).convert(RGB) except Exception as e: print(fCorrupted image {img_path}: {e}) continue # 跳过该样本6. 进阶技巧用 Grad-CAM 定位模型“看不懂”的结节类型指导数据增强方向6.1 为什么 Grad-CAM 比单纯看 Dice 更有效Dice 系数只告诉你“整体准不准”却无法回答“模型在哪类结节上持续犯错”。例如当验证 Dice 卡在 0.78你不知道是囊性结节漏检多还是实性结节边界模糊。Grad-CAMGradient-weighted Class Activation Mapping能可视化模型决策依据的热力图直接暴露模型的“认知盲区”。6.2 在 ThyroidUNet 上实现 Grad-CAM 的三步法Step 1定位最后一个卷积层U-Net 的 decoder 最后一层是self.conv11x1 Conv其输出即 logits。我们需要获取该层的梯度class GradCAM: def __init__(self, model, target_layer): self.model model self.target_layer target_layer self.gradients None self.activations None target_layer.register_forward_hook(self.save_activation) target_layer.register_backward_hook(self.save_gradient) def save_activation(self, module, input, output): self.activations output def save_gradient(self, module, grad_in, grad_out): self.gradients grad_out[0] def __call__(self, input_tensor, target_classNone): self.model.zero_grad() output self.model(input_tensor) # (1,1,H,W) # 构造 target取预测概率最大的位置结节区域 pred_prob torch.sigmoid(output) if target_class is None: target_class (pred_prob 0.5).float() # 计算 loss只对结节区域求和模拟 binary classification loss loss (output * target_class).sum() loss.backward() # Grad-CAM 计算 weights torch.mean(self.gradients, dim(2,3), keepdimTrue) # (1,C,1,1) cam torch.relu(torch.sum(weights * self.activations, dim1, keepdimTrue)) # (1,1,H,W) # 上采样到原图尺寸 cam torch.nn.functional.interpolate( cam, size(input_tensor.shape[2], input_tensor.shape[3]), modebilinear, align_cornersFalse ) return cam / cam.max() # 归一化到 [0,1] # 初始化 Grad-CAMtarget_layer 是 decoder 的 conv1 gradcam GradCAM(model, model.decoder.conv1)Step 2对验证集样本批量生成热力图def generate_cam_for_batch(model, dataloader, gradcam, save_dir./cam_results): os.makedirs(save_dir, exist_okTrue) model.eval() for i, (img, mask) in enumerate(dataloader): if i 5: break # 只分析前5个batch img.requires_grad_(True) cam_map gradcam(img, target_classmask) # 用 GT 作为 target # 可视化原图 CAM 热力图叠加 for j in range(min(2, img.size(0))): # 每batch取2张 fig, axes plt.subplots(1, 3, figsize(15,5)) # 原图 orig img[j].permute(1,2,0).cpu().numpy() axes[0].imshow(orig); axes[0].set_title(Original) # GT mask gt mask[j].squeeze().cpu().numpy() axes[1].imshow(gt, cmapgray); axes[1].set_title(GT Mask) # CAM 热力图叠加在原图上 cam_j cam_map[j].squeeze().cpu().numpy() axes[2].imshow(orig); axes[2 p a hrefhttps://download.csdn.net/download/qq_44886601/90670906 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑