【PP3DSeg】基于PaddleSeg实现3DUnet分割肝脏
PP3DSeg初衷:在aistudio暂时还没有找到属于医疗数据的3D分割,自己又没有本地算力,又想自己搞一下3D分割,所以就有了这个项目。PP3DSeg这个工具是基于PaddlePaddle和PaddleSeg构建的,其中3DUnet网络和一些transform方法是参考https://github.com/wolny/pytorch-3dunet 。整个项目基本和PaddleSeg很像,只是针
PP3DSeg
初衷:在aistudio暂时还没有找到属于医疗数据的3D分割,自己又没有本地算力,又想自己搞一下3D分割,所以就有了这个项目。
PP3DSeg这个工具是基于PaddlePaddle和PaddleSeg构建的,其中3DUnet网络和一些transform方法是参考https://github.com/wolny/pytorch-3dunet 。
整个项目基本和PaddleSeg很像,只是针对3D医疗数据进行修改。专门用来处理3D的医疗数据,并对数据进行3D分割。
目前项目中只支持3DUnet网络和交叉熵损失函数。
数据增强暂时支持随机水平翻转、随机垂直翻转,重采样,归一化,随机对比度改变,随机角度旋转等方法。
可见PP3DSeg这个项目是不成熟的。里面存在各种各样的未知Bug,不过后面会慢慢更新,加入更多处理医疗数据的方法和3D分割网络等等。希望更多大佬可以给予珍贵的意见。
#克隆PP3DSeg
#!git clone https://github.com/richarddddd198/PP3DSeg.git
!git clone https://gitee.com/richarddddd198/PP3DSeg.git
# 安装必要的库
!pip install SimpleITK scikit-image
#解压肝脏数据
#数据来源https://www.ircad.fr/research/3d-ircadb-01/
#标注只有肝脏,并且数据集只有14个病例
!unzip data/data114428/liver.zip -d /home/aistudio/work/
#导入常用的库
import random
import os
import SimpleITK as sitk
from scipy import ndimage
import matplotlib.pyplot as plt
import numpy as np
import paddle
import cv2
#划分训练集合验证集
random.seed(1000)
path_origin = '/home/aistudio/work/liver/origin'
files = list(filter(lambda x: x.endswith('.nii'), os.listdir(path_origin)))
random.shuffle(files)
rate = int(len(files) * 0.9)#训练集和测试集9:1
train_txt = open('/home/aistudio/work/liver/train_list.txt','w')
val_txt = open('/home/aistudio/work/liver/val_list.txt','w')
for i,f in enumerate(files):
image_path = os.path.join(path_origin, f)
label_path = image_path.replace("origin", "masks")
if i < rate:
train_txt.write(image_path + ' ' + label_path+ '\n')
else:
val_txt.write(image_path + ' ' + label_path+ '\n')
train_txt.close()
val_txt.close()
print('完成')
完成
叠加Mask展示原始CT图像
绿色标注的是肝脏
def wwwc(sitkImage,ww,wc):
#设置窗宽窗位
min = int(wc - ww/2.0)
max = int(wc + ww/2.0)
intensityWindow = sitk.IntensityWindowingImageFilter()
intensityWindow.SetWindowMaximum(max)
intensityWindow.SetWindowMinimum(min)
sitkImage = intensityWindow.Execute(sitkImage)
return sitkImage
origin = sitk.ReadImage('/home/aistudio/work/liver/origin/1.nii')
print(origin.GetSize())
mask = sitk.ReadImage('/home/aistudio/work/liver/masks/1.nii')
origin = wwwc(origin, 350,60)
mask= sitk.Cast(mask, sitk.sitkUInt8)
#原图和mask叠加在一起
new_data= sitk.LabelOverlay(origin, mask, opacity=0.01)
data = sitk.GetArrayFromImage(new_data)
d = data.shape[0]
plt.figure(figsize=(12,12))
for index,i in enumerate(range(int(d/2)-6,int(d/2)+6)) :
plt.subplot(4,3,index+1)
plt.imshow(data[i,...])
plt.axis('off')
plt.subplots_adjust(left=0.0,bottom=0.0,top=1,right=1,wspace =0.0001, hspace =0.0001)#调整子图间距
plt.show()
(512, 512, 129)
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2349: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
if isinstance(obj, collections.Iterator):
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2366: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
return list(data) if isinstance(data, collections.MappingView) else data
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
导入DataSet 和 Transforms
DataSet和Transform是参考PaddleSeg,使用起来和PaddleSeg差不多,只是PaddleSeg是处理2D数据,我这个用来专门处理3D医疗数据。具体代码可以进入/home/aistudio/PP3DSeg查看
from PP3DSeg.transforms import transforms as T
from PP3DSeg.datasets import Dataset
WW = 350
WC = 60
SIZE = (48,256,256)
train_transforms =T.Compose( [
T.Normalize(ww=WW,wc=WC),
T.RandomHorizontalFlip(),#水平翻转
T.RandomContrast( alpha=(0.2, 1.6)),#随机改变对比度
T.RandomRotate(max_rotation=25),#随机旋转一定角度
T.Resize3D(target_size=SIZE),#重采样
T.ToTensor()
])
val_transforms = T.Compose([
T.Normalize(ww=WW,wc=WC),
T.Resize3D(target_size=SIZE),
T.ToTensor()
])
train_path = '/home/aistudio/work/liver/train_list.txt'
val_path = '/home/aistudio/work/liver/val_list.txt'
train_dataset = Dataset(transforms=train_transforms,
dataset_root='/home/',
num_classes=2,
mode='train',
train_path=train_path,
flipud=False)
val_dataset = Dataset(transforms=val_transforms,
dataset_root='/home/',
num_classes=2,
mode='val',
val_path=val_path,
flipud=False)
img,label = train_dataset[0]
print(img.shape)
print(label.shape)
img = paddle.squeeze(img).numpy()
label = label.numpy()
plt.figure(figsize=(10,6))
plt.subplot(121)
plt.imshow(img[21,...],'gray')
plt.subplot(122)
plt.imshow(label[21,...],'gray')
[1, 48, 256, 256]
[48, 256, 256]
<matplotlib.image.AxesImage at 0x7f29b3b6c090>
导入模型
2DUnet
3DUnet
从上面两张图看到,这个3D网络其实和2DUNet区别不大,简单说可以理解为2d卷积换为了3d卷积
由于医疗数据。例如CT,MR等都是三维数据,如果是2D网络去分割,会丢失在z轴方向的信息。但是也不一定是3D网络就比2D网络好,例如患者的病例数据的层数是不多的厚层,用2D的效果可能会更佳。
from PP3DSeg.models.unet3d import Unet3d
model = Unet3d(in_channels=1, num_filters=16, class_num=2)
paddle.summary(model,(2,1,32,256,256))
-------------------------------------------------------------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===============================================================================================================================
Conv3D-1 [[2, 1, 32, 256, 256]] [2, 8, 32, 256, 256] 224
BatchNorm3D-1 [[2, 8, 32, 256, 256]] [2, 8, 32, 256, 256] 32
ReLU-1 [[2, 8, 32, 256, 256]] [2, 8, 32, 256, 256] 0
Conv3D-2 [[2, 8, 32, 256, 256]] [2, 16, 32, 256, 256] 3,472
BatchNorm3D-2 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 64
ReLU-2 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 0
conv_block-1 [[2, 1, 32, 256, 256]] [2, 16, 32, 256, 256] 0
MaxPool3D-1 [[2, 16, 32, 256, 256]] [2, 16, 16, 128, 128] 0
Down-1 [[2, 1, 32, 256, 256]] [[2, 16, 32, 256, 256], [2, 16, 16, 128, 128]] 0
Conv3D-3 [[2, 16, 16, 128, 128]] [2, 16, 16, 128, 128] 6,928
BatchNorm3D-3 [[2, 16, 16, 128, 128]] [2, 16, 16, 128, 128] 64
ReLU-3 [[2, 16, 16, 128, 128]] [2, 16, 16, 128, 128] 0
Conv3D-4 [[2, 16, 16, 128, 128]] [2, 32, 16, 128, 128] 13,856
BatchNorm3D-4 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 128
ReLU-4 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 0
conv_block-2 [[2, 16, 16, 128, 128]] [2, 32, 16, 128, 128] 0
MaxPool3D-2 [[2, 32, 16, 128, 128]] [2, 32, 8, 64, 64] 0
Down-2 [[2, 16, 16, 128, 128]] [[2, 32, 16, 128, 128], [2, 32, 8, 64, 64]] 0
Conv3D-5 [[2, 32, 8, 64, 64]] [2, 32, 8, 64, 64] 27,680
BatchNorm3D-5 [[2, 32, 8, 64, 64]] [2, 32, 8, 64, 64] 128
ReLU-5 [[2, 32, 8, 64, 64]] [2, 32, 8, 64, 64] 0
Conv3D-6 [[2, 32, 8, 64, 64]] [2, 64, 8, 64, 64] 55,360
BatchNorm3D-6 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 256
ReLU-6 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 0
conv_block-3 [[2, 32, 8, 64, 64]] [2, 64, 8, 64, 64] 0
MaxPool3D-3 [[2, 64, 8, 64, 64]] [2, 64, 4, 32, 32] 0
Down-3 [[2, 32, 8, 64, 64]] [[2, 64, 8, 64, 64], [2, 64, 4, 32, 32]] 0
Conv3D-7 [[2, 64, 4, 32, 32]] [2, 64, 4, 32, 32] 110,656
BatchNorm3D-7 [[2, 64, 4, 32, 32]] [2, 64, 4, 32, 32] 256
ReLU-7 [[2, 64, 4, 32, 32]] [2, 64, 4, 32, 32] 0
Conv3D-8 [[2, 64, 4, 32, 32]] [2, 128, 4, 32, 32] 221,312
BatchNorm3D-8 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 512
ReLU-8 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 0
conv_block-4 [[2, 64, 4, 32, 32]] [2, 128, 4, 32, 32] 0
MaxPool3D-4 [[2, 128, 4, 32, 32]] [2, 128, 2, 16, 16] 0
Down-4 [[2, 64, 4, 32, 32]] [[2, 128, 4, 32, 32], [2, 128, 2, 16, 16]] 0
Conv3D-9 [[2, 128, 2, 16, 16]] [2, 128, 2, 16, 16] 442,496
BatchNorm3D-9 [[2, 128, 2, 16, 16]] [2, 128, 2, 16, 16] 512
ReLU-9 [[2, 128, 2, 16, 16]] [2, 128, 2, 16, 16] 0
Conv3D-10 [[2, 128, 2, 16, 16]] [2, 256, 2, 16, 16] 884,992
BatchNorm3D-10 [[2, 256, 2, 16, 16]] [2, 256, 2, 16, 16] 1,024
ReLU-10 [[2, 256, 2, 16, 16]] [2, 256, 2, 16, 16] 0
conv_block-5 [[2, 128, 2, 16, 16]] [2, 256, 2, 16, 16] 0
Upsample-1 [[2, 256, 2, 16, 16]] [2, 256, 4, 32, 32] 0
Conv3D-11 [[2, 384, 4, 32, 32]] [2, 128, 4, 32, 32] 1,327,232
BatchNorm3D-11 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 512
ReLU-11 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 0
Conv3D-12 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 442,496
BatchNorm3D-12 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 512
ReLU-12 [[2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 0
conv_block-6 [[2, 384, 4, 32, 32]] [2, 128, 4, 32, 32] 0
Up-1 [[2, 256, 2, 16, 16], [2, 128, 4, 32, 32]] [2, 128, 4, 32, 32] 0
Upsample-2 [[2, 128, 4, 32, 32]] [2, 128, 8, 64, 64] 0
Conv3D-13 [[2, 192, 8, 64, 64]] [2, 64, 8, 64, 64] 331,840
BatchNorm3D-13 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 256
ReLU-13 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 0
Conv3D-14 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 110,656
BatchNorm3D-14 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 256
ReLU-14 [[2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 0
conv_block-7 [[2, 192, 8, 64, 64]] [2, 64, 8, 64, 64] 0
Up-2 [[2, 128, 4, 32, 32], [2, 64, 8, 64, 64]] [2, 64, 8, 64, 64] 0
Upsample-3 [[2, 64, 8, 64, 64]] [2, 64, 16, 128, 128] 0
Conv3D-15 [[2, 96, 16, 128, 128]] [2, 32, 16, 128, 128] 82,976
BatchNorm3D-15 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 128
ReLU-15 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 0
Conv3D-16 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 27,680
BatchNorm3D-16 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 128
ReLU-16 [[2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 0
conv_block-8 [[2, 96, 16, 128, 128]] [2, 32, 16, 128, 128] 0
Up-3 [[2, 64, 8, 64, 64], [2, 32, 16, 128, 128]] [2, 32, 16, 128, 128] 0
Upsample-4 [[2, 32, 16, 128, 128]] [2, 32, 32, 256, 256] 0
Conv3D-17 [[2, 48, 32, 256, 256]] [2, 16, 32, 256, 256] 20,752
BatchNorm3D-17 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 64
ReLU-17 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 0
Conv3D-18 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 6,928
BatchNorm3D-18 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 64
ReLU-18 [[2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 0
conv_block-9 [[2, 48, 32, 256, 256]] [2, 16, 32, 256, 256] 0
Up-4 [[2, 32, 16, 128, 128], [2, 16, 32, 256, 256]] [2, 16, 32, 256, 256] 0
Conv3D-19 [[2, 16, 32, 256, 256]] [2, 2, 32, 256, 256] 34
===============================================================================================================================
Total params: 4,122,466
Trainable params: 4,117,570
Non-trainable params: 4,896
-------------------------------------------------------------------------------------------------------------------------------
Input size (MB): 16.00
Forward/backward pass size (MB): 11465.00
Params size (MB): 15.73
Estimated Total Size (MB): 11496.73
-------------------------------------------------------------------------------------------------------------------------------
{'total_params': 4122466, 'trainable_params': 4117570}
设置优化器和loss
from PP3DSeg.models.losses.cross_entropy_loss import CrossEntropyLoss
BATCH_SIZE = 2
EPOCHS = 100
decay_steps = int(len(train_dataset)/BATCH_SIZE * EPOCHS)
iters = decay_steps
# 设置学习率
base_lr = 0.02
lr = paddle.optimizer.lr.PolynomialDecay(learning_rate=base_lr, decay_steps=decay_steps, verbose=False)
optimizer = paddle.optimizer.Momentum(lr, parameters=model.parameters())
losses = {}
losses['types'] = [CrossEntropyLoss()]
losses['coef'] = [1]
开始训练
from PP3DSeg.core import train
train(
model=model,
train_dataset=train_dataset,
val_dataset=val_dataset,
optimizer=optimizer,
save_dir='/home/aistudio/MyOutput',
iters=iters,
batch_size=BATCH_SIZE,
save_interval=11,
log_iters=2,
num_workers=0,
losses=losses,
use_vdl=True)
验证训练效果
import paddle
from PP3DSeg.core import evaluate
model = model = Unet3d(in_channels=1, num_filters=16, class_num=2)
# model_path = '/home/aistudio/MyOutput/best_model/model.pdparams'
#这是我训练的模型,记得用会上面的路径
model_path = '/home/aistudio/MyBestModel/model.pdparams'
para_state_dict = paddle.load(model_path)
model.set_dict(para_state_dict)
evaluate(model,val_dataset)
2021-10-31 21:55:36 [INFO] Start evaluating (total_samples: 2, total_iters: 2)...
2/2 [==============================] - 2s 995ms/step - batch_cost: 0.9940 - reader cost: 0.9823
2021-10-31 21:55:39 [INFO] [EVAL] #Images: 2 mIoU: 0.9161 Acc: 0.9850 Kappa: 0.9098
2021-10-31 21:55:39 [INFO] [EVAL] Class IoU:
[0.9837 0.8485]
2021-10-31 21:55:39 [INFO] [EVAL] Class Acc:
[0.9933 0.9037]
(0.9160656,
0.98502254,
array([0.9836514 , 0.84847975], dtype=float32),
array([0.99334145, 0.90370494], dtype=float32),
0.9097904048554073)
加载模型进行推理
并对预测的mask进行大小还原
import matplotlib.pyplot as plt
import numpy as np
import SimpleITK as sitk
import paddle
from PP3DSeg.transforms import transforms as T
from PP3DSeg.core import infer
from PP3DSeg.models.unet3d import Unet3d
def readNii(path,flipud=False):
#读取nii文件并转换成numpy
data = sitk.ReadImage(path)
data = sitk.GetArrayFromImage(data)
if flipud:
#是否上下翻转
data = np.flip(data,1)
return data
def restore(im,target_size,model='nearest'):
"把预测的mask还有成原来大小"
desired_depth = depth = target_size[0]
desired_width = width = target_size[1]
desired_height = height = target_size[2]
current_depth = im.shape[0]
current_width = im.shape[1]
current_height = im.shape[2]
depth = current_depth / desired_depth
width = current_width / desired_width
height = current_height / desired_height
depth_factor = 1 / depth
width_factor = 1 / width
height_factor = 1 / height
im = ndimage.zoom(im,(depth_factor,width_factor, height_factor),
order=0,mode=model, cval=0.0)
return im
#显示一个系列图
def show_img(data):
plt.figure(figsize=(12,24))
row = int(data.shape[0] / 6)+1
for i in range(0,data.shape[0]):
plt.subplot(row,6,i+1)
plt.imshow(data[i,:,:], 'gray')
plt.axis('off')
plt.subplots_adjust(left=0.0,bottom=0.0,top=1,right=1,wspace =0.0001, hspace =0.0001)#调整子图间距
plt.show()
test_transforms = T.Compose([
T.Normalize(ww=350,wc=60),
T.Resize3D(target_size=(48,256,256)),
T.ToTensor()
])
def nn_infer(model, img, model_path,transforms):
#加载模型并预测
para_state_dict = paddle.load(model_path)
model.set_dict(para_state_dict)
img, _ = transforms(img)
img = paddle.unsqueeze(img,axis=0)
pre = infer.inference(model, img)
pred = paddle.squeeze(pre)
return pred.astype('uint8')
params = '/home/aistudio/MyBestModel/model.pdparams'
model = Unet3d(in_channels=1, num_filters=16, class_num=2)
ori_path = '/home/aistudio/work/liver/origin/1.nii'
lab_path = '/home/aistudio/work/liver/masks/1.nii'
origin = readNii(img_path)
print("原始图像的形状{}".format(origin.shape))
label = readNii(lab_path)
pre = nn_infer(model, origin, params,test_transforms).numpy()
print("预测mask的形状{}".format(pre.shape))
#对预测mask进行大小还原
pre_mask = restore(pre,origin.shape)
print("预测mask的还原后的形状{}".format(pre_mask.shape))
show_img(pre_mask)
原始图像的形状(129, 512, 512)
预测mask的形状(48, 256, 256)
预测mask的还原后的形状(129, 512, 512)
numpy转换nii格式,与原始数据叠加显示效果
ori_path = '/home/aistudio/work/liver/origin/1.nii'
ori_sitkImage = sitk.ReadImage(ori_path)
pre_sitkImage = sitk.GetImageFromArray(pre_mask)
pre_sitkImage.CopyInformation(ori_sitkImage)
#是否保存
#sitk.WriteImage(pre_sitkImage,'/home/aistudio/1_pre_mask.nii')
new_data= sitk.LabelOverlay(ori_sitkImage, pre_sitkImage, opacity=0.01)
data = sitk.GetArrayFromImage(new_data)
d = data.shape[0]
plt.figure(figsize=(12,12))
for index,i in enumerate(range(int(d/2)-6,int(d/2)+6)) :
plt.subplot(4,3,index+1)
plt.imshow(data[i,...])
plt.axis('off')
plt.subplots_adjust(left=0.0,bottom=0.0,top=1,right=1,wspace =0.0001, hspace =0.0001)#调整子图间距
plt.show()
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
个人介绍
个人介绍
广州某医院的放射科的一名放射技师。
只是一位编程爱好者
只是想把自己的爱好融入工作中
只是想让自己通过努力获取成就和快乐
欢迎更多志同道合的朋友一起玩耍~~~
我在AI Studio上获得至尊等级,点亮10个徽章,来互关呀~ https://aistudio.baidu.com/aistudio/personalcenter/thirdview/181096
请点击此处查看本环境基本用法.
Please click here for more detailed instructions.
更多推荐
所有评论(0)