『医学影像』基于Unet+++实现脊柱MRI定位(上)

本文介绍基于Unet+++实现脊柱MRI定位的项目。因手动选择锥体截面耗时易错,项目将3D数据映射为2D,用深度学习定位L3水平中间轴向切片。处理数据集为PNG格式,定义网络、数据读取类,经训练和验证,测试集平均定位误差为4.0。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

『医学影像』基于unet+++实现脊柱mri定位(上) - 创想鸟

基于Unet+++实现脊柱MRI定位-(上)

目前AIstudio已经有许多基于Unet的分割项目,本项目主要介绍分割网络的另外的应用场景,希望对大家的研究有所启发。

0.研究动机

在医学领域,经常需要分析患某种疾病后身体脂肪含量的变化,一般通过选择某个锥体的截面来估计全身的脂肪含量。 常规的方法是通过手动从几百张影像中选择需要的切片(一般为L3),这种方法即耗时又枯燥,稍不注意还容易出错。

在选择到目标切片后,随后进行手动分割,然后使用相关公式估计全身的脂肪含量。

切片选择相关的研究大部分都是在3D数据上对所有的锥体进行标注,但是这个任务中不需要其他的锥体的具体位置,而且3D数据对设备的要求更高。

因此,目前的一个解决方案是通过将三维数据映射使用MLP映射到二维,然后使用深度学习进行定位。

一个经典的解决方案如下

『医学影像』基于Unet+++实现脊柱MRI定位(上) - 创想鸟        

1.项目介绍

计算机断层扫描(CT)成像广泛用于研究身体成分,即肌肉和脂肪组织的比例,应用于营养或化疗剂量设计等领域。

特别是,来自固定位置的轴向CT切片通常用于身体成分分析。然而,如果手动进行,从数百张切片中手动选择是非常繁琐的操作。

本项目的目的是从全身或部分身体扫描体积中自动找到L3水平的中间轴向切片。

2.数据集介绍

使用公开数据集—磁共振图像脊柱结构多类别三维自动分割数据集,该数据集是一个分割数据集,数据格式是nii.gz。分割磁共振T2腰椎矢状位,加背景一共20类。

椎体有S、L5、L4、L3、L2、L1、T12、T11、T10、T9,椎间盘有L5/S, L4/L5, L3/L4, L2/L3, L1/L2, T12/L1, T11/T12, T10/T11, T9/T10

我们对该数据集进行二次处理,包括MLP,剪裁等,建立自己的实验数据集。

『医学影像』基于Unet+++实现脊柱MRI定位(上) - 创想鸟        

3.代码实现

3.1 解压数据并导入常用库

In [ ]

# 数据集解压#!unzip  -o data/data81211/train.zip -d /home/aistudio/work/

   In [ ]

#安装 nii处理工具  SimpleITK 和分割工具paddleSeg!pip install SimpleITK!pip install paddleseg!pip install nibabel

   In [ ]

#导入常用库import osimport randomimport numpy as npimport matplotlib.pyplot as pltfrom random import shuffleimport cv2import paddlefrom PIL import Imageimport shutilimport reimport globimport reimport SimpleITK as sitk

   

3.2 将数据处理为PNG格式

使用分割的思路来解决定位问题,多次实验将目标位置宽度设置为7个像素效果最佳。

slices的选择与窗宽窗位需要自己根据数据调整

In [ ]

from PIL import Imagedef read_intensity(path):    sitkImage = sitk.ReadImage(path)    intensityWindowingFilter = sitk.IntensityWindowingImageFilter()    #转换成0到255之间    intensityWindowingFilter.SetOutputMaximum(255)    intensityWindowingFilter.SetOutputMinimum(0)    if 'mask' not in path:        #调窗宽窗位        intensityWindowingFilter.SetWindowMaximum(1900)        intensityWindowingFilter.SetWindowMinimum(-300)    sitkImage = intensityWindowingFilter.Execute(sitkImage)    return sitkImagefilename = r'data//Data_L3Location//'if  not os.path.exists(filename):    os.mkdir(filename)path_ ='work/train/MR/*.nii.gz'dcm_list_ = glob.glob(path_)s_s = 4 # 开始slices位置s_e = 6 # 结束slices位置idx = 0for i,_ in enumerate(dcm_list_):    item = dcm_list_[i]    NUM = re.findall("d+",item)[0]    print(i,idx)    path_mri  ='work/train/MR/Case' + str(NUM) + '.nii.gz'    path_mask ='work/train/Mask/mask_case' + str(NUM) + '.nii.gz'    mri = read_intensity(path_mri)    mask = read_intensity(path_mask)    npdata = sitk.GetArrayFromImage(mri)    npmask = sitk.GetArrayFromImage(mask)    npdata = cv2.flip(np.transpose(npdata[:,:,:],(1,2,0)),0)    npmask = cv2.flip(np.transpose(npmask[:,:,:],(1,2,0)),0)    h,w = np.max(npdata[:,:,s_s:s_e],2).shape    if h<768 or w<696:        continue    else:        scale = 0.3        npdata[:,:int(scale*npdata.shape[1]),:] = 0        npdata[:,int((1-scale)*npdata.shape[1]):,:] = 0        npdata_max = np.max(npdata[:,:,s_s:s_e],2) # 最大值压缩        npdata_mean = np.mean(npdata[:,:,s_s:s_e],2) # 均值压缩        npdata_mix = 0.5*(npdata_max+npdata_mean) # 混合压缩        npmask_ = np.max(npmask[:,:,s_s:s_e],2)        npmask_13 = npmask_.copy()        npmask_14 = npmask_.copy()        # 13 / 14  L3        npmask_13[npmask_ != 13] = 0        npmask_14[npmask_ != 14] = 0        npmask_13[npmask_13 == 13] = 255        npmask_14[npmask_14 == 14] = 255        mid_13 = np.where(np.max(npmask_13,1) == 255)[0].mean() # 获取13的中间行索引        mid_14 = np.where(np.max(npmask_14,1) == 255)[0].mean() # 获取14的中间行索引        mid_index = int((mid_13+mid_14)*0.5) # 获取 L3锥体的中间行索引        # 对数据进行截断        npdata_max = npdata_max[:768,184:696] # 880/2 = 440 - 256 = 184 目的是取中间的512列        npdata_mix = npdata_mix[:768,184:696] # 880/2 = 440 - 256 = 184 目的是取中间的512列        npdata_mean = npdata_mean[:768,184:696] # 880/2 = 440 - 256 = 184 目的是取中间的512列        mask = np.zeros_like(npdata_max)        mask[mid_index-3:mid_index+3,int(scale*mask.shape[1]):int((1-scale)*mask.shape[1])] = 255 # 标注 L3锥体的中间位置                # 对数据两侧进行切除处理        img_ma = Image.fromarray(np.uint8(npdata_max))        img_mi = Image.fromarray(np.uint8(npdata_mix))        img_me = Image.fromarray(np.uint8(npdata_mean))        img_la = Image.fromarray(np.uint8(mask))        img_ma.save(filename+'max_'+str(idx) +'.png')        img_mi.save(filename+'mix_'+str(idx) +'.png')        img_me.save(filename+'mean_'+str(idx) +'.png')        img_la.save(filename+'label_'+str(idx) +'.png')        idx = idx+1

   

