简介:基于Python编程语言实现深度生成对抗网络图像修复模型的完整工程项目,源码与项目文档齐备,主要面向毕业设计、课程设计与项目开发场景,也适合具备一定深度学习基础的学习者作为实践参考。压缩包共包含七个文件,其中六个为Python脚本,一个为Markdown说明文档,整体体积仅12KB,脚本覆盖模型定义、网络操作、数据工具、训练与图像补全等核心环节,目录精简且职责清晰。源码已经过严格测试,可直接运行并在此基础上延展,配套文档详细介绍了项目结构、运行方式与使用细节,便于快速理解生成对抗网络的构建逻辑和图像修复实现思路。目前已有253人学习/下载,读者可借助这份紧凑而完整的代码骨架,快速跑通图像修复实验,并针对自身课题进行调整与二次开发,适合毕业设计、课程设计或项目起步阶段参考。
1. 项目概述与技术选型思路
1.1 这个项目解决什么问题
图像修复(Image Inpainting)是计算机视觉里的一个常青话题,说人话就是:把图片中缺失、被遮挡、被损坏的区域补回来,并且让观感上看不出修补痕迹。修复的对象可能是老照片上的划痕、被物体遮挡的背景、因为裁剪或擦除造成的空洞,甚至是有害信息遮挡后的内容重建。这个需求在文物保护、影像后期、医疗影像、自动驾驶(去除遮挡物)等领域都有直接应用场景。
我这次做的项目,是基于深度生成对抗网络(GAN)的图像修复模型,语言用Python,框架选PyTorch,整体从数据准备、模型搭建到训练推理全部手写实现,并配套完整源码和项目文档。之所以选这个题目,是因为图像修复本身是一个特别适合做毕设或课程设计的场景:它比图像分类、目标检测这种“标准任务”更有视觉冲击力,演示效果好,同时技术栈覆盖全面——从卷积神经网络、注意力机制、对抗训练到损失函数设计,几乎把深度学习里的核心知识点都串起来了。做完一个项目,基本相当于把生成模型这条线吃透了。
这个项目适合的人群包括:正在选毕设题目的本科生、需要交课程项目的研究生、想从分类任务转向生成任务练手的开发者,以及秋招想往AIGC算法方向走的同学。难度上属于中等偏上,但我会把每一步拆开讲清楚,跟着做完全来得及。
1.2 为什么用GAN而不是传统方法
影像修复这个方向其实很早就有了,早期方法大致有两类:一类是基于扩散的(Diffusion-based),从空洞边缘一圈一圈往里填充,效果像用图章工具一点一点补,慢且只能处理小区域;另一类是基于Patch匹配的(Exemplar-based),在图像其他区域找相似纹理块贴到空洞里,对于背景纹理规整的图效果尚可,但遇到复杂结构或者语义信息(比如人脸的五官)就完全不行了。
这两类方法的本质缺陷是:它们都在做“像素级别的相似度匹配”,不理解图像的高层语义。说白了就是不懂“这里应该有一张桌子”“这里应该是眼睛”。修复结果经常出现纹理错乱、结构扭曲的情况。而GAN恰好能解决这个问题——生成器在对抗训练的过程中,会被迫学习真实图像的分布,也就是说它知道“合理的图像长什么样”。有了这种先验知识,面对一个大空洞时,它生成的不再是一堆纹理的堆砌,而是符合语义逻辑的内容。
当然,现在也有基于扩散模型的修复方案,效果确实更强,但计算资源要求高,训练周期长,对毕设来说不够友好。GAN在这个场景下的平衡点最好:训练可控、显存要求适中、效果可视化明显,而且源码和论文资料积累多,踩坑容易找到参考。
1.3 项目整体架构与工作流程
整个项目的处理流程可以用一条线串起来:原始图片 → 人为生成掩码(模拟损坏区域)→ 将图片和掩码输入生成器 → 生成器输出修复结果 → 判别器判断修复结果是否真实 → 计算多种损失函数 → 反向传播更新参数 → 循环迭代直到收敛。
模型的总体结构采用“双网络对抗”框架:
- 生成器(Generator):负责根据残缺图像和掩码生成完整的、看起来合理的图像。
- 判别器(Discriminator):负责区分输入图像是“真实原图”还是“生成器修复出来的图”。
两个网络互相博弈:生成器想骗过判别器,判别器想抓住生成器的破绽。这种对抗训练的方式,使得生成器输出的图像越来越逼真。这是GAN系列方法的通用训练范式,具体到图像修复任务,生成器的设计会更复杂——它不仅要生成逼真的纹理,还要保证生成区域和周围已有像素在颜色、光照、结构上完全协调,任务难度比“从噪声生成图像”的传统GAN更高。
2. 核心原理与关键技术解析
2.1 生成器网络的结构设计
生成器是整个修复模型的重头戏,我采用的是类似U-Net的编码器-解码器结构,并在此基础上加入了部分卷积(Partial Convolution)和注意力机制(Attention)。先解释为什么用U-Net结构:图像修复要求输出和输入是同一尺寸的大图,U-Net通过跳跃连接(Skip Connection)把编码器各层的特征直接送到解码器对应层,这样保留了大量空间细节信息,避免深层网络丢失边缘、纹理等细节。
编码器部分用步长为2的卷积逐层下采样,每下一层特征图的尺寸减半、通道数翻倍。这样做的目的是扩大感受野,让网络能“看到”更大范围内的上下文信息。想象一下,如果空洞区域很大,而你只盯着空洞旁边几十个像素,根本猜不出这里该填什么;必须看到更大范围的场景结构,才能做出合理的推断。解码器部分用转置卷积或者最近邻上采样把特征图逐步放大回原图尺寸。
这里有一个关键改进点:部分卷积(Partial Convolution)。普通的卷积操作在处理带掩码的图像时有一个严重问题——空洞区域的像素值(通常是0或噪声)会被当成有效信息参与卷积计算,导致生成结果出现明显的色斑或模糊边界。部分卷积的处理方式是:卷积的时候只对有效区域的像素做计算,同时自动更新掩码,随着层数加深,原本无效的区域逐渐变成有效区域。这套机制是NVIDIA在2018年提出的(Partial Convolutions for Image Inpainting),效果比普通卷积好一大截。
2.2 判别器与局部判别机制
判别器我采用的是PatchGAN结构,不是输出一个0到1的全局真伪概率,而是输出一个N×N的矩阵,每个元素对应原图的一个小区域是真还是假。这个设计的动机很实际:图像修复好不好,关键看局部细节是否逼真——你可能整体看觉得没问题,但放大看某个小区域,纹理、边缘完全对不上。PatchGAN把注意力放在局部块上,强制生成器在每个局部区域都生成足够真实的内容。
PatchGAN的另一个优势是参数少、计算快,因为它没有全连接层,全部是卷积操作,输入也不需要固定尺寸,灵活性很高。实际使用中我会把判别器的感受野设置为70×70(对应每个输出节点的感知区域),这个数值在原始论文和大量复现中被验证是有效的平衡点。
此外,为了让判别器也能关注到空洞区域本身,我实现了全局判别和局部判别两个分支:全局判别器看整张修复后的图像,局部判别器只裁剪空洞区域及其周边一小块进行判断。两个分支的损失相加作为最终判别损失。这样生成器就必须同时保证“整体和谐”和“局部逼真”。
2.3 损失函数的设计思路
这是整个项目里最需要花心思的地方。图像修复的损失函数不能只用GAN那套对抗损失,否则模型训练不稳定,容易输出带有伪影的图像。我采用了一个复合损失函数,由以下几部分组成:
L1损失(像素重建损失):计算生成图像和真实图像在像素级别的绝对误差。L1损失相比L2(MSE)损失的优点是它对离群点不敏感,生成的图像边缘更锐利,不会像L2那样把结果平均得过于平滑。
感知损失(Perceptual Loss):把生成图像和真实图像分别送入预训练的VGG16网络,取中间若干层的特征图做L1距离。这个损失衡量的是“语义层面的差异”而非像素层面的差异。举例来说,两张图像素值可能有差异,但人眼看都是“一只猫”,它们的高层特征就应该是接近的。这个损失能显著改善修复结果的语义合理性。
风格损失(Style Loss):计算特征图的Gram矩阵差异,用来约束生成图像的纹理风格与全局一致。在修复背景纹理(草地、墙壁、水面)时效果特别明显。
对抗损失(Adversarial Loss):使用LSGAN的损失形式(最小二乘损失),用判别器输出的特征计算。选择LSGAN而不是原始GAN的交叉熵损失,是因为LSGAN的梯度更平滑,训练更稳定,生成的图像质量更高。
空洞区域加权损失:在计算L1损失时,对空洞区域内的像素施加更大的权重,因为修复任务的核心就是把空洞区域填好,网络应该把主要“精力”放在这里。
这几种损失叠加在一起,如果比例不合适,会出现互相拉扯的情况,比如像素损失过大导致图像模糊,对抗损失过大会导致训练震荡。我最终使用的比例是:L1损失权重1.0,感知损失权重0.1,风格损失权重120(因为Gram矩阵数值很大),对抗损失权重0.01,空洞区域权重放大为普通区域的10倍。这个比例是通过多次实验调出来的,可作为初值参考,具体任务还需微调。
3. 环境配置与数据集准备
3.1 Python环境与依赖安装
这个项目我建议使用Python 3.8或3.9版本。PyTorch建议安装1.10以上版本,Linux和Windows均可运行,不过训练推荐用Linux + NVIDIA显卡,Windows上做推理和调试也没问题。如果只是做课程设计,CPU也能跑起来,只是训练速度会慢很多,建议用预训练模型直接做推理演示。
创建虚拟环境并安装依赖的步骤如下:
conda create -n inpainting python=3.8 conda activate inpainting pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy pillow tqdm tensorboard matplotlib需要说明的是,CUDA版本的PyTorch安装要结合自己机器上的显卡驱动版本来选。可以用nvidia-smi查看驱动支持的CUDA版本,选择对应的安装命令。如果驱动版本较老,就装cpu版本先跑通流程,后续再去申请带GPU的服务器训练。
3.2 数据集选择与掩码生成
训练数据我优先推荐 Places2 数据集,这是图像修复领域最常用的基准数据集,包含超过800万张场景图片,涵盖各种室内外场景,很适合训练泛化能力强的修复模型。如果你的机器资源有限,也可以先用COCO数据集的一个子集,或者干脆用torchvision.datasets.Place365接口加载一部分数据来跑通全流程。我当时初学的时候,最早是拿几百张自然风光图片训练,效果其实也还过得去,关键是流程先跑通。
数据预处理中有一环特别重要,就是随机掩码的生成。掩码是一个与输入图像同尺寸的二值矩阵,取值为1的位置表示缺失区域,取值为0的位置表示有效区域。掩码形状需要考虑多样性:矩形块、不规则多边形、细长条、多个小区域混合,这样才能让模型见到丰富的损坏场景,防止过拟合到某种固定形状。我写了一个掩码生成器,每次训练迭代时随机生成不同类型的掩码,并限制空洞占比在10%到40%之间。占比太小,模型学不到东西;占比太大,上下文信息不足,生成难度过高,训练也不稳定。
def generate_mask(shape, max_vertex=8, max_angle=4): mask = np.zeros(shape, dtype=np.uint8) num_polygons = random.randint(1, 4) for _ in range(num_polygons): vertex_count = random.randint(3, max_vertex) points = [] for _ in range(vertex_count): x = random.randint(0, shape[1]) y = random.randint(0, shape[0]) points.append((x, y)) cv2.fillPoly(mask, [np.array(points)], 1) if random.random() < 0.5: mask = np.maximum(mask, generate_rectangle(shape)) return mask训练时,原图和掩码合在一起作为生成器输入,掩码单独用于损失函数计算时区分有效和无效区域。注意别忘记在数据增强阶段保持掩码和图像的同步变换,比如随机翻转、缩放时,图像变换了,掩码也必须做一模一样的变换,否则掩码标注的位置就和实际损坏位置对不上了。
4. 模型实现与训练流程
4.1 项目目录结构
一个规范的工程目录可以让整个项目加分不少,尤其毕设要交文档和代码,结构清晰很重要。我采用下面的组织方式:
GAN-Inpainting/ ├── config.py # 配置文件,集中管理超参数 ├── datasets.py # 数据加载和掩码生成 ├── models/ │ ├── generator.py # 生成器网络定义 │ ├── discriminator.py # 判别器网络定义 │ └── losses.py # 损失函数定义 ├── train.py # 训练主脚本 ├── test.py # 推理脚本 ├── utils.py # 工具函数(可视化、保存模型等) ├── checkpoints/ # 模型权重保存位置 └── results/ # 修复结果输出配置文件单独拆出来的好处是调参不用改源码,所有超参数集中在一个文件里,一目了然。我在config.py里配置了batch size、学习率、训练轮数、损失权重、GPU编号等参数,这也是很多参考项目不太注意的细节,但对用户使用体验来说影响却很大。
4.2 生成器实现细节
生成器分为两个阶段。第一阶段是粗修复网络,先生成一个模糊但结构正确的初步结果;第二阶段是精修复网络,输入粗修复结果和掩码,再输出一个细节更丰富的最终结果。这种两阶段设计来自PatchMatch等经典工作的启发,也被证明能显著提升修复质量。
粗修复网络结构相对简单:编码器使用6层卷积,解码器使用最近邻上采样加卷积,中间输出特征图尺寸是32×32。精修复网络在这个基础上增加了注意力层。注意力层的核心作用是让模型在特征层面“搜索”与空洞区域相似的特征块,并用相似块的信息来填充空洞对应的特征位置。打个比方:如果图像里有一块天空被抠掉了,注意力层会在图像其他部位找到同样是蓝色的、纹理接近的天空区域,把那个区域的特征“搬运”到空洞位置。虽然注意力机制不是完美的,但确实能有效减少修复时常见的重复纹理和结构错乱。
以下是精修复网络中注意力层的关键实现(简化版):
class AttentionModule(nn.Module): def __init__(self, in_channels): super().__init__() self.conv_f = nn.Conv2d(in_channels, in_channels // 2, kernel_size=1) self.conv_g = nn.Conv2d(in_channels, in_channels // 2, kernel_size=1) self.conv_v = nn.Conv2d(in_channels, in_channels, kernel_size=1) self.softmax = nn.Softmax(dim=-1) def forward(self, x): B, C, H, W = x.shape f = self.conv_f(x).view(B, -1, H * W).permute(0, 2, 1) # 关键点query g = self.conv_g(x).view(B, -1, H * W) # 关键点key v = self.conv_v(x).view(B, -1, H * W) # 用于聚合的value attn = self.softmax(torch.bmm(f, g)) # 注意力权重 out = torch.bmm(v, attn.permute(0, 2, 1)) return out.view(B, C, H, W)这里注意:注意力机制的数值稳定性靠softmax保证,但实际训练时注意力权重容易出现极端分布,建议在后面加一层LayerNorm或者残差连接来稳定训练。这是我踩过的一个坑,后面会细说。
4.3 训练循环与损失计算
训练循环采用的是“先训判别器,再训生成器”交替进行的策略,这也是GAN训练的标准范式。每一步迭代中,先固定生成器参数,更新判别器;然后固定判别器参数,更新生成器。两个优化器都使用Adam,学习率设为0.0002,beta1设为0.5(GAN训练中Adam的beta1通常不使用默认的0.9,因为0.5能让训练更稳定)。
判别器的输入有两类:一类是真实图像拼接对应区域的掩码,标签设为1(真);另一类是生成器输出的修复图拼接掩码,标签设为0(假)。判别损失使用最小二乘形式(LSGAN):
# 判别器损失:希望真实图像的判别结果接近1,生成图像的判别结果接近0 real_loss = torch.mean((d_real - 1) ** 2) fake_loss = torch.mean(d_fake ** 2) d_loss = 0.5 * (real_loss + fake_loss)生成器损失包括前面讲的四种损失总和,但注意对抗损失的反向传播:生成器要尽量让判别器把生成图像判为真实,所以对抗损失里生成图像对应的标签设为1,这部分的梯度会流回生成器,迫使生成器生成更真实的图像。我开始学的时候经常在这搞混,需要特别留意。
4.4 训练参数配置
训练参数我建议从小的开始试,不要一上来就跑大数据集。下面是验证阶段效果不错的配置:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 输入图像大小 | 256×256 | 兼顾显存和效果,太小细节丢失严重 |
| Batch Size | 8(单卡) | 显存不够就降到4或2 |
| 初始学习率 | 0.0002 | Adam优化器默认配置,常用 |
| 训练轮数 | 100 | 视数据集规模和Loss收敛情况调整 |
| 损失权重 | 见上文 | 根据任务微调 |
| 学习率衰减 | 每30轮×0.5 | 让后期训练更稳定 |
如果显存只有8G,输入尺寸降到128×128,batch size设为4也能训练,只是修复细节会打折。我建议课程设计用256×256,最多一个晚上能跑完一个小数据集;毕设如果时间充裕,可以完整在Places2的子集上训练3到5天。
5. 推理流程与效果评估
5.1 推理脚本的设计
训练完成之后,推理部分反而简单。加载训练好的生成器权重,输入一张图像和掩码,前向传播一次就得到修复结果。但有几个实用细节需要注意:
掩码的预处理:掩码需要和训练时保持一致的数据处理方式,包括归一化到0到1之间、resize到相同尺寸。推理时常用的是把掩码区域像素值置为0或127灰度值,我在推理代码中提供了一个可选参数,可以把输入图像中损坏区用均值填充。因为网络训练时看到的是带掩码的输入,推理时保持相同模式效果才会一致。
重叠区域的平滑:如果用户输入的掩码区域不规则或者边界特别生硬,修复结果可能会有边缘痕迹。可以在推理后对修复区域和原图进行边界融合,推荐使用OpenCV的seamlessClone做泊松融合,或者简单地在空洞边界做一个高斯模糊的alpha blending。
5.2 模型效果评估指标
除了肉眼观察,还需要用客观指标评估模型效果。常用的两个指标是PSNR(峰值信噪比)和SSIM(结构相似性),这两个指标都是将修复结果与真实原图对比:
- PSNR越大越好(一般25dB以上算不错,30dB以上就是比较理想了),它衡量的是像素级别的误差大小。
- SSIM越接近1越好,它衡量的是结构信息的保持程度,也就是人眼感知上的相似性。
但这两个指标都有局限,它们对纹理细节的评判并不准确。有时PSNR很高的修复图像,人眼看起来却怪怪的,因为结构合理但纹理过于平滑(这是L1/L2损失的常见问题)。所以我的经验是:指标作为参考,最终效果还是要靠人眼判断——把修复结果放大到100%看纹理细节,看边缘是否锐利,看空洞区域和周围光照是不是协调。
5.3 结果可视化与演示
给毕设答辩做演示时,我强烈建议做一个直观的对比图展示:一行展示原图,一行展示带掩码的残缺图,一行展示修复结果。最好能挑一组“修复前后对比”和“真实原图对比”同时出现在一张图上的样例,这种效果比口头解释强太多。
给个参考代码,用matplotlib可以快速生成对比图:
import matplotlib.pyplot as plt def visualize(original, masked, output, save_path): fig, axes = plt.subplots(1, 3, figsize=(15, 5)) titles = ['Original', 'Masked', 'Output'] images = [original, masked, output] for ax, img, title in zip(axes, images, titles): ax.imshow(img) ax.set_title(title) ax.axis('off') plt.tight_layout() plt.savefig(save_path, dpi=150, bbox_inches='tight')如果想让演示更“抓眼球”,可以录制一个动态过程:输入一张损坏图,把模型在多个迭代步(比如用不同训练轮数的checkpoint推理,或者使用GAN的插值特性生成一系列渐进修复的结果)的输出做成GIF,展示模型从一团模糊到细节清晰的变化。答辩现场放这个视频,基本能让评委眼前一亮。
6. 常见问题与排查技巧实录
这一部分是我在实际跑项目时真正遇到过的坑,有些问题甚至花了好几天才定位。写出来供大家参考:
Q1:训练不收敛,Loss值振荡剧烈
常见原因有三个:学习率过高、batch size过小、判别器能力太强(生成器跟不上)。排查思路是:先把学习率降到0.0001或更低试试,看Loss曲线是否趋于平滑;如果波动仍然很大,就在训练循环里降低判别器的更新频率,比如每训练2次判别器才训练1次生成器,用这种“不平衡训练”来稳定对抗过程。还有一个容易被忽视的细节:训练开始时最好用较大的L1损失权重,让生成器先学会“填对内容”,后期再逐步加大对抗损失的权重,这样生成器不容易在前期就学崩。
Q2:生成结果出现棋盘格伪影
棋盘格伪影通常是不合理的上采样方式导致的。我之前用转置卷积做上采样时,生成图像总是有一块一块的方格状纹理,非常难消除。查了一圈发现是转置卷积的“不均匀重叠”问题——卷积核在滑动时,某些位置重叠次数多,某些位置少,导致像素强度不均匀。解决办法有两个:一是改用最近邻上采样或双线性插值先放大再普通卷积;二是用PixelShuffle操作。我建议直接换成最近邻上采样+普通卷积的组合,省事且效果稳定。
Q3:修复结果边缘模糊,边界过渡不自然
这个问题的核心是掩码边界信息没有被网络有效利用。我当时的解决方案是:在输入层面,把掩码做一次高斯模糊(模拟边界渐变),让网络知道边缘附近有过渡区域;在损失层面,对空洞边界附近像素的L1损失权重进一步加大,让网络更关注边界的生成质量。
Q4:Attention层导致显存溢出
注意力机制的计算复杂度是O(H²W²),特征图越大显存占用越高。我在一张512×512的图上训练时,直接OOM了。解决办法:带注意力层之前先下采样,在32×32或更小分辨率上做注意力计算,再把结果上采样回原尺寸;或者用稀疏注意力/窗口注意力来替代全局注意力。对图像修复来说,在低分辨率上做全局注意力、在高分辨率上用卷积细化,效果就已经足够好了。
Q5:生成的图像色调偏灰或整体偏暗
这通常是因为训练数据没有做合适的归一化,或者生成器最后一层用了Sigmoid激活而数据范围没有映射对。我当时犯的错误是数据归一化到[0,1]但生成器最后用Tanh(输出范围[-1,1]),导致结果整体偏暗。要保证数据预处理和网络输出激活函数匹配一致:如果是Tanh,数据就要归一化到[-1,1];如果是Sigmoid,数据范围应该是[0,1]。
Q6:模型在验证集上效果不错但测试集上很差
这是典型的过拟合。图像修复模型对训练数据分布很敏感,如果训练集里全是室内场景,拿到户外照片上就崩。缓解办法是数据增强(随机旋转、色彩抖动、水平翻转)和加大数据多样性。另外可以试试在预训练VGG特征上做微调,把感知损失的权重适当调高,因为感知损失本身能提供一定的泛化能力——它让模型学到的不是“记住像素”,而是“生成语义合理的结构”。
7. 项目文档与后续扩展建议
7.1 项目文档应包含哪些内容
这个标题是“源码+项目文档”,所以文档部分也需要用心。一份完整的项目文档至少应包含这些内容:项目背景和意义、国内外研究现状(可引用几篇经典论文如Partial Convolution、EdgeConnect、GAN Inpainting)、技术路线图、核心算法的推导过程、实验环境配置说明、数据集描述、模型结构图和参数量表、训练过程记录(Loss曲线和评估指标变化)、结果展示(修复前后对比图)、创新点的总结、以及可行的改进方向。
文档里的图表很重要,架构图、流程图、结果对比图,每个都值得认真画。这些不只是为了“凑篇幅”,而是老师或评审判断你是否真正理解项目的直接依据。
7.2 后续可以扩展的方向
如果这个项目做完还有余力,有几个很容易出彩的扩展方向:
从单一掩码到任意掩码:增加掩码生成器的复杂度,训练模型支持用户手动涂鸦式掩码,做成一个交互式修复工具,演示效果会非常好。
从图像修复到视频修复:把单帧修复扩展到视频序列,关键是保持时序一致性,这又可以作为研究生级别的研究方向。
从CNN到Transformer:尝试把生成器的骨干网络替换为Swin Transformer或者结合ViT的结构,看看对长距离依赖关系的建模能力能否带来进一步的提升。
结合文字引导修复:加入CLIP等跨模态模型,让用户用文字描述“这里应该是什么”,模型根据语义描述来修复空洞。这个方向目前是研究热点,做出简单版demo并不难,但足够展示技术深度。
根据我个人经验,最值得投入精力的扩展方向是第一个——交互式修复工具。因为它的演示效果最好,也最能体现“从模型到产品”的完整链路。其他方向在答辩时列在“未来展望”里就够有分量了。最后再说一个小的实用建议:训练过程中定期用TensorBoard记录损失曲线和生成结果,不仅方便调试,后续写文档写实验报告时直接截图就是现成的素材,省去补实验的时间。这些积累,最后都会变成项目里最亮眼的部分。
本文还有配套的精品资源,点击获取