ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

改进U-Net实现轮毂X射线缺陷实时分割:卷积下采样与Dropout实践

改进U-Net实现轮毂X射线缺陷实时分割:卷积下采样与Dropout实践 简介基于U-Net卷积神经网络的轮毂缺陷分割是一份面向图像处理与深度学习研究者的技术论文PDF针对铸造轮毂X射线图像中裂纹、缩孔等缺陷难以自动检测的问题系统提出改进型U-Net分割方案。文中将原始U-Net最大池化操作替换为卷积下采样并引入Dropout层以提升模型稳定性同时完成缺陷区域标注、去均值、归一化、样本扩充等数据预处理流程。实验表明改进网络在复杂轮毂X射线缺陷识别上DICE系数达到0.8554SSIM系数为0.9655单张识别耗时仅3毫秒能够满足无损检测自动化需求。资源包大小815KB共1个PDF文件内容覆盖数据预处理细节、网络结构改动思路、实验指标分析与作者研究背景适合机器学习、数据建模方向学习者理解U-Net在工业质检场景中的工程化落地。当前已有141人浏览学习是一份可直接用于技术调研、方案设计或论文写作参考的实用资料。1. 轮毂缺陷分割这件事U-Net 为什么能轻轻松松做到 3 ms/张轮毂在铸造过程中最容易出两类缺陷裂纹和缩孔。这两样东西在 X 射线图像里往往只有几个像素宽人眼盯屏幕连续看半小时就会疲劳漏检而传统图像处理的阈值分割又根本抓不住这类低对比度目标——背景的铸件纹理和缺陷在灰度值上几乎重合。这篇论文给出的答案是 U-Net 卷积神经网络更具体地说是一个把最大池化换成卷积、再加了 Dropout 的改进 U-Net。整套方案的实测结果是 DICE 系数 0.8554、SSIM 系数 0.9655单张图像推理只要 3 ms。对一个做工业视觉的人来说这个数字意味着它已经能满足产线上的实时无损检测节奏。这篇笔记就围绕这个论文方案来拆从网络改哪里、数据怎么处理、训练时看什么曲线到部署时哪些环节容易翻车一次讲透。2. 先把网络结构立住把 Max-Pooling 替换成卷积加了 Dropout理由是什么2.1 U-Net 的骨架编码器、解码器、跳跃连接缺一不可U-Net 最早是给医学细胞图像分割设计的它的特点一句话就能说清左边编码器逐层下采样提取语义特征右边解码器逐层上采样恢复空间分辨率中间用跳跃连接把编码器每一层的特征图直接拼到解码器对应层上。这个跳跃连接是精华所在——下采样会丢边缘位置信息而分割恰恰需要精确的边缘定位跳跃连接相当于给解码器递了一张高清地图让它能在恢复尺寸的同时找回细节。论文里用的 U-Net 整体是 27 层左边收缩路径 4 个 ConvBlock右边扩展路径 4 个 Up-ConvBlock输入图像是 572×572。每个下采样块里先用一个 2×2 卷积核做步长 2 的卷积来降采样再接两个 3×3 卷积层每层后面跟 ReLU 激活。特征通道数从 64 开始逐层翻倍图像尺寸逐层减半最后缩到 28×28。上采样路径则是反卷积把图放大一倍特征通道数减一半同时和左边对应的特征图做拼接然后用两个 3×3 卷积去提炼特征。最后一层是 1×1 卷积加 Sigmoid输出一张每个像素值都在 0 到 1 之间的概率图值越大代表该像素属于缺陷的概率越高完成缺陷和背景的二分类。2.2 为什么要把最大池化替换成卷积信息保留的差异在哪原始 U-Net 的下采样用的是最大池化论文把它换成了步长 2 的 2×2 卷积。这个改动的实际意义有两层。第一层是特征保留最大池化只取窗口内最大的那个值相当于把局部区域压缩成一个最有把握的特征点这在分类任务里没问题但在分割任务里会丢掉目标的空间分布细节。卷积下采样是让网络自己学权重的下采样方式虽然在运算量上比池化略大但对缺陷这类小目标更友好。第二层是感受野。池化本身没有可学习的参数下采样的信息提取能力完全靠前面那层卷积撑着而卷积下采样多了一层可学习的特征变换等于在下采样的同时还在做一轮特征提取。这就是论文里 Loss 曲线对比能看到改进 U-Net 收敛更快、损失更小的直接原因——网络在同样的深度下做了更多有效计算而不是简单地对特征图做减半处理。我在自己的分割项目里做过对比卷积下采样在小目标分割上的 DICE 提升一般在 2 到 5 个百分点之间原理就在这——它保留的细节比池化多。2.3 Dropout 的作用机制它为什么能治样本少带来的过拟合轮毂缺陷图像是典型的样本稀缺场景——缺陷种类多、形态变化大但工厂能提供的带缺陷 X 光图就那么几百张。数据扩充能增加训练样本量但扩充样本之间的相关性很高模型照样容易记住训练集特有过拟合。Dropout 的引入就是冲这个问题去的。训练时每个 Dropout 层设定一个概率 p论文里取 p0.5也就是说每个神经元有 50% 的概率在当前批次被暂时丢弃只保留计算得到的权重下次迭代再参与更新。这样做的效果是每次迭代都在训练一个瘦身版的 U-Net权重分配不会过度依赖某一个节点特征提取在各个通道之间更均匀。这里有一个很多人忽略的工程细节Dropout 只在训练阶段生效推理阶段要把所有神经元参与计算输出还要乘上保留概率做补偿或者用 inverted dropout 让训练输出直接归一化。PyTorch 里 nn.Dropout 默认就是 inverted dropout即训练时自动除以保留概率推理时直接传值即可不需要手动调整。如果你是自己手写的 Dropout 逻辑这个补偿一定要做否则推理输出的概率图整体偏小阈值怎么调都不对。2.4 复现网络改造的关键代码卷积下采样与 Dropout 怎么落基于 PyTorch 搭一个改进 U-Net 的收缩块和扩展块核心代码这样写import torch import torch.nn as nn class ConvBlock(nn.Module): U-Net 收缩块步长2的2x2卷积做下采样 两个3x3卷积 ReLU def __init__(self, in_ch, out_ch, dropout_p0.5): super().__init__() self.down_conv nn.Conv2d(in_ch, out_ch, kernel_size2, stride2) self.conv1 nn.Conv2d(out_ch, out_ch, kernel_size3, padding1) self.conv2 nn.Conv2d(out_ch, out_ch, kernel_size3, padding1) self.relu nn.ReLU(inplaceTrue) self.dropout nn.Dropout2d(pdropout_p) def forward(self, x): # 卷积下采样替代原版最大池化 x self.down_conv(x) x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) # 在卷积层后加 Dropout防止过拟合 x self.dropout(x) return x这里 down_conv 的 kernel_size2、stride2已经把原特征图尺寸减半所以外面不需要再套池化层。注意 padding 参数3×3 卷积我用 padding1 保持特征图尺寸不变这样下采样只发生在 down_conv 这一层尺寸变化路径清晰可控。class UpConvBlock(nn.Module): U-Net 扩展块反卷积上采样 跳跃连接拼接 两个3x3卷积 def __init__(self, in_ch, skip_ch, out_ch): super().__init__() # 转置卷积图像尺寸放大一倍通道数减半 self.up_conv nn.ConvTranspose2d(in_ch, out_ch, kernel_size2, stride2) self.conv1 nn.Conv2d(out_ch skip_ch, out_ch, kernel_size3, padding1) self.conv2 nn.Conv2d(out_ch, out_ch, kernel_size3, padding1) self.relu nn.ReLU(inplaceTrue) def forward(self, x, skip_feat): x self.up_conv(x) # 跳跃连接编码器特征图与解码器特征图在通道维度拼接 x torch.cat([x, skip_feat], dim1) x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) return x拼接时要注意 skip_feat 的空间尺寸必须和上采样后的 x 完全一致否则 torch.cat 直接报错。如果原图尺寸不是 2 的整数次幂倍数比如 572×572 经过 4 次下采样变成 35.75就要在预处理阶段把人家的尺寸统一改成 2 的幂次倍数常见做法是 padding 到 576×576或者直接 resize 到 572×572 然后让网络结构去适配。论文的输入正好是 572经过四次减半落到 28×28 附近说明他们做数据的时候已经把这个尺寸问题处理掉了。Dropout2d 和 Dropout 的区别值得注意。图像分割用的是 Dropout2d它按通道整体丢弃而不是按单个像素丢弃也就是让某张特征图整体失效。这样做的合理性在于相邻像素高度相关按像素随机丢弃对卷积特征提取的破坏没有按通道丢弃大。3. 数据预处理全流程从 Labelme 标注人到训练样本四步不能省3.1 感兴趣区域提取为什么要先切 ROI 再训练轮毂 X 射线原图分辨率很高直接整图送进 U-Net 会带来两个问题一是 GPU 显存扛不住大尺寸输入二是缺陷区域在整张图里占比太小负样本严重压制正样本的梯度贡献。论文的做法是用 Labelme 先把缺陷区域标注出来提取感兴趣区域ROI之后再做训练和测试。在实际做的时候我要强调一点Labelme 标出来的多边形不只是用来做标签图的它同时定义了 ROI 的边界。你可以用标注框先裁出 ROI再基于裁剪后的区域去建模这样网络输入永远是缺陷周围一小块正负样本比例要健康得多。import json import numpy as np import cv2 from labelme.utils import shape_to_mask # 读取 Labelme 标注的 JSON 文件 with open(defect_001.json, r, encodingutf-8) as f: label_data json.load(f) # 读取原始图像 orig_img cv2.imread(label_data[imagePath], cv2.IMREAD_GRAYSCALE) # 从 Labelme 多边形标注生成二值掩码 mask shape_to_mask( (orig_img.shape[0], orig_img.shape[1]), label_data[shapes][0][points], shape_typepolygon ).astype(np.uint8) # 根据掩码边界框裁剪 ROI ys, xs np.where(mask 0) x_min, x_max xs.min(), xs.max() 1 y_min, y_max ys.min(), ys.max() 1 roi_img orig_img[y_min:y_max, x_min:x_max] roi_mask mask[y_min:y_max, x_min:x_max]shape_to_mask 是从 labelme 工具包自带 utils 里导出的函数它会把你手工点出来的多边形自动转成和原图同尺寸的二值掩码。边界框裁剪用的是掩码的最小外接矩形这样 ROI 既不会引入太多无关背景又不会把缺陷边缘切掉。如果缺陷形状是长条形的裂纹建议适当外扩几个像素避免缺陷正好压在裁剪边界上。3.2 去均值让模型训练更快进入收敛区间去均值就是把图像每个像素减去整个训练集的平均像素值让数据各维度中心化到 0 附近。这样做的直接收益是训练初期梯度下降的方向更稳定——如果原始图像的灰度分布整体偏移网络前几层要先去适配这个偏移量白白浪费迭代次数。在轮毂 X 射线图像的场景下去均值还有一个隐含好处不同批次的 X 射线图像可能因为曝光参数不同而整体偏亮或偏暗去均值之后这种设备层面的灰度漂移被压制网络学习到的特征更集中在缺陷本身的纹理结构上。# 计算整个训练集的平均灰度 mean_val np.mean(np.stack([roi_img.astype(np.float32) for roi_img in train_imgs])) # 去均值每个样本减去全局均值 train_imgs_centered [img.astype(np.float32) - mean_val for img in train_imgs]代码里注意两点一是必须先统一裁剪后的 ROI 尺寸再算均值否则不同尺寸图像堆叠会报错二是去均值操作一定要用 float32 精度uint8 减法会出现负数截断等于白做。论文里提到去均值之后训练不容易过拟合机理上说中心化后的数据让权重更新的方差更小模型对噪声的敏感度降低。3.3 归一化为什么说它能加快梯度下降收敛效率归一化是把像素值缩放到 0 到 1 或 -1 到 1 的区间。X 射线图像的原始灰度范围是 0 到 255而 U-Net 里的 Sigmoid 输出范围是 0 到 1如果输入不归一化网络第一层卷积的输入尺度就是输出尺度的几百倍梯度传播容易震荡甚至爆掉。常见的做法是除以 255 映射到 [0,1]但更推荐按训练集的均值方差做标准化# Z-Score 标准化 std_val np.std(np.stack([img for img in train_imgs_centered])) train_imgs_norm [(img - mean_val) / std_val for img in train_imgs_centered]这样处理之后输入数据的分布接近标准正态分布配合 ReLU 激活函数时神经元的输出不会频繁落入死亡区域。归一化和去均值要配套使用先去均值再除以标准差这是标准的 Z-Score 流程缺一步效果都会打折。3.4 样本扩充数据不够怎么用平移翻转凑出训练量标注好的轮毂缺陷样本往往只有一两百张喂给深度 U-Net 肯定过拟合。论文用随机平移和水平翻转做数据扩充——这些变换不改变缺陷的本质特征只改变缺陷在图像中的位置和朝向正好适合 X 射线图像这类目标姿态本来就没规律的应用场景。import random import torch from torchvision import transforms train_transform transforms.Compose([ transforms.RandomAffine( degrees0, # 不旋转轮毂缺陷方向有意义 translate(0.05, 0.05), # 随机平移最多5% scale(0.95, 1.05), # 轻微缩放 ), transforms.RandomHorizontalFlip(p0.5), transforms.ToTensor(), # 转Tensor并归一化到[0,1] ])这里我特意没开旋转因为轮毂是铸件裂纹方向往往和应力方向相关旋转 90 度会生成物理上不存在的缺陷样本。这个细节是论文没提但实际训练时要注意的——数据增强想当然地全开模型训练出来的精度是虚高的现场一测就露馅。平移范围控制在 5% 也是为了保留缺陷在 ROI 内的完整性平移量太大会把缺陷挪出边界导致标签错位。扩充后的训练集样本量建议至少是原始标注量的 8 到 10 倍。在这套预处理流程里最容易忽略的是最后一步标签图要和输入图像做完全相同的变换。很多人只对原始图做平移翻转标签掩码保持不动训练出的模型分割位置整体错位Loss 始终压不下去。用上面的 torchvision.transforms 方式写图片和掩码要分别定义两次同样的变换流程或者封装到一个自定义 Dataset 里同步处理。4. 模型训练与评估硬件配置、Loss 曲线和评价指标的工程含义4.1 训练环境与网络输入尺寸的选择逻辑论文给出的训练环境是 Intel i9-9900K CPU、NVIDIA GeForce RTX 2080Ti GPU、64 位 Windows 7 系统。这套配置放到现在看依然是中高端的深度学习工作台RTX 2080Ti 有 11 GB 显存训练 572×572 输入的 U-Net 完全够用batch size 开到 8 到 16 没有压力。输入尺寸 572×572 的选择有讲究。太大——比如 1024×1024——显存直接吃紧训练速度掉到不可接受。太小——比如 256×256——裂纹这类细长缺陷可能只剩两三个像素宽分割精度必然下降。572 是一个折中值配合四层下采样正好压缩到 28×28既不浪费显存又保住了缺陷的结构信息。如果要在自己的机器上复现显存小于 8 GB 的卡把输入尺寸降到 384×384效果差距不大但训练速度能快一倍。4.2 Loss 曲线对比改进网络为什么更平更稳论文给出了原始 U-NetMax-pooling和改造 U-NetConv的 Loss 曲线对比改进版损失值更小、收敛更快、曲线更平稳。这个结果和网络结构改动是直接对应的。改进版的 Loss 下降快是因为卷积下采样在每一步都做了一次可学习的特征提取梯度回传路径更丰富有效信息在每个下采样层都被保留。原始池化下采样没有参数下采样操作不产生梯度贡献信息提取的压力全压在后面几层卷积上收敛自然慢。Loss 曲线更平稳这件事Dropout 起了很大作用。Dropout 会让训练 Loss 在初期有一定抖动因为每次丢弃的神经元不同但迭代够多之后反而更平滑——防止了模型在某几个异常样本上过拟合导致的大幅波动。如果你训练时发现 Loss 曲线剧烈震荡先检查是不是 Dropout 的 p 设得太高0.7 以上或者学习率设得太大导致权重更新步长过猛。4.3 评价指标怎么读DICE 和 SSIM 分别衡量什么论文用了两个评价指标来量化分割精度。DICE 系数是分割任务最常用的指标衡量预测分割区域和真实标注区域的重叠程度。公式是 2 倍交集除以两个区域面积之和取值在 0 到 1 之间越高越好。论文训练集平均 DICE 0.8554这个数字在缺陷分割领域算是相当不错——因为缺陷往往是不规则的细长形状边界像素占比高DICE 天然不容易刷到 0.9 以上。原始 U-Net 只有 0.7932换成卷积下采样直接提升了 6 个百分点。SSIM 是结构相似性度量从亮度、对比度、结构三个维度比较两幅图像。论文里 SSIM 0.9655 远高于原版 U-Net 的 0.9176这说明改进网络输出的分割图在整体结构上和人工标注非常接近——边缘定位准伪影少。4.4 推理速度 3 ms/张背后的工程账3 ms/张的识别速度是在 RTX 2080Ti 上得到的。如果不算图像 IO 和预处理纯网络推理 3 毫秒意味着产线上即使按每秒 5 张的速度连续过料GPU 也只用不到 2% 的算力。真正卡脖子的往往不是网络推理而是前端的图像采集和缺陷标注的 ROI 提取耗时。这里要泼一盆冷水3 ms 是论文理想条件下的数据部署到现场生产环境时需要考虑工业相机触发、图像传输、预处理流水线带来的额外延迟。常见的做法是把 U-Net 转成 TensorRT 的 FP16 引擎推理速度还能再快一倍代价是 DICE 会掉 0.5 到 1 个百分点用在对精度要求不那么极端的场景完全划算。5. 避坑与常见问题排查轮毂缺陷分割复现路上的五个坑5.1 标注样本太少U-Net 训练直接崩掉现象训练到第 10 个 epoch 左右训练 Loss 降到很低但验证集 DICE 一直在 0.3 到 0.4 徘徊明显过拟合。原因标注样本只有几十张数据扩充量撑不起整个 U-Net 的参数量。U-Net 虽然结构比大型分类网络轻量但 27 层的参数量依然是百万级几十张训练样本只能让网络记住训练集本身。解决先把样本扩充规模翻到 10 倍以上再谈训练。如果扩充后 Loss 还在 0.1 以下而验证 DICE 上不去加大 Dropout 概率到 0.6或者给卷积层加权重衰减weight decay 设为 1e-4。再不行就考虑迁移学习用公开数据集如医学图像分割数据集预训练 U-Net 编码器再冻结前几层微调轮毂缺陷数据。5.2 标签图和输入图像尺寸不一致Loss 恒为常数现象训练 Loss 不下降稳定在某个固定值附近。原因代码里对输入图和掩码分别做了 resize但尺寸参数写得不一致。比如输入图是 572×572掩码被 resize 成 256×256U-Net 输出的 572×572 概率图和标签做损失计算时PyTorch 的 BCELoss 不会报错但逐像素比较的结果基本是随机的。解决在 Dataset 的getitem里 return 前加断言强制检查 img.shape 和 mask.shape 完全一致。归一化、resize、旋转这些 transform 统一封装起来输入图和标签图走同一个 pipeline不要分开写两套流程。模板代码assert img_norm.shape mask.shape, fImage {img_norm.shape} and mask {mask.shape} mismatch5.3 Sigmoid 输出后直接二值化边缘出现大量椒盐噪声现象概率图阈值取 0.5 后缺陷边缘周围出现密集的小块噪点。原因X 射线图像噪声较高缺陷边缘在像素级上的灰度过渡很宽网络在这些区域的输出概率恰好在 0.5 附近徘徊二值化之后就成了散点。解决别急着上一刀切的阈值。先对概率图做一次高斯滤波kernel 3×3 或 5×5再取阈值 0.5边缘噪声能消掉大半。更稳的办法是用阈值 0.3 和 0.7 做双层判定概率大于 0.7 的像素一定标记为缺陷小于 0.3 的一定不是中间地带用连通域分析配合形态学开运算决定归属。现场部署时我会先用测试集统计一个最优阈值而不是默认 0.5。5.4 训练 Loss 一直降不下去问题出在数据标注质量现象Loss 下降到 0.3 左右就卡住无法继续下降DICE 在 0.7 上下徘徊。原因Labelme 标注的缺陷边界不精确。多边形标注的边界是直边裂纹的实际边缘是连续弧线网络在标注误差大的样本上反复学习矛盾信息Loss 下不去。解决标注时用 Labelme 的 brush 工具或圆点模式做边缘精细标注而不是粗放地画多边形。标注完成后做一轮交叉复核至少两个人独立标注同一批图像计算标注之间的一致性用 DICE 衡量低于 0.9 的样本要重新标。这类脏数据宁可丢掉也不要灌进训练集害处比益处大得多。5.5 现场采集图和训练图灰度分布不同模型精度断崖式下跌现象训练集上 DICE 0.85换到现场新采集的轮毂 X 光图上直接掉到 0.5。原因现场的 X 射线成像参数管电压、管电流、曝光时间和实验室采集时的设置不一致导致图像整体灰度分布偏移。模型虽然训练时做过归一化但归一化的均值和标准差是训练集算出来的新图分布不同归一化后依然对不上。解决部署前先采集一组现场图统计灰度均值方差用现场数据重新计算归一化的 mean 和 std替换掉训练时的统计值。如果现场图像亮度波动幅度很大超过 ±20%建议在预处理里加一步自适应直方图均衡化把灰度分布强制拉到统一区间。这个操作要在训练集和测试集上同步做否则又是一次分布不匹配。6. 落地验证技巧用 SSIM 和 DICE 的组合判断模型是否值得上线6.1 DICE 分数与 SSIM 分数互为参照的意义很多人在评估分割模型时只盯 DICE但 DICE 有一个盲区它只看区域重叠度不关心分割结果在结构上是否合理。举个例子一个模型输出的缺陷区域位置、大小、形状都和标注高度重合但边缘整体向内缩了 2 个像素DICE 可能只从 0.9 掉到 0.85肉眼看很难分辨此时 SSIM 会从 0.98 掉到 0.92直接暴露边界退化。所以在评估模型时我用 DICE 和 SSIM 一起看两个指标同时下降才说明模型真的退步了单一指标波动大概率是标注差异或阈值微调引起的。论文里的评测瞒不住任何问题改进 U-Net 的 DICE 0.8554 和 SSIM 0.9655 是同步提升的这比单个指标好看更有说服力。6.2 DICE 指标的重新标定用测试集做阈值曲线扫描import numpy as np def dice_score(pred_prob, gt_mask, threshold0.5): pred (pred_prob threshold).astype(np.uint8) intersection np.logical_and(pred, gt_mask).sum() union pred.sum() gt_mask.sum() return 2 * intersection / union if union 0 else 1.0 # 测试集上扫描阈值找最优 thresholds np.arange(0.3, 0.7, 0.02) dice_scores [np.mean([dice_score(model_prob[i], gt_mask[i], t) for i in range(len(model_prob))]) for t in thresholds] best_threshold thresholds[np.argmax(dice_scores)]这段代码做的事情在测试集的概率图和标注图上从 0.3 到 0.7 扫一遍阈值找出让平均 DICE 最高的那个阈值替换默认的 0.5。这样的好处是阈值选择有统计依据而不是拍脑袋定。经过扫描选出的阈值通常比 0.5 低 0.05 到 0.1因为缺陷像素的概率值集中在 0.6 附近取 0.5 会把边缘区域的弱信号全部滤掉DICE 白白丢一两个点。6.3 模型泛化能力的快速自检留出法之外的验证设计轮毂缺陷分割的样本量决定了你不能像公开数据集那样做五折交叉验证太奢侈。我一般会按缺陷类型分层留出验证集裂纹样本留 10%缩孔样本留 10%保证验证集里两类缺陷都有。训练完成之后把验证集的预测结果逐张看一眼DICE 高的看边界细节DICE 低的看模型到底把错标当成缺陷还是漏检。这种人工复核比任何指标都更能暴露模型的实际问题——通常你会发现DICE 低的样本集中在细碎裂纹上属于标注本身就存在分歧的图像模型跟不上是正常的。从模型设计到落地这段路我最深的体会是U-Net 的改动幅度不用很大——一个卷积替换池化、一层 Dropout就能换来 6 个百分点的 DICE 提升关键是把每个改动背后的场景逻辑想清楚再配合数据预处理把干净样本喂进去。从那以后我每次训练分割模型都强制自己在数据集层面走一遍标注质量复核 → 扩充前向性检查 → 分布对齐这三步宁可慢半天也不能让模型在脏数据上跑轮次。这套方法在轮毂缺陷分割上验证有效换成其他工业视觉小目标分割场景的思路也完全通用希望帮到你。本文还有配套的精品资源点击获取
返回列表