3.3 定义数据读取类

训练集与测试集比例为8:2In [ ]

import paddlefrom paddle.io import Datasetimport paddleseg.transforms as Timport matplotlib.image as mpimg # mpimg 用于读取图片import numpy as np# 重写数据读取类class MRILocationDataset(Dataset):    def __init__(self,mode = 'train',transform =None):               label_path_ ='data/Data_L3Location/label_*.png'        self.png_list_ = glob.glob(label_path_)        self.transforms = transform        self.mode = mode        # 选择前80%训练,后20%测试        if self.mode == 'train':            self.png_list_ = self.png_list_[:int(0.8*len(self.png_list_))]        else:            self.png_list_ = self.png_list_[int(0.8*len(self.png_list_)):]    def __getitem__(self, index):        item = self.png_list_[index]        mask = mpimg.imread(item) # 读取和代码处于同一目录下的 lena.png        mix_ = mpimg.imread(item.replace('label','mix'))         max_ = mpimg.imread(item.replace('label','max'))         mean_ = mpimg.imread(item.replace('label','mean'))         mask = np.expand_dims(mask, axis=0)        mix_ = np.expand_dims(mix_, axis=0)        max_ = np.expand_dims(max_, axis=0)        mean_ = np.expand_dims(mean_, axis=0)        data = np.concatenate((mix_,max_,mean_),axis=0)        if self.transforms:            data ,mask= self.transforms(data,mask)                    return data ,mask    def __len__(self):        return len(self.png_list_)

   In [ ]

# 预览数据dataset = MRILocationDataset(mode='train')print('=============train dataset=============')imga, imgb = dataset[4]print(imga.shape,imgb.shape)imga = imga[0]*255imga = Image.fromarray(imga)#当要保存的图片为灰度图像时,灰度图像的 numpy 尺度是 [1, h, w]。需要将 [1, h, w] 改变为 [h, w]imgb = np.squeeze(imgb)plt.figure(figsize=(12, 6))plt.subplot(1,2,1),plt.xticks([]),plt.yticks([]),plt.imshow(imga)plt.subplot(1,2,2),plt.xticks([]),plt.yticks([]),plt.imshow(imgb)plt.show()

       

=============train dataset=============(3, 768, 512) (1, 768, 512)

       

               

3.4 定义unet+++网络

简介

UNet的发展

2006年Hinton大神提出了一种encoder-decoder结构,当时这个encoder-decoder结构提出的主要作用并不是分割,而是压缩图像和去噪声。输入是一幅图,经过下采样的编码,得到一串比原先图像更小的特征,相当于压缩,然后再经过一个解码,理想状况就是能还原到原来的图像。而在2015,基于此拓扑结构的FCN和UNet相继提出,其中UNet的对称结构简单易懂,效果还好,就成为了许多网络改进的范本之一。

医真AI+开放平台 医真AI+开放平台

医真AI+ 医学AI开放平台

医真AI+开放平台 52 查看详情 医真AI+开放平台

来源

ICASSP 2020 paper 《UNet 3+: A full-scale connected unet for medical image segmentation》

设计特点

全尺度连接:

为了弥补UNet和UNet++不能精确分割图像中器官的位置和边界,UNet3+中每一个解码器都结合了全部编码器的特征,这些不同尺度的特征能够获取细粒度的细节和粗粒度的语义。UNet 3+中的每一个解码器层都融合了来自编码器中的小尺度和同尺度的特征图,以及来自解码器的da尺度的特征图,这些特征图捕获了全尺度下的细粒度语义和粗粒度语义。下图表明了第三层解码器的特征图如何构造

『医学影像』基于Unet+++实现脊柱MRI定位(上) - 创想鸟                        

全尺度监督:

在UNet++中,已经实现了深度监督。它对生成的全分辨率特征图进行操作,即 X0,1 、X0,2、 X0,3 、X0,4后面加一个1×1的卷积核,相当于监督每个分支的UNet的输出。与UNet++对每个嵌套的子网络进行监督不同的是,在UNet3+中每一个解码器模块都有一个输出,与ground truth进行比较计算loss,从而实现全尺度的监督

『医学影像』基于Unet+++实现脊柱MRI定位(上) - 创想鸟                        

分类引导模块:

为了防止非器官图像的过度分割,和提高模型的分割精度,作者通过添加一个额外的分类任务来预测输入图像是否有器官,从而实现更精准的分割。具体就是利用最丰富的语义信息,分类结果可以进一步指导每一个切分侧边输出两个步骤。首先,在argmax函数的帮助下,将二维张量转化为{0,1}的单个输出,表示有/没有目标。随后将单个分类输出与侧分割输出相乘。由于二值分类任务的简单性,该模块通过优化二值交叉熵损失函数,轻松获得准确的分类结果,实现了对非目标图像过分割的指导。

『医学影像』基于Unet+++实现脊柱MRI定位(上) - 创想鸟                        

(图源知乎:玖零猴,侵删)

网络结构

与UNet和UNet++相比,UNet3+结合了多尺度特征,重新设计了跳跃连接,并利用多尺度的深度监督,UNet3+提供更少的参数,但可以产生更准确的位置感知和边界增强的分割图

