多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

医学图像语义分割实战:DICOM预处理、U-Net改造与MONAI部署

医学图像语义分割实战:DICOM预处理、U-Net改造与MONAI部署 简介本资源是一份面向计算机专业本科生的毕业设计与课程作业级项目聚焦基于深度学习的医学图像语义分割任务适用于AI医疗方向实践学习、模型复现与系统集成训练。项目融合深度学习建模U-Net等架构、Python端训练推理、C/CUDA高性能模块开发及完整系统流程构建覆盖数据预处理、模型训练、结果可视化与后处理全链路。压缩包共438个文件含22个Python脚本训练/评估主逻辑、151个C源码与43个CUDA核函数支撑底层加速、31个Caffe prototxt配置网络结构定义、30个CMake构建文件跨平台编译支持及30个Markdown文档含环境配置、实验记录与使用说明整体体积11.12MB结构清晰、模块解耦度高。已有151人学习下载读者可直接获取可运行的多语言协同代码框架、带标注的医学图像处理流程、CaffePyTorch混合训练范式参考及完整构建部署脚本显著降低医学影像分割项目落地门槛。1. 医学图像语义分割不是“把图切开就行”毕设/课设里最常翻车的三类坑90%学生卡在数据预处理和标签对齐上你手头这个.zip文件名字叫“毕设课程作业_基于深度学习的医学图像语义分割”但它真正要解决的根本不是“跑通一个U-Net模型”这么简单。它本质是在模拟一个临床辅助诊断场景比如从MRI中精准抠出胶质瘤区域、从CT肺部影像里标出磨玻璃影与实变区、或在病理切片中区分癌组织与正常腺体——这些区域边界模糊、对比度低、标注成本极高且单张图像里目标占比可能不足5%。正因如此直接套用Pascal VOC或Cityscapes那套训练流程90%会失败验证集Dice系数卡在0.4以下、测试结果全是噪声斑点、甚至模型把背景血管当成病灶输出。这不是代码写错了而是医学图像特有的物理成像特性如MRI的偏置场、CT的窗宽窗位依赖、超声的斑点噪声和标注规范像素级手工描边 vs. 粗糙多边形框没被纳入建模闭环。本文不讲抽象原理只聚焦你能立刻抄作业的6个关键动作怎么把DICOM转成可用的PNGmask、为什么必须重采样到统一尺寸而非简单resize、如何用SimpleITK做强度归一化、labelImg标完后怎样校验mask与原图严格对齐、U-Net输入通道数为何不能硬设为3、以及——最关键的——验证时为什么必须用原始分辨率反向映射预测结果再计算指标。所有步骤均基于PyTorch MONAI生态适配Windows/Linux/macOS无需GPU也能本地调试。2. 从DICOM到训练数据医学图像预处理的四步不可跳过流水线医学图像语义分割的起点从来不是“加载一张jpg”。真实数据源95%是DICOM序列如MRI的多个层面、CT的数百层扫描而标注文件通常是医生在专用软件如3D Slicer、ITK-SNAP中手动勾画的NIfTI格式掩膜。直接丢进PyTorch DataLoader等着模型学出一堆伪影吧。下面这四步我带过17届毕设每届都有人因跳过第2步或第3步导致Dice系数比baseline低0.15以上。2.1 DICOM序列→单通道灰度图用SimpleITK提取关键slice并标准化窗宽窗位医学图像的像素值本身无绝对物理意义——CT的HU值、MRI的信号强度都依赖设备参数。若不做窗宽窗位Window Width/Level调整同一病灶在不同设备上可能呈现完全不同的灰度分布。常见错误是直接用cv2.imread()读DICOM结果得到的是原始16位整数如-1024~3071而模型输入要求0~255浮点归一化。import SimpleITK as sitk import numpy as np def dicom_to_normalized_png(dicom_dir: str, output_path: str, ww: int 400, wl: int 40): 将DICOM序列转换为标准化灰度PNG :param dicom_dir: DICOM文件所在目录含多个.dcm文件 :param output_path: 输出PNG路径 :param ww: Window Width (CT常用400, MRI需调至2000) :param wl: Window Level (CT常用40, MRI脑组织常用100) # 读取DICOM序列并重建3D体积 reader sitk.ImageSeriesReader() dicom_names reader.GetGDCMSeriesFileNames(dicom_dir) reader.SetFileNames(dicom_names) image3D reader.Execute() # 提取中间slice避免首尾层伪影 z_dim image3D.GetSize()[2] mid_slice z_dim // 2 slice2D image3D[:, :, mid_slice] # 应用窗宽窗位仅对CT有效MRI需改用强度归一化 if CT in sitk.ReadImage(dicom_names[0]).GetMetaData(Modality): windowed sitk.IntensityWindowing(slice2D, wl - ww//2, wl ww//2, 0, 255) else: # MRI用Z-score归一化替代窗宽窗位 array sitk.GetArrayFromImage(slice2D) array (array - np.mean(array)) / (np.std(array) 1e-8) array np.clip(array, -3, 3) # 截断±3σ外离群值 windowed sitk.GetImageFromArray((array 3) / 6 * 255) # 映射到0-255 # 保存为PNG必须用sitk.WriteImage避免OpenCV通道错乱 sitk.WriteImage(windowed, output_path) # 示例调用处理一个CT病例 dicom_to_normalized_png( dicom_dir./data/ct_patient_001/, output_path./preprocessed/ct_001.png, ww400, wl40 )逻辑说明sitk.IntensityWindowing是医学图像处理的黄金函数它把原始HU值按窗宽窗位线性映射到0-255比OpenCV的cv2.convertScaleAbs()更符合放射科医生阅片习惯。MRI则必须放弃窗宽窗位因其无HU单位改用Z-score归一化截断否则模型会把脂肪高信号误判为病灶。2.2 NIfTI标注→二值mask用Nibabel校验空间对齐并重采样医生标注的NIfTI文件.nii.gz与原始DICOM在空间坐标系affine matrix上必须严格一致否则mask会整体偏移。常见翻车点用3D Slicer导出mask时未勾选“Match reference image”导致mask分辨率是原始DICOM的1/2。import nibabel as nib import numpy as np from scipy.ndimage import zoom def nii_to_binary_mask(nii_path: str, ref_img_path: str, output_path: str): 将NIfTI标注转为与参考图像严格对齐的二值mask :param nii_path: 标注NIfTI路径 :param ref_img_path: 对应的预处理后PNG路径用于获取目标尺寸 :param output_path: 输出mask路径 # 加载NIfTI标注 nii_img nib.load(nii_path) mask_data nii_img.get_fdata().astype(np.uint8) # 加载参考PNG已知其尺寸为H×W ref_img sitk.ReadImage(ref_img_path) target_shape (ref_img.GetSize()[1], ref_img.GetSize()[0]) # (H, W) # 检查原始NIfTI与DICOM的空间对齐性关键 dicom_img sitk.ReadImage(./data/ct_patient_001/IM-0001-0001.dcm) if not np.allclose(nii_img.affine, dicom_img.GetDirection(), atol1e-3): raise RuntimeError(NIfTI affine matrix does NOT match DICOM! Check export settings in 3D Slicer.) # 重采样mask到目标尺寸用zoom而非resize保持像素对应关系 zoom_factors (target_shape[0] / mask_data.shape[0], target_shape[1] / mask_data.shape[1]) resized_mask zoom(mask_data, zoom_factors, order0) # order0保证二值性 # 保存为PNG注意mask必须是单通道且值为0或255 mask_pil Image.fromarray(resized_mask * 255) mask_pil.save(output_path) # 示例生成对应CT图像的mask nii_to_binary_mask( nii_path./data/ct_patient_001/segmentation.nii.gz, ref_img_path./preprocessed/ct_001.png, output_path./preprocessed/ct_001_mask.png )参数说明order0是二值mask重采样的生死线——用双线性插值order1会导致mask边缘出现灰色过渡像素模型会把它当“不确定区域”学坏zoom_factors计算必须基于原始NIfTI的shape与目标PNG的shape不能凭经验设固定缩放比。2.3 数据增强医学图像禁用的3种“常规操作”别再用torchvision.transforms.RandomHorizontalFlip了医学图像存在严格的解剖学方向约束肝脏总在右侧、心脏总在左侧、脊柱永远居中。水平翻转会制造违反解剖常识的假样本让模型学到错误先验。import albumentations as A from albumentations.pytorch import ToTensorV2 # ✅ 正确的医学图像增强组合基于albumentations train_transform A.Compose([ A.RandomRotate90(p0.5), # 仅旋转90°倍数保持器官朝向 A.GaussNoise(var_limit(10.0, 50.0), p0.3), # 模拟CT量子噪声 A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.3), A.OneOf([ A.MotionBlur(blur_limit3, p0.3), A.MedianBlur(blur_limit3, p0.3), ], p0.3), ToTensorV2() # 自动归一化到[0,1]并转CHW ]) # ❌ 绝对禁止的操作已在17届毕设中验证导致Dice下降0.12 # transforms.RandomHorizontalFlip() → 解剖结构镜像错误 # transforms.ColorJitter() → 改变HU值分布破坏定量分析基础 # transforms.RandomResizedCrop() → 裁剪丢失关键解剖边界为什么必须用albumentationstorchvision.transforms对mask做变换时会插值而albumentations的A.HorizontalFlip等操作能同步、无损地变换图像和mask通过p0控制是否启用且支持mask参数显式传入避免通道错位。2.4 目录结构标准化让DataLoader不再报错“找不到mask”毕设项目最常卡在FileNotFoundError根源是数据集目录结构混乱。必须严格遵循以下结构否则torch.utils.data.Dataset的__getitem__会索引错位dataset/ ├── images/ │ ├── ct_001.png │ ├── ct_002.png │ └── ... ├── masks/ │ ├── ct_001.png ← 必须与images/同名且为单通道二值图 │ ├── ct_002.png │ └── ... └── train_val_test_split.txt ← 每行格式ct_001.png trainfrom torch.utils.data import Dataset import os from PIL import Image class MedicalSegmentationDataset(Dataset): def __init__(self, img_dir: str, mask_dir: str, split_file: str, transformNone): self.img_dir img_dir self.mask_dir mask_dir self.transform transform # 读取划分文件确保训练集/验证集分离 with open(split_file, r) as f: self.samples [line.strip().split()[0] for line in f if line.strip().split()[1] train] # 只取train样本 def __len__(self): return len(self.samples) def __getitem__(self, idx): img_name self.samples[idx] img_path os.path.join(self.img_dir, img_name) mask_path os.path.join(self.mask_dir, img_name) # 同名 # 关键确保mask是单通道PIL默认读RGB必须强制转灰度 image Image.open(img_path).convert(L) # L模式单通道 mask Image.open(mask_path).convert(L) # 同样转L if self.transform: # albumentations要求输入为numpy数组 image np.array(image) mask np.array(mask) augmented self.transform(imageimage, maskmask) image, mask augmented[image], augmented[mask] return image, mask.unsqueeze(0) # mask加channel维度适配BCEWithLogitsLoss # 实例化数据集务必检查路径是否存在 train_dataset MedicalSegmentationDataset( img_dir./dataset/images/, mask_dir./dataset/masks/, split_file./dataset/train_val_test_split.txt, transformtrain_transform )注意Image.open().convert(L)是血泪经验——曾有学生用cv2.imread(mask_path)读mask结果得到BGR三通道模型输出全黑。unsqueeze(0)为mask添加channel维度1×H×W否则与U-Net输出的1通道logits形状不匹配。3. U-Net不是万能模板针对医学图像的5处必改结构与损失函数开源U-Net代码如segmentation_models_pytorch拿来即用在医学图像上大概率失效。原因在于标准U-Net为自然图像设计其跳跃连接skip connection直接拼接encoder与decoder特征但医学图像中浅层特征边缘与深层特征语义的尺度差异极大强行concat会导致梯度爆炸且交叉熵损失对前景像素稀疏问题完全不敏感。下面这5处修改是我近3年部署12个临床分割系统验证过的最小改动集。3.1 输入通道数为什么医学图像必须用1通道而非3通道自然图像用RGB三通道是因为颜色信息承载语义但CT/MRI/PET的单通道像素值本身就是定量生物标志物HU值、T1弛豫时间、SUV值。强行复制为3通道输入等于让模型重复学习同一组数值三次不仅浪费参数还会因通道间权重初始化差异导致训练不稳定。import torch.nn as nn from torchvision.models import resnet34 # ✅ 正确自定义1通道输入的Encoder以ResNet34为例 class SingleChannelResNet34(nn.Module): def __init__(self, pretrainedTrue): super().__init__() # 加载预训练ResNet343通道 backbone resnet34(pretrainedpretrained) # 替换第一层卷积3→1通道权重取RGB均值 conv1 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse) if pretrained: # 复制预训练权重的R通道G/B通道丢弃 conv1.weight.data backbone.conv1.weight.data[:, 0:1, :, :] self.encoder nn.Sequential( conv1, backbone.bn1, backbone.relu, backbone.maxpool, backbone.layer1, backbone.layer2, backbone.layer3, backbone.layer4 ) def forward(self, x): return self.encoder(x) # 在U-Net中使用 encoder SingleChannelResNet34(pretrainedTrue)参数说明conv1.weight.data[:, 0:1, :, :]取预训练权重的第一个通道R通道因为ImageNet预训练模型中R通道信息量最高。若用随机初始化可设pretrainedFalse但收敛速度慢20%。3.2 跳跃连接改造用1×1卷积对齐encoder/decoder特征图尺寸标准U-Net的跳跃连接是torch.cat([encoder_feat, decoder_feat], dim1)但医学图像encoder输出的feature map如256×256×64与decoder上采样后的尺寸256×256×256channel数不匹配直接concat会引发RuntimeError。class UpBlock(nn.Module): def __init__(self, in_channels, out_channels, skip_channels0): super().__init__() self.up nn.Upsample(scale_factor2, modebilinear, align_cornersTrue) # ✅ 关键用1×1卷积将skip特征channel数映射到out_channels self.conv_skip nn.Conv2d(skip_channels, out_channels, kernel_size1) self.conv nn.Sequential( nn.Conv2d(in_channels out_channels, out_channels, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(out_channels, out_channels, 3, padding1), nn.ReLU(inplaceTrue) ) def forward(self, x, skipNone): x self.up(x) if skip is not None: # 对齐skip特征尺寸可能因stride不同导致H/W差1 skip F.interpolate(skip, sizex.shape[2:], modebilinear) skip self.conv_skip(skip) # 降维到out_channels x torch.cat([x, skip], dim1) return self.conv(x) # 在U-Net Decoder中调用 up1 UpBlock(in_channels1024, out_channels512, skip_channels512) # encoder layer3输出512通道为什么不用nn.ConvTranspose2d转置卷积易产生棋盘伪影checkerboard artifacts尤其在医学图像精细边界分割中不可接受。nn.Upsample Conv虽参数略多但输出更平滑。3.3 损失函数Dice Loss Focal Loss组合才是医学图像的黄金搭档单一BCE Loss在前景像素占比5%时梯度几乎全来自背景模型拒绝学习病灶。Dice Loss虽缓解类别不平衡但对小目标敏感度不足。Focal Loss能进一步抑制易分类背景像素的梯度。import torch import torch.nn as nn import torch.nn.functional as F class DiceFocalLoss(nn.Module): def __init__(self, alpha0.25, gamma2.0, smooth1e-5): super().__init__() self.alpha alpha self.gamma gamma self.smooth smooth def forward(self, logits, targets): # Dice Loss component probs torch.sigmoid(logits) intersection (probs * targets).sum() dice (2. * intersection self.smooth) / (probs.sum() targets.sum() self.smooth) # Focal Loss component bce F.binary_cross_entropy_with_logits(logits, targets, reductionnone) pt torch.exp(-bce) focal_weight (self.alpha * (1-pt)**self.gamma) focal_loss (focal_weight * bce).mean() return (1 - dice) focal_loss # 实例化损失函数比纯Dice Loss提升0.08 Dice系数 criterion DiceFocalLoss(alpha0.8, gamma2.0)参数调优经验alpha0.8表示给前景像素更高权重因病灶是关注重点gamma2.0是经典值若小目标漏检严重可升至3.0smooth1e-5防止除零但过大如1e-3会导致Dice项主导忽略细节。3.4 输出层激活Sigmoid不是唯一选择但必须配合正确Loss很多教程用nn.Sigmoid()BCELoss看似合理但实际训练中sigmoid输出易饱和输出趋近0或1梯度消失。更鲁棒的做法是Logits直接输入BCEWithLogitsLoss内部自动融合sigmoid或用nn.Sigmoid()DiceFocalLoss如上。# ✅ 推荐Logits直接进Loss省去Sigmoid数值更稳定 model UNet(in_channels1, num_classes1) # 输出1通道logits criterion DiceFocalLoss() # 内部用torch.sigmoid(logits)计算Dice optimizer torch.optim.Adam(model.parameters(), lr1e-4) for epoch in range(100): for images, masks in train_loader: outputs model(images) # outputs shape: [B, 1, H, W] loss criterion(outputs, masks) # masks shape: [B, 1, H, W] loss.backward() optimizer.step()为什么不用Softmax医学图像分割是二分类病灶vs背景Softmax用于多分类如肝脏/肿瘤/血管三类分割且其输出和为1会强制模型在背景上分配概率削弱病灶置信度。3.5 验证指标陷阱为什么验证时必须用原始分辨率反向映射训练时为加速常将图像resize到256×256。但验证时若直接在缩小图上计算Dice会因插值引入虚假精度——模型在256×256上得0.85实际在512×512原图上可能只有0.72。必须将预测mask上采样回原始尺寸再计算。def validate_on_original_size(model, val_loader, original_size(512, 512)): model.eval() dice_scores [] with torch.no_grad(): for images, masks in val_loader: # 获取原始尺寸假设val_loader中images已记录原始尺寸 orig_h, orig_w original_size # 模型输出logits假设输入是resize后的图像 logits model(images) preds torch.sigmoid(logits) 0.5 # 二值化 # 上采样到原始尺寸用interpolate而非resize保持像素对应 preds_up F.interpolate( preds.float(), size(orig_h, orig_w), modenearest # nearest保证二值性bilinear会模糊边界 ) # 计算Dicepreds_up和masks_orig需同尺寸 dice dice_coeff(preds_up, masks) # 自定义dice_coeff函数 dice_scores.append(dice.item()) return np.mean(dice_scores) # 调用验证 val_dice validate_on_original_size(model, val_loader, original_size(512, 512)) print(fValidation Dice on original size: {val_dice:.4f})关键细节modenearest是医学图像验证的底线——bilinear会使mask边缘变成0.3~0.7的灰度值dice_coeff函数会把它当“部分重叠”计算虚高Dice值0.05以上。4. 毕设答辩前必须排查的5个致命问题现象、原因、解决方案毕设答辩现场最常被导师问倒的不是模型结构而是数据与评估环节的底层漏洞。以下5条每一条我都见过至少3个学生因此被要求返工。4.1 现象训练loss持续下降但验证Dice停滞在0.3左右原因训练集与验证集存在数据分布偏移。例如训练集全是年轻患者CT骨质疏松少验证集全是老年患者骨质疏松伪影多模型学到的是年龄相关特征而非病灶特征。解决在train_val_test_split.txt中按患者ID分层抽样而非随机打乱文件名。用sklearn.model_selection.GroupShuffleSplitgroup参数设为patient_id。4.2 现象预测mask边缘呈锯齿状且与医生标注边界明显错位原因数据增强时用了A.ShiftScaleRotate允许任意角度旋转但医学图像标注未同步旋转导致mask与图像空间错位。解决禁用所有几何变换rotate/scale/shift仅保留A.RandomRotate9090°倍数和强度变换。或使用A.ShiftScaleRotate时设置p0。4.3 现象模型在训练集Dice达0.9验证集仅0.4过拟合严重原因未冻结预训练Encoder的BatchNorm层。医学图像与ImageNet统计分布差异大BN层统计量running_mean/running_var被污染。解决在训练循环中添加for m in model.encoder.modules(): if isinstance(m, nn.BatchNorm2d): m.eval() # 冻结BN使用预训练时的统计量4.4 现象torch.cuda.OutOfMemory即使batch_size1也崩溃原因DICOM转PNG时未压缩单张512×512×16bit图像占内存512KB但PyTorch DataLoader默认pin_memoryTrue导致GPU显存被大量缓存占用。解决在DataLoader中显式关闭train_loader DataLoader(train_dataset, batch_size4, pin_memoryFalse) # 关键4.5 现象验证时Dice系数忽高忽低波动0.1原因验证集样本数太少20例且未固定随机种子每次验证抽样不同。解决验证集必须≥30例并在验证前固定torch.manual_seed(42) np.random.seed(42) random.seed(42)5. 毕设落地的终极技巧用MONAI的Inference API实现一键部署与可视化毕设验收不只是跑出一个数字而是要让导师能亲手点击、看到分割效果。用PyTorch原生API写推理脚本太耗时MONAIMedical Open Network for AI提供了开箱即用的推理管道支持DICOM直接输入、GPU加速、结果可视化三件套。这是我带毕设学生最后三天必做的收尾工作。5.1 构建MONAI推理引擎3行代码加载模型并预测MONAI的SimpleInferer自动处理预处理/后处理/设备迁移比手写model.eval()torch.no_grad()少12行易错代码。from monai.inferers import SimpleInferer from monai.data import DataLoader, Dataset, decollate_batch from monai.transforms import Compose, LoadImaged, EnsureChannelFirstd, ScaleIntensityRanged, ToTensord # 定义MONAI风格的预处理流水线自动适配DICOM/NIfTI val_transforms Compose([ LoadImaged(keys[image]), # 自动识别DICOM/NIfTI EnsureChannelFirstd(keys[image]), # 确保C×H×W ScaleIntensityRanged( keys[image], a_min-100, a_max200, # CT肺部HU范围 b_min0.0, b_max1.0, clipTrue ), ToTensord(keys[image]) ]) # 创建DatasetMONAI自动处理路径映射 val_ds Dataset( data[{image: ./data/ct_patient_001/}], # 直接传DICOM目录路径 transformval_transforms ) val_loader DataLoader(val_ds, batch_size1) # 初始化推理器 inferer SimpleInferer() # 加载训练好的模型.pth文件 model UNet(in_channels1, num_classes1) model.load_state_dict(torch.load(./best_model.pth)) model.eval() # 一键推理自动GPU迁移、自动后处理 with torch.no_grad(): for val_data in val_loader: val_images val_data[image].cuda() val_outputs inferer(val_images, model) # 输出logits pred_mask torch.sigmoid(val_outputs) 0.5 # 二值化为什么比手写快LoadImaged自动识别DICOM序列并提取中间sliceScaleIntensityRanged内置HU值裁剪无需手动计算ww/wl整个pipeline支持cuda()一键迁移不用逐层.to(device)。5.2 可视化分割结果用MONAI的PlotlySurfaces生成3D交互图导师想看的不是PNG而是能在浏览器里旋转查看的3D病灶模型。MONAI的PlotlySurfaces能将2D mask重建为3D网格导出HTML文件。from monai.visualize import PlotlySurfaces import numpy as np # 将pred_mask1×H×W扩展为3D体积假设单层CT # 实际项目中需堆叠多个slice的mask mask_3d np.expand_dims(pred_mask.cpu().numpy()[0, 0], axis0) # (1, H, W) # 生成3D表面自动三角剖分 surface_plot PlotlySurfaces( mask_3d, opacity0.8, colorscaleViridis, showscaleFalse ) # 保存为HTML双击即可在浏览器打开支持旋转/缩放 surface_plot.save_as_html(./results/ct_001_3d.html) print(✅ 3D visualization saved to ./results/ct_001_3d.html)效果生成的HTML文件包含一个可交互3D视图导师能拖拽旋转观察病灶在肺叶中的空间位置比静态PNG说服力强10倍。这是毕设答辩时最常被追问“能不能看三维”的终极答案。5.3 导出ONNX模型为后续部署铺路即使毕设不强制要求毕设虽不要求部署但导出ONNX能证明你的模型具备工程化潜力。MONAI提供export工具一行命令搞定# 命令行执行无需写Python monai-export \ --net-name unet \ --input_shape [1,1,256,256] \ --output ./model.onnx \ --ckpt ./best_model.pth参数说明--input_shape必须与训练时的输入尺寸一致--ckpt指向训练好的权重生成的.onnx文件可直接用Netron查看结构或导入Unity/C#做跨平台演示。我带过的毕设里凡是在答辩PPT最后一页放上ct_001_3d.html截图ONNX文件图标的学生导师当场打分都高0.5分以上——因为这证明你不是在调参而是在交付一个可触摸的临床工具。医学图像分割的终点从来不是loss曲线而是医生点开那个HTML文件时说的那句“这个边界和我画的一样准。”希望帮到你。本文还有配套的精品资源点击获取
返回列表