『医学影像』基于Unet+++实现脊柱MRI定位(上) - 创想鸟                

说明

pytorch版本中有UNet3+、用到了深度监督的UNet3+以及分类指导模块的UNet3+,都以在unet.py中转为paddle的版本。具体介绍还是请移步知乎:UNet3+(UNet+++)论文解读

参考项目 https://aistudio.baidu.com/aistudio/projectdetail/1555546In [ ]

import paddleimport paddle.nn as nnimport paddle.nn.functional as Ffrom paddle.nn import initializerdef init_weights(init_type='kaiming'):    if init_type == 'normal':        return paddle.framework.ParamAttr(initializer=paddle.nn.initializer.Normal())    elif init_type == 'xavier':        return paddle.framework.ParamAttr(initializer=paddle.nn.initializer.XavierNormal())    elif init_type == 'kaiming':        return paddle.framework.ParamAttr(initializer=paddle.nn.initializer.KaimingNormal)    else:        raise NotImplementedError('initialization method [%s] is not implemented' % init_type)class unetConv2(nn.Layer):    def __init__(self, in_size, out_size, is_batchnorm, n=2, ks=3, stride=1, padding=1):        super(unetConv2, self).__init__()        self.n = n        self.ks = ks        self.stride = stride        self.padding = padding        s = stride        p = padding        if is_batchnorm:            for i in range(1, n + 1):                conv = nn.Sequential(nn.Conv2D(in_size, out_size, ks, s, p),                                     nn.BatchNorm(out_size),                                     nn.ReLU(), )                setattr(self, 'conv%d' % i, conv)                in_size = out_size        else:            for i in range(1, n + 1):                conv = nn.Sequential(nn.Conv2D(in_size, out_size, ks, s, p),                                     nn.ReLU(), )                setattr(self, 'conv%d' % i, conv)                in_size = out_size        # initialise the blocks        for m in self.children():            m.weight_attr=init_weights(init_type='kaiming')            m.bias_attr=init_weights(init_type='kaiming')    def forward(self, inputs):        x = inputs        for i in range(1, self.n + 1):            conv = getattr(self, 'conv%d' % i)            x = conv(x)        return x'''    UNet 3+'''class UNet_3Plus(nn.Layer):    def __init__(self, in_channels=3, n_classes=1, is_deconv=True, is_batchnorm=True, end_sigmoid=True):        super(UNet_3Plus, self).__init__()        self.is_deconv = is_deconv        self.in_channels = in_channels        self.is_batchnorm = is_batchnorm        self.end_sigmoid = end_sigmoid        filters = [16, 32, 64, 128, 256]        ## -------------Encoder--------------        self.conv1 = unetConv2(self.in_channels, filters[0], self.is_batchnorm)        self.maxpool1 = nn.MaxPool2D(kernel_size=2)        self.conv2 = unetConv2(filters[0], filters[1], self.is_batchnorm)        self.maxpool2 = nn.MaxPool2D(kernel_size=2)        self.conv3 = unetConv2(filters[1], filters[2], self.is_batchnorm)        self.maxpool3 = nn.MaxPool2D(kernel_size=2)        self.conv4 = unetConv2(filters[2], filters[3], self.is_batchnorm)        self.maxpool4 = nn.MaxPool2D(kernel_size=2)        self.conv5 = unetConv2(filters[3], filters[4], self.is_batchnorm)        ## -------------Decoder--------------        self.CatChannels = filters[0]        self.CatBlocks = 5        self.UpChannels = self.CatChannels * self.CatBlocks        '''stage 4d'''        # h2->320*320, hd4->40*40, Pooling 8 times        self.h2_PT_hd4 = nn.MaxPool2D(8, 8, ceil_mode=True)        self.h2_PT_hd4_conv = nn.Conv2D(filters[0], self.CatChannels, 3, padding=1)        self.h2_PT_hd4_bn = nn.BatchNorm(self.CatChannels)        self.h2_PT_hd4_relu = nn.ReLU()        # h2->160*160, hd4->40*40, Pooling 4 times        self.h2_PT_hd4 = nn.MaxPool2D(4, 4, ceil_mode=True)        self.h2_PT_hd4_conv = nn.Conv2D(filters[1], self.CatChannels, 3, padding=1)        self.h2_PT_hd4_bn = nn.BatchNorm(self.CatChannels)        self.h2_PT_hd4_relu = nn.ReLU()        # h3->80*80, hd4->40*40, Pooling 2 times        self.h3_PT_hd4 = nn.MaxPool2D(2, 2, ceil_mode=True)        self.h3_PT_hd4_conv = nn.Conv2D(filters[2], self.CatChannels, 3, padding=1)        self.h3_PT_hd4_bn = nn.BatchNorm(self.CatChannels)        self.h3_PT_hd4_relu = nn.ReLU()        # h4->40*40, hd4->40*40, Concatenation        self.h4_Cat_hd4_conv = nn.Conv2D(filters[3], self.CatChannels, 3, padding=1)        self.h4_Cat_hd4_bn = nn.BatchNorm(self.CatChannels)        self.h4_Cat_hd4_relu = nn.ReLU()        # hd5->20*20, hd4->40*40, Upsample 2 times        self.hd5_UT_hd4 = nn.Upsample(scale_factor=2, mode='bilinear')  # 14*14        self.hd5_UT_hd4_conv = nn.Conv2D(filters[4], self.CatChannels, 3, padding=1)        self.hd5_UT_hd4_bn = nn.BatchNorm(self.CatChannels)        self.hd5_UT_hd4_relu = nn.ReLU()        # fusion(h2_PT_hd4, h2_PT_hd4, h3_PT_hd4, h4_Cat_hd4, hd5_UT_hd4)        self.conv4d_1 = nn.Conv2D(self.UpChannels, self.UpChannels, 3, padding=1)  # 16        self.bn4d_1 = nn.BatchNorm(self.UpChannels)        self.relu4d_1 = nn.ReLU()        '''stage 3d'''        # h2->320*320, hd3->80*80, Pooling 4 times        self.h2_PT_hd3 = nn.MaxPool2D(4, 4, ceil_mode=True)        self.h2_PT_hd3_conv = nn.Conv2D(filters[0], self.CatChannels, 3, padding=1)        self.h2_PT_hd3_bn = nn.BatchNorm(self.CatChannels)        self.h2_PT_hd3_relu = nn.ReLU()        # h2->160*160, hd3->80*80, Pooling 2 times        self.h2_PT_hd3 = nn.MaxPool2D(2, 2, ceil_mode=True)        self.h2_PT_hd3_conv = nn.Conv2D(filters[1], self.CatChannels, 3, padding=1)        self.h2_PT_hd3_bn = nn.BatchNorm(self.CatChannels)        self.h2_PT_hd3_relu = nn.ReLU()        # h3->80*80, hd3->80*80, Concatenation        self.h3_Cat_hd3_conv = nn.Conv2D(filters[2], self.CatChannels, 3, padding=1)        self.h3_Cat_hd3_bn = nn.BatchNorm(self.CatChannels)        self.h3_Cat_hd3_relu = nn.ReLU()        # hd4->40*40, hd4->80*80, Upsample 2 times        self.hd4_UT_hd3 = nn.Upsample(scale_factor=2, mode='bilinear')  # 14*14        self.hd4_UT_hd3_conv = nn.Conv2D(self.UpChannels, self.CatChannels, 3, padding=1)        self.hd4_UT_hd3_bn = nn.BatchNorm(self.CatChannels)        self.hd4_UT_hd3_relu = nn.ReLU()        # hd5->20*20, hd4->80*80, Upsample 4 times        self.hd5_UT_hd3 = nn.Upsample(scale_factor=4, mode='bilinear')  # 14*14        self.hd5_UT_hd3_conv = nn.Conv2D(filters[4], self.CatChannels, 3, padding=1)        self.hd5_UT_hd3_bn = nn.BatchNorm(self.CatChannels)        self.hd5_UT_hd3_relu = nn.ReLU()        # fusion(h2_PT_hd3, h2_PT_hd3, h3_Cat_hd3, hd4_UT_hd3, hd5_UT_hd3)        self.conv3d_1 = nn.Conv2D(self.UpChannels, self.UpChannels, 3, padding=1)  # 16        self.bn3d_1 = nn.BatchNorm(self.UpChannels)        self.relu3d_1 = nn.ReLU()        '''stage 2d '''        # h2->320*320, hd2->160*160, Pooling 2 times        self.h2_PT_hd2 = nn.MaxPool2D(2, 2, ceil_mode=True)        self.h2_PT_hd2_conv = nn.Conv2D(filters[0], self.CatChannels, 3, padding=1)        self.h2_PT_hd2_bn = nn.BatchNorm(self.CatChannels)        self.h2_PT_hd2_relu = nn.ReLU()        # h2->160*160, hd2->160*160, Concatenation        self.h2_Cat_hd2_conv = nn.Conv2D(filters[1], self.CatChannels, 3, padding=1)        self.h2_Cat_hd2_bn = nn.BatchNorm(self.CatChannels)        self.h2_Cat_hd2_relu = nn.ReLU()        # hd3->80*80, hd2->160*160, Upsample 2 times        self.hd3_UT_hd2 = nn.Upsample(scale_factor=2, mode='bilinear')  # 14*14        self.hd3_UT_hd2_conv = nn.Conv2D(self.UpChannels, self.CatChannels, 3, padding=1)        self.hd3_UT_hd2_bn = nn.BatchNorm(self.CatChannels)        self.hd3_UT_hd2_relu = nn.ReLU()        # hd4->40*40, hd2->160*160, Upsample 4 times        self.hd4_UT_hd2 = nn.Upsample(scale_factor=4, mode='bilinear')  # 14*14        self.hd4_UT_hd2_conv = nn.Conv2D(self.UpChannels, self.CatChannels, 3, padding=1)        self.hd4_UT_hd2_bn = nn.BatchNorm(self.CatChannels)        self.hd4_UT_hd2_relu = nn.ReLU()        # hd5->20*20, hd2->160*160, Upsample 8 times        self.hd5_UT_hd2 = nn.Upsample(scale_factor=8, mode='bilinear')  # 14*14        self.hd5_UT_hd2_conv = nn.Conv2D(filters[4], self.CatChannels, 3, padding=1)        self.hd5_UT_hd2_bn = nn.BatchNorm(self.CatChannels)        self.hd5_UT_hd2_relu = nn.ReLU()        # fusion(h2_PT_hd2, h2_Cat_hd2, hd3_UT_hd2, hd4_UT_hd2, hd5_UT_hd2)        self.Conv2D_1 = nn.Conv2D(self.UpChannels, self.UpChannels, 3, padding=1)  # 16        self.bn2d_1 = nn.BatchNorm(self.UpChannels)        self.relu2d_1 = nn.ReLU()        '''stage 1d'''        # h2->320*320, hd1->320*320, Concatenation        self.h2_Cat_hd1_conv = nn.Conv2D(filters[0], self.CatChannels, 3, padding=1)        self.h2_Cat_hd1_bn = nn.BatchNorm(self.CatChannels)        self.h2_Cat_hd1_relu = nn.ReLU()        # hd2->160*160, hd1->320*320, Upsample 2 times        self.hd2_UT_hd1 = nn.Upsample(scale_factor=2, mode='bilinear')  # 14*14        self.hd2_UT_hd1_conv = nn.Conv2D(self.UpChannels, self.CatChannels, 3, padding=1)        self.hd2_UT_hd1_bn = nn.BatchNorm(self.CatChannels)        self.hd2_UT_hd1_relu = nn.ReLU()        # hd3->80*80, hd1->320*320, Upsample 4 times        self.hd3_UT_hd1 = nn.Upsample(scale_factor=4, mode='bilinear')  # 14*14        self.hd3_UT_hd1_conv = nn.Conv2D(self.UpChannels, self.CatChannels, 3, padding=1)        self.hd3_UT_hd1_bn = nn.BatchNorm(self.CatChannels)        self.hd3_UT_hd1_relu = nn.ReLU()        # hd4->40*40, hd1->320*320, Upsample 8 times        self.hd4_UT_hd1 = nn.Upsample(scale_factor=8, mode='bilinear')  # 14*14        self.hd4_UT_hd1_conv = nn.Conv2D(self.UpChannels, self.CatChannels, 3, padding=1)        self.hd4_UT_hd1_bn = nn.BatchNorm(self.CatChannels)        self.hd4_UT_hd1_relu = nn.ReLU()        # hd5->20*20, hd1->320*320, Upsample 16 times        self.hd5_UT_hd1 = nn.Upsample(scale_factor=16, mode='bilinear')  # 14*14        self.hd5_UT_hd1_conv = nn.Conv2D(filters[4], self.CatChannels, 3, padding=1)        self.hd5_UT_hd1_bn = nn.BatchNorm(self.CatChannels)        self.hd5_UT_hd1_relu = nn.ReLU()        # fusion(h2_Cat_hd1, hd2_UT_hd1, hd3_UT_hd1, hd4_UT_hd1, hd5_UT_hd1)        self.conv1d_1 = nn.Conv2D(self.UpChannels, self.UpChannels, 3, padding=1)  # 16        self.bn1d_1 = nn.BatchNorm(self.UpChannels)        self.relu1d_1 = nn.ReLU()        # output        self.outconv1 = nn.Conv2D(self.UpChannels, n_classes, 3, padding=1)        # initialise weights        for m in self.sublayers ():            if isinstance(m, nn.Conv2D):                m.weight_attr = init_weights(init_type='kaiming')                m.bias_attr = init_weights(init_type='kaiming')            elif isinstance(m, nn.BatchNorm):                m.param_attr =init_weights(init_type='kaiming')                m.bias_attr = init_weights(init_type='kaiming')    def forward(self, inputs):        ## -------------Encoder-------------        h2 = self.conv1(inputs)  # h2->320*320*64        h2 = self.maxpool1(h2)        h2 = self.conv2(h2)  # h2->160*160*128        h3 = self.maxpool2(h2)        h3 = self.conv3(h3)  # h3->80*80*256        h4 = self.maxpool3(h3)        h4 = self.conv4(h4)  # h4->40*40*512        h5 = self.maxpool4(h4)        hd5 = self.conv5(h5)  # h5->20*20*1024        ## -------------Decoder-------------        h2_PT_hd4 = self.h2_PT_hd4_relu(self.h2_PT_hd4_bn(self.h2_PT_hd4_conv(self.h2_PT_hd4(h2))))        h2_PT_hd4 = self.h2_PT_hd4_relu(self.h2_PT_hd4_bn(self.h2_PT_hd4_conv(self.h2_PT_hd4(h2))))        h3_PT_hd4 = self.h3_PT_hd4_relu(self.h3_PT_hd4_bn(self.h3_PT_hd4_conv(self.h3_PT_hd4(h3))))        h4_Cat_hd4 = self.h4_Cat_hd4_relu(self.h4_Cat_hd4_bn(self.h4_Cat_hd4_conv(h4)))        hd5_UT_hd4 = self.hd5_UT_hd4_relu(self.hd5_UT_hd4_bn(self.hd5_UT_hd4_conv(self.hd5_UT_hd4(hd5))))        hd4 = self.relu4d_1(self.bn4d_1(self.conv4d_1(            paddle.concat([h2_PT_hd4, h2_PT_hd4, h3_PT_hd4, h4_Cat_hd4, hd5_UT_hd4], 1)))) # hd4->40*40*UpChannels        h2_PT_hd3 = self.h2_PT_hd3_relu(self.h2_PT_hd3_bn(self.h2_PT_hd3_conv(self.h2_PT_hd3(h2))))        h2_PT_hd3 = self.h2_PT_hd3_relu(self.h2_PT_hd3_bn(self.h2_PT_hd3_conv(self.h2_PT_hd3(h2))))        h3_Cat_hd3 = self.h3_Cat_hd3_relu(self.h3_Cat_hd3_bn(self.h3_Cat_hd3_conv(h3)))        hd4_UT_hd3 = self.hd4_UT_hd3_relu(self.hd4_UT_hd3_bn(self.hd4_UT_hd3_conv(self.hd4_UT_hd3(hd4))))        hd5_UT_hd3 = self.hd5_UT_hd3_relu(self.hd5_UT_hd3_bn(self.hd5_UT_hd3_conv(self.hd5_UT_hd3(hd5))))        hd3 = self.relu3d_1(self.bn3d_1(self.conv3d_1(            paddle.concat([h2_PT_hd3, h2_PT_hd3, h3_Cat_hd3, hd4_UT_hd3, hd5_UT_hd3], 1)))) # hd3->80*80*UpChannels        h2_PT_hd2 = self.h2_PT_hd2_relu(self.h2_PT_hd2_bn(self.h2_PT_hd2_conv(self.h2_PT_hd2(h2))))        h2_Cat_hd2 = self.h2_Cat_hd2_relu(self.h2_Cat_hd2_bn(self.h2_Cat_hd2_conv(h2)))        hd3_UT_hd2 = self.hd3_UT_hd2_relu(self.hd3_UT_hd2_bn(self.hd3_UT_hd2_conv(self.hd3_UT_hd2(hd3))))        hd4_UT_hd2 = self.hd4_UT_hd2_relu(self.hd4_UT_hd2_bn(self.hd4_UT_hd2_conv(self.hd4_UT_hd2(hd4))))        hd5_UT_hd2 = self.hd5_UT_hd2_relu(self.hd5_UT_hd2_bn(self.hd5_UT_hd2_conv(self.hd5_UT_hd2(hd5))))        hd2 = self.relu2d_1(self.bn2d_1(self.Conv2D_1(            paddle.concat([h2_PT_hd2, h2_Cat_hd2, hd3_UT_hd2, hd4_UT_hd2, hd5_UT_hd2], 1)))) # hd2->160*160*UpChannels        h2_Cat_hd1 = self.h2_Cat_hd1_relu(self.h2_Cat_hd1_bn(self.h2_Cat_hd1_conv(h2)))        hd2_UT_hd1 = self.hd2_UT_hd1_relu(self.hd2_UT_hd1_bn(self.hd2_UT_hd1_conv(self.hd2_UT_hd1(hd2))))        hd3_UT_hd1 = self.hd3_UT_hd1_relu(self.hd3_UT_hd1_bn(self.hd3_UT_hd1_conv(self.hd3_UT_hd1(hd3))))        hd4_UT_hd1 = self.hd4_UT_hd1_relu(self.hd4_UT_hd1_bn(self.hd4_UT_hd1_conv(self.hd4_UT_hd1(hd4))))        hd5_UT_hd1 = self.hd5_UT_hd1_relu(self.hd5_UT_hd1_bn(self.hd5_UT_hd1_conv(self.hd5_UT_hd1(hd5))))        hd1 = self.relu1d_1(self.bn1d_1(self.conv1d_1(            paddle.concat([h2_Cat_hd1, hd2_UT_hd1, hd3_UT_hd1, hd4_UT_hd1, hd5_UT_hd1], 1)))) # hd1->320*320*UpChannels        d1 = self.outconv1(hd1)  # d1->320*320*n_classes        if self.end_sigmoid:            out = F.sigmoid(d1)        else:            out = d1        return out

   In [ ]

# 模型可视化import numpyimport paddleunet3p = UNet_3Plus(in_channels=3, n_classes=1)model = paddle.Model(unet3p)model.summary((2,3, 768, 512))

       

--------------------------------------------------------------------------- Layer (type)       Input Shape          Output Shape         Param #    ===========================================================================   Conv2D-2      [[2, 3, 768, 512]]   [2, 16, 768, 512]         448        BatchNorm-1   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-1      [[2, 16, 768, 512]]   [2, 16, 768, 512]          0          Conv2D-3     [[2, 16, 768, 512]]   [2, 16, 768, 512]        2,320       BatchNorm-2   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-2      [[2, 16, 768, 512]]   [2, 16, 768, 512]          0         unetConv2-2    [[2, 3, 768, 512]]   [2, 16, 768, 512]          0         MaxPool2D-1   [[2, 16, 768, 512]]   [2, 16, 384, 256]          0          Conv2D-4     [[2, 16, 384, 256]]   [2, 32, 384, 256]        4,640       BatchNorm-3   [[2, 32, 384, 256]]   [2, 32, 384, 256]         128          ReLU-3      [[2, 32, 384, 256]]   [2, 32, 384, 256]          0          Conv2D-5     [[2, 32, 384, 256]]   [2, 32, 384, 256]        9,248       BatchNorm-4   [[2, 32, 384, 256]]   [2, 32, 384, 256]         128          ReLU-4      [[2, 32, 384, 256]]   [2, 32, 384, 256]          0         unetConv2-3   [[2, 16, 384, 256]]   [2, 32, 384, 256]          0         MaxPool2D-2   [[2, 32, 384, 256]]   [2, 32, 192, 128]          0          Conv2D-6     [[2, 32, 192, 128]]   [2, 64, 192, 128]       18,496       BatchNorm-5   [[2, 64, 192, 128]]   [2, 64, 192, 128]         256          ReLU-5      [[2, 64, 192, 128]]   [2, 64, 192, 128]          0          Conv2D-7     [[2, 64, 192, 128]]   [2, 64, 192, 128]       36,928       BatchNorm-6   [[2, 64, 192, 128]]   [2, 64, 192, 128]         256          ReLU-6      [[2, 64, 192, 128]]   [2, 64, 192, 128]          0         unetConv2-4   [[2, 32, 192, 128]]   [2, 64, 192, 128]          0         MaxPool2D-3   [[2, 64, 192, 128]]    [2, 64, 96, 64]           0          Conv2D-8      [[2, 64, 96, 64]]     [2, 128, 96, 64]       73,856       BatchNorm-7    [[2, 128, 96, 64]]    [2, 128, 96, 64]         512          ReLU-7       [[2, 128, 96, 64]]    [2, 128, 96, 64]          0          Conv2D-9      [[2, 128, 96, 64]]    [2, 128, 96, 64]       147,584      BatchNorm-8    [[2, 128, 96, 64]]    [2, 128, 96, 64]         512          ReLU-8       [[2, 128, 96, 64]]    [2, 128, 96, 64]          0         unetConv2-5    [[2, 64, 96, 64]]     [2, 128, 96, 64]          0         MaxPool2D-4    [[2, 128, 96, 64]]    [2, 128, 48, 32]          0          Conv2D-10     [[2, 128, 48, 32]]    [2, 256, 48, 32]       295,168      BatchNorm-9    [[2, 256, 48, 32]]    [2, 256, 48, 32]        1,024         ReLU-9       [[2, 256, 48, 32]]    [2, 256, 48, 32]          0          Conv2D-11     [[2, 256, 48, 32]]    [2, 256, 48, 32]       590,080     BatchNorm-10    [[2, 256, 48, 32]]    [2, 256, 48, 32]        1,024         ReLU-10      [[2, 256, 48, 32]]    [2, 256, 48, 32]          0         unetConv2-6    [[2, 128, 48, 32]]    [2, 256, 48, 32]          0         MaxPool2D-5   [[2, 16, 768, 512]]    [2, 16, 96, 64]           0          Conv2D-12     [[2, 16, 96, 64]]     [2, 16, 96, 64]         2,320      BatchNorm-11    [[2, 16, 96, 64]]     [2, 16, 96, 64]          64           ReLU-11      [[2, 16, 96, 64]]     [2, 16, 96, 64]           0         MaxPool2D-6   [[2, 32, 384, 256]]    [2, 32, 96, 64]           0          Conv2D-13     [[2, 32, 96, 64]]     [2, 16, 96, 64]         4,624      BatchNorm-12    [[2, 16, 96, 64]]     [2, 16, 96, 64]          64           ReLU-12      [[2, 16, 96, 64]]     [2, 16, 96, 64]           0         MaxPool2D-7   [[2, 64, 192, 128]]    [2, 64, 96, 64]           0          Conv2D-14     [[2, 64, 96, 64]]     [2, 16, 96, 64]         9,232      BatchNorm-13    [[2, 16, 96, 64]]     [2, 16, 96, 64]          64           ReLU-13      [[2, 16, 96, 64]]     [2, 16, 96, 64]           0          Conv2D-15     [[2, 128, 96, 64]]    [2, 16, 96, 64]        18,448      BatchNorm-14    [[2, 16, 96, 64]]     [2, 16, 96, 64]          64           ReLU-14      [[2, 16, 96, 64]]     [2, 16, 96, 64]           0         Upsample-1     [[2, 256, 48, 32]]    [2, 256, 96, 64]          0          Conv2D-16     [[2, 256, 96, 64]]    [2, 16, 96, 64]        36,880      BatchNorm-15    [[2, 16, 96, 64]]     [2, 16, 96, 64]          64           ReLU-15      [[2, 16, 96, 64]]     [2, 16, 96, 64]           0          Conv2D-17     [[2, 80, 96, 64]]     [2, 80, 96, 64]        57,680      BatchNorm-16    [[2, 80, 96, 64]]     [2, 80, 96, 64]          320          ReLU-16      [[2, 80, 96, 64]]     [2, 80, 96, 64]           0         MaxPool2D-8   [[2, 16, 768, 512]]   [2, 16, 192, 128]          0          Conv2D-18    [[2, 16, 192, 128]]   [2, 16, 192, 128]        2,320      BatchNorm-17   [[2, 16, 192, 128]]   [2, 16, 192, 128]         64           ReLU-17     [[2, 16, 192, 128]]   [2, 16, 192, 128]          0         MaxPool2D-9   [[2, 32, 384, 256]]   [2, 32, 192, 128]          0          Conv2D-19    [[2, 32, 192, 128]]   [2, 16, 192, 128]        4,624      BatchNorm-18   [[2, 16, 192, 128]]   [2, 16, 192, 128]         64           ReLU-18     [[2, 16, 192, 128]]   [2, 16, 192, 128]          0          Conv2D-20    [[2, 64, 192, 128]]   [2, 16, 192, 128]        9,232      BatchNorm-19   [[2, 16, 192, 128]]   [2, 16, 192, 128]         64           ReLU-19     [[2, 16, 192, 128]]   [2, 16, 192, 128]          0         Upsample-2     [[2, 80, 96, 64]]    [2, 80, 192, 128]          0          Conv2D-21    [[2, 80, 192, 128]]   [2, 16, 192, 128]       11,536      BatchNorm-20   [[2, 16, 192, 128]]   [2, 16, 192, 128]         64           ReLU-20     [[2, 16, 192, 128]]   [2, 16, 192, 128]          0         Upsample-3     [[2, 256, 48, 32]]   [2, 256, 192, 128]         0          Conv2D-22    [[2, 256, 192, 128]]  [2, 16, 192, 128]       36,880      BatchNorm-21   [[2, 16, 192, 128]]   [2, 16, 192, 128]         64           ReLU-21     [[2, 16, 192, 128]]   [2, 16, 192, 128]          0          Conv2D-23    [[2, 80, 192, 128]]   [2, 80, 192, 128]       57,680      BatchNorm-22   [[2, 80, 192, 128]]   [2, 80, 192, 128]         320          ReLU-22     [[2, 80, 192, 128]]   [2, 80, 192, 128]          0        MaxPool2D-10   [[2, 16, 768, 512]]   [2, 16, 384, 256]          0          Conv2D-24    [[2, 16, 384, 256]]   [2, 16, 384, 256]        2,320      BatchNorm-23   [[2, 16, 384, 256]]   [2, 16, 384, 256]         64           ReLU-23     [[2, 16, 384, 256]]   [2, 16, 384, 256]          0          Conv2D-25    [[2, 32, 384, 256]]   [2, 16, 384, 256]        4,624      BatchNorm-24   [[2, 16, 384, 256]]   [2, 16, 384, 256]         64           ReLU-24     [[2, 16, 384, 256]]   [2, 16, 384, 256]          0         Upsample-4    [[2, 80, 192, 128]]   [2, 80, 384, 256]          0          Conv2D-26    [[2, 80, 384, 256]]   [2, 16, 384, 256]       11,536      BatchNorm-25   [[2, 16, 384, 256]]   [2, 16, 384, 256]         64           ReLU-25     [[2, 16, 384, 256]]   [2, 16, 384, 256]          0         Upsample-5     [[2, 80, 96, 64]]    [2, 80, 384, 256]          0          Conv2D-27    [[2, 80, 384, 256]]   [2, 16, 384, 256]       11,536      BatchNorm-26   [[2, 16, 384, 256]]   [2, 16, 384, 256]         64           ReLU-26     [[2, 16, 384, 256]]   [2, 16, 384, 256]          0         Upsample-6     [[2, 256, 48, 32]]   [2, 256, 384, 256]         0          Conv2D-28    [[2, 256, 384, 256]]  [2, 16, 384, 256]       36,880      BatchNorm-27   [[2, 16, 384, 256]]   [2, 16, 384, 256]         64           ReLU-27     [[2, 16, 384, 256]]   [2, 16, 384, 256]          0          Conv2D-29    [[2, 80, 384, 256]]   [2, 80, 384, 256]       57,680      BatchNorm-28   [[2, 80, 384, 256]]   [2, 80, 384, 256]         320          ReLU-28     [[2, 80, 384, 256]]   [2, 80, 384, 256]          0          Conv2D-30    [[2, 16, 768, 512]]   [2, 16, 768, 512]        2,320      BatchNorm-29   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-29     [[2, 16, 768, 512]]   [2, 16, 768, 512]          0         Upsample-7    [[2, 80, 384, 256]]   [2, 80, 768, 512]          0          Conv2D-31    [[2, 80, 768, 512]]   [2, 16, 768, 512]       11,536      BatchNorm-30   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-30     [[2, 16, 768, 512]]   [2, 16, 768, 512]          0         Upsample-8    [[2, 80, 192, 128]]   [2, 80, 768, 512]          0          Conv2D-32    [[2, 80, 768, 512]]   [2, 16, 768, 512]       11,536      BatchNorm-31   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-31     [[2, 16, 768, 512]]   [2, 16, 768, 512]          0         Upsample-9     [[2, 80, 96, 64]]    [2, 80, 768, 512]          0          Conv2D-33    [[2, 80, 768, 512]]   [2, 16, 768, 512]       11,536      BatchNorm-32   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-32     [[2, 16, 768, 512]]   [2, 16, 768, 512]          0         Upsample-10    [[2, 256, 48, 32]]   [2, 256, 768, 512]         0          Conv2D-34    [[2, 256, 768, 512]]  [2, 16, 768, 512]       36,880      BatchNorm-33   [[2, 16, 768, 512]]   [2, 16, 768, 512]         64           ReLU-33     [[2, 16, 768, 512]]   [2, 16, 768, 512]          0          Conv2D-35    [[2, 80, 768, 512]]   [2, 80, 768, 512]       57,680      BatchNorm-34   [[2, 80, 768, 512]]   [2, 80, 768, 512]         320          ReLU-34     [[2, 80, 768, 512]]   [2, 80, 768, 512]          0          Conv2D-36    [[2, 80, 768, 512]]    [2, 1, 768, 512]         721      ===========================================================================Total params: 1,693,537Trainable params: 1,687,009Non-trainable params: 6,528---------------------------------------------------------------------------Input size (MB): 9.00Forward/backward pass size (MB): 8980.50Params size (MB): 6.46Estimated Total Size (MB): 8995.96---------------------------------------------------------------------------

       

{'total_params': 1693537, 'trainable_params': 1687009}

               

3.5 开始训练

In [14]

model =  UNet_3Plus(in_channels=3, n_classes=1)#SEUNet(3,1)# 开启模型训练模式model.train()# 定义优化算法,使用随机梯度下降SGD,学习率设置为0.01scheduler = paddle.optimizer.lr.StepDecay(learning_rate=0.01, step_size=30, gamma=0.1, verbose=False)optimizer = paddle.optimizer.Adam(learning_rate=scheduler, parameters=model.parameters())EPOCH_NUM = 60  # 设置外层循环次数BATCH_SIZE = 2  # 设置batch大小train_dataset =  MRILocationDataset(mode='train')test_dataset =  MRILocationDataset(mode='test')# 使用paddle.io.DataLoader 定义DataLoader对象用于加载Python生成器产生的数据,data_loader = paddle.io.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=False)test_data_loader = paddle.io.DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)loss_BCEloss = paddle.nn.BCELoss()# 定义外层循环for epoch_id in range(EPOCH_NUM):    # 定义内层循环    for iter_id, data in enumerate(data_loader()):        x, y = data # x 为数据 ,y 为标签        # 将numpy数据转为飞桨动态图tensor形式        x = paddle.to_tensor(x,dtype='float32')        y = paddle.to_tensor(y,dtype='float32')        # 前向计算        predicts = model(x)        # 计算损失        loss = loss_BCEloss(predicts, y)        # 清除梯度        optimizer.clear_grad()        # 反向传播        loss.backward()        # 最小化loss,更新参数        optimizer.step()    scheduler.step()    print("epoch: {}, iter: {}, loss is: {}".format(epoch_id+1, iter_id+1, loss.numpy()))# 保存模型参数,文件名为Unet_model.pdparamspaddle.save(model.state_dict(), 'work/Unet3p_model.pdparams')print("模型保存成功,模型参数保存在Unet3p_model.pdparams中")

   

3.6 模型验证

In [15]

import paddle# 模型验证Error = []# 清理缓存print("开始测试")# 用于加载之前的训练过的模型参数para_state_dict = paddle.load('work/Unet3p_model.pdparams')model =  UNet_3Plus(in_channels=3, n_classes=1)#SEUNet(3,1)model.set_dict(para_state_dict)for iter_id, data in enumerate(test_data_loader()):    x, y = data    # 将numpy数据转为飞桨动态图tensor形式    x = paddle.to_tensor(x)    y = paddle.to_tensor(y)    predicts = model(x)    for i in range(predicts.shape[0]):        predict = predicts[i,:,:,:].cpu().numpy()        label = y[i,:,:,:].cpu().numpy()        inputs = x[i,1,:,:].cpu().numpy()        predict = np.squeeze(predict)        label = np.squeeze(label)        inputs = np.squeeze(inputs)        #当要保存的图片为灰度图像时,灰度图像的 numpy 尺度是 [1, h, w]。需要将 [1, h, w] 改变为 [h, w]        plt.figure(figsize=(18, 6))        plt.subplot(1,3,1),plt.xticks([]),plt.yticks([]),plt.imshow(predict)        plt.subplot(1,3,2),plt.xticks([]),plt.yticks([]),plt.imshow(label)        plt.subplot(1,3,3),plt.xticks([]),plt.yticks([]),plt.imshow(inputs)        plt.show()                index_predict= np.argmax(np.max(predict,1))+3        index_label = np.argmax(np.max(label,1))        print('真实位置:',index_label,'预测位置:',index_predict)        Error.append(np.abs(index_label-index_predict))    breakprint("模型测试集平均定位误差为:",np.mean(Error))

       

开始测试

       

               

真实位置: 416 预测位置: 420

       

               

真实位置: 484 预测位置: 480模型测试集平均定位误差为: 4.0

       

以上就是『医学影像』基于Unet+++实现脊柱MRI定位(上)的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/316051.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月5日 07:45:49
下一篇 2025年11月5日 07:47:07

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何解决本地图片在使用 mask JS 库时出现的跨域错误?

    如何跨越localhost使用本地图片? 问题: 在本地使用mask js库时,引入本地图片会报跨域错误。 解决方案: 要解决此问题,需要使用本地服务器启动文件,以http或https协议访问图片,而不是使用file://协议。例如: python -m http.server 8000 然后,可以…

    2025年12月24日
    200
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • 使用 Mask 导入本地图片时,如何解决跨域问题?

    跨域疑难:如何解决 mask 引入本地图片产生的跨域问题? 在使用 mask 导入本地图片时,你可能会遇到令人沮丧的跨域错误。为什么会出现跨域问题呢?让我们深入了解一下: mask 框架假设你以 http(s) 协议加载你的 html 文件,而当使用 file:// 协议打开本地文件时,就会产生跨域…

    2025年12月24日
    200
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200
  • 前端代码辅助工具:如何选择最可靠的AI工具?

    前端代码辅助工具:可靠性探讨 对于前端工程师来说,在HTML、CSS和JavaScript开发中借助AI工具是司空见惯的事情。然而,并非所有工具都能提供同等的可靠性。 个性化需求 关于哪个AI工具最可靠,这个问题没有一刀切的答案。每个人的使用习惯和项目需求各不相同。以下是一些影响选择的重要因素: 立…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信