昇思25天学习打卡营第7天|Pix2Pix实现图像转换

文章目录

      • 昇思MindSpore应用实践
        • 基于MindSpore的Pix2Pix图像转换
          • 1、Pix2Pix 概述
          • 2、U-Net架构
            • 定义UNet Skip Connection Block
          • 2、生成器部分
          • 3、基于PatchGAN的判别器
          • 4、Pix2Pix的生成器和判别器初始化
          • 5、模型训练
          • 6、模型推理
      • Reference

昇思MindSpore应用实践

本系列文章主要用于记录昇思25天学习打卡营的学习心得。

基于MindSpore的Pix2Pix图像转换
1、Pix2Pix 概述

Pix2Pix 是一个专门为图像到图像的转换任务设计的网络,可以实现语义/标签到真实图片、灰度图到彩色图、航空图到地图、白天到黑夜、线稿图到实物图的转换。Pix2Pix是将条件GAN(CGAN)应用于有监督(需要成对的输入素描图像Sketch和真实图像GT,来训练网络)的图像到图像翻译的经典之作,和所有的GANs一样,模型同样包括:生成器判别器两个部分。

CGAN:CGAN(条件GAN) 的目标是生成与给定条件匹配的数据样本。这些条件可以是标签、部分实例标注数据或任何其他形式的多模态辅助信息。CGAN 通过将条件并入网络的生成器和判别器中来指导数据生成过程。
在这里插入图片描述
相比普通的生成对抗损失:
L G A N ( G , D ) = E y [ l o g ( D ( y ) ) ] + E ( x , z ) [ l o g ( 1 − D ( x , z ) ) ] L_{GAN}(G,D)=\mathbb{E}_{y}[log(D(y))]+\mathbb{E}_{(x,z)}[log(1-D(x,z))] LGAN(G,D)=Ey[log(D(y))]+E(x,z)[log(1D(x,z))]

  • x x x:代表观测图像的数据。
  • z z z:代表随机噪声的数据。
  • y = G ( x , z ) y=G(x,z) y=G(x,z):生成器网络,给出由观测图像 x x x与随机噪声 z z z生成的“假”图片,其中 x x x来自于训练数据而非生成器。
  • D ( x , G ( x , z ) ) D(x,G(x,z)) D(x,G(x,z)):判别器网络,给出图像判定为真实图像的概率,其中 x x x来自于训练数据, G ( x , z ) G(x,z) G(x,z)来自于生成器。

CGAN多了来自于观测图像的条件 x x x(因此Pix2Pix训练时采用有监督的方式,需要标注好的语义数据,如下图中的
Map2Aerial数据集、Anime Sketch Colorization Pair 素描生成动漫数据集),
在这里插入图片描述
在这里插入图片描述

CGAN的目标可以表示为:

L C G A N ( G , D ) = E ( x , y ) [ l o g ( D ( x , y ) ) ] + E ( x , z ) [ l o g ( 1 − D ( x , G ( x , z ) ) ) ] L_{CGAN}(G,D)=\mathbb{E}_{(x,y)}[log(D(x,y))]+\mathbb{E}_{(x,z)}[log(1-D(x,G(x,z)))] LCGAN(G,D)=E(x,y)[log(D(x,y))]+E(x,z)[log(1D(x,G(x,z)))]

Pix2Pix 还包括 L1 损失,帮助生成器产生结构上接近真实图像的结果,这一点在图像翻译任务中尤为重要:
L L 1 ( G ) = E ( x , y , z ) [ ∣ ∣ y − G ( x , z ) ∣ ∣ 1 ] L_{L1}(G)=\mathbb{E}_{(x,y,z)}[||y-G(x,z)||_{1}] LL1(G)=E(x,y,z)[∣∣yG(x,z)1]

进而得到最终目标:

a r g min ⁡ G max ⁡ D L C G A N ( G , D ) + λ L L 1 ( G ) arg\min_{G}\max_{D}L_{CGAN}(G,D)+\lambda L_{L1}(G) argGminDmaxLCGAN(G,D)+λLL1(G)

图像转换问题本质上其实就是像素到像素的映射问题,Pix2Pix使用完全一样的网络结构和目标函数,仅更换不同的训练数据集就能分别实现以上的任务。

2、U-Net架构

U-Net架构:Pix2Pix 使用 U-Net 架构作为其生成器,在传统的编解码网络结构基础上加入了跳跃连接的方式,可以更好地捕捉图像的细节和上下文信息,适合于图像到图像的翻译任务。相比于普通的编解码结构(Encoder-Decoder),U-Net在编码器和解码器之间引入了跳跃连接,极大地改善了梯度流:
在这里插入图片描述

定义UNet Skip Connection Block
import mindspore
import mindspore.nn as nn
import mindspore.ops as opsclass UNetSkipConnectionBlock(nn.Cell):def __init__(self, outer_nc, inner_nc, in_planes=None, dropout=False,submodule=None, outermost=False, innermost=False, alpha=0.2, norm_mode='batch'):super(UNetSkipConnectionBlock, self).__init__()down_norm = nn.BatchNorm2d(inner_nc)up_norm = nn.BatchNorm2d(outer_nc)use_bias = Falseif norm_mode == 'instance':down_norm = nn.BatchNorm2d(inner_nc, affine=False)up_norm = nn.BatchNorm2d(outer_nc, affine=False)use_bias = Trueif in_planes is None:in_planes = outer_ncdown_conv = nn.Conv2d(in_planes, inner_nc, kernel_size=4,stride=2, padding=1, has_bias=use_bias, pad_mode='pad')down_relu = nn.LeakyReLU(alpha)up_relu = nn.ReLU()if outermost:up_conv = nn.Conv2dTranspose(inner_nc * 2, outer_nc,kernel_size=4, stride=2,padding=1, pad_mode='pad')down = [down_conv]up = [up_relu, up_conv, nn.Tanh()]model = down + [submodule] + upelif innermost:up_conv = nn.Conv2dTranspose(inner_nc, outer_nc,kernel_size=4, stride=2,padding=1, has_bias=use_bias, pad_mode='pad')down = [down_relu, down_conv]up = [up_relu, up_conv, up_norm]model = down + upelse:up_conv = nn.Conv2dTranspose(inner_nc * 2, outer_nc,kernel_size=4, stride=2,padding=1, has_bias=use_bias, pad_mode='pad')down = [down_relu, down_conv, down_norm]up = [up_relu, up_conv, up_norm]model = down + [submodule] + upif dropout:model.append(nn.Dropout(p=0.5))self.model = nn.SequentialCell(model)self.skip_connections = not outermostdef construct(self, x):out = self.model(x)if self.skip_connections:out = ops.concat((out, x), axis=1)return out
2、生成器部分

原始CGAN的输入是条件x和噪声z两种信息,这里的生成器只使用了条件信息,因此不能生成多样性的结果。因此Pix2Pix在训练和测试时都使用了dropout,这样可以生成多样性的结果。

通过MindSpore实现基于U-Net的生成器:

class UNetGenerator(nn.Cell):def __init__(self, in_planes, out_planes, ngf=64, n_layers=8, norm_mode='bn', dropout=False):super(UNetGenerator, self).__init__()unet_block = UNetSkipConnectionBlock(ngf * 8, ngf * 8, in_planes=None, submodule=None,norm_mode=norm_mode, innermost=True)for _ in range(n_layers - 5):unet_block = UNetSkipConnectionBlock(ngf * 8, ngf * 8, in_planes=None, submodule=unet_block,norm_mode=norm_mode, dropout=dropout)unet_block = UNetSkipConnectionBlock(ngf * 4, ngf * 8, in_planes=None, submodule=unet_block,norm_mode=norm_mode)unet_block = UNetSkipConnectionBlock(ngf * 2, ngf * 4, in_planes=None, submodule=unet_block,norm_mode=norm_mode)unet_block = UNetSkipConnectionBlock(ngf, ngf * 2, in_planes=None, submodule=unet_block,norm_mode=norm_mode)self.model = UNetSkipConnectionBlock(out_planes, ngf, in_planes=in_planes, submodule=unet_block,outermost=True, norm_mode=norm_mode)def construct(self, x):return self.model(x)
3、基于PatchGAN的判别器

判别器使用的PatchGAN结构,可看做卷积。
生成的矩阵中的每个点代表原图的一小块区域(patch)。通过矩阵中的各个值来判断原图中对应每个Patch的真假。

import mindspore.nn as nnclass ConvNormRelu(nn.Cell):def __init__(self,in_planes,out_planes,kernel_size=4,stride=2,alpha=0.2,norm_mode='batch',pad_mode='CONSTANT',use_relu=True,padding=None):super(ConvNormRelu, self).__init__()norm = nn.BatchNorm2d(out_planes)if norm_mode == 'instance':norm = nn.BatchNorm2d(out_planes, affine=False)has_bias = (norm_mode == 'instance')if not padding:padding = (kernel_size - 1) // 2if pad_mode == 'CONSTANT':conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, pad_mode='pad',has_bias=has_bias, padding=padding)layers = [conv, norm]else:paddings = ((0, 0), (0, 0), (padding, padding), (padding, padding))pad = nn.Pad(paddings=paddings, mode=pad_mode)conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, pad_mode='pad', has_bias=has_bias)layers = [pad, conv, norm]if use_relu:relu = nn.ReLU()if alpha > 0:relu = nn.LeakyReLU(alpha)layers.append(relu)self.features = nn.SequentialCell(layers)def construct(self, x):output = self.features(x)return outputclass Discriminator(nn.Cell):def __init__(self, in_planes=3, ndf=64, n_layers=3, alpha=0.2, norm_mode='batch'):super(Discriminator, self).__init__()kernel_size = 4layers = [nn.Conv2d(in_planes, ndf, kernel_size, 2, pad_mode='pad', padding=1),nn.LeakyReLU(alpha)]nf_mult = ndffor i in range(1, n_layers):nf_mult_prev = nf_multnf_mult = min(2 ** i, 8) * ndflayers.append(ConvNormRelu(nf_mult_prev, nf_mult, kernel_size, 2, alpha, norm_mode, padding=1))nf_mult_prev = nf_multnf_mult = min(2 ** n_layers, 8) * ndflayers.append(ConvNormRelu(nf_mult_prev, nf_mult, kernel_size, 1, alpha, norm_mode, padding=1))layers.append(nn.Conv2d(nf_mult, 1, kernel_size, 1, pad_mode='pad', padding=1))self.features = nn.SequentialCell(layers)def construct(self, x, y):x_y = ops.concat((x, y), axis=1)output = self.features(x_y)return output
4、Pix2Pix的生成器和判别器初始化

实例化Pix2Pix生成器和判别器:

import mindspore.nn as nn
from mindspore.common import initializer as initg_in_planes = 3
g_out_planes = 3
g_ngf = 64
g_layers = 8
d_in_planes = 6
d_ndf = 64
d_layers = 3
alpha = 0.2
init_gain = 0.02
init_type = 'normal'net_generator = UNetGenerator(in_planes=g_in_planes, out_planes=g_out_planes,ngf=g_ngf, n_layers=g_layers)
for _, cell in net_generator.cells_and_names():if isinstance(cell, (nn.Conv2d, nn.Conv2dTranspose)):if init_type == 'normal':cell.weight.set_data(init.initializer(init.Normal(init_gain), cell.weight.shape))elif init_type == 'xavier':cell.weight.set_data(init.initializer(init.XavierUniform(init_gain), cell.weight.shape))elif init_type == 'constant':cell.weight.set_data(init.initializer(0.001, cell.weight.shape))else:raise NotImplementedError('initialization method [%s] is not implemented' % init_type)elif isinstance(cell, nn.BatchNorm2d):cell.gamma.set_data(init.initializer('ones', cell.gamma.shape))cell.beta.set_data(init.initializer('zeros', cell.beta.shape))net_discriminator = Discriminator(in_planes=d_in_planes, ndf=d_ndf,alpha=alpha, n_layers=d_layers)
for _, cell in net_discriminator.cells_and_names():if isinstance(cell, (nn.Conv2d, nn.Conv2dTranspose)):if init_type == 'normal':cell.weight.set_data(init.initializer(init.Normal(init_gain), cell.weight.shape))elif init_type == 'xavier':cell.weight.set_data(init.initializer(init.XavierUniform(init_gain), cell.weight.shape))elif init_type == 'constant':cell.weight.set_data(init.initializer(0.001, cell.weight.shape))else:raise NotImplementedError('initialization method [%s] is not implemented' % init_type)elif isinstance(cell, nn.BatchNorm2d):cell.gamma.set_data(init.initializer('ones', cell.gamma.shape))cell.beta.set_data(init.initializer('zeros', cell.beta.shape))class Pix2Pix(nn.Cell):"""Pix2Pix模型网络"""def __init__(self, discriminator, generator):super(Pix2Pix, self).__init__(auto_prefix=True)self.net_discriminator = discriminatorself.net_generator = generatordef construct(self, reala):fakeb = self.net_generator(reala)return fakeb
5、模型训练

训练分为两个主要部分:训练判别器和训练生成器;
训练判别器的目的是最大程度地提高判别图像真伪的概率;
训练生成器是希望能产生更好的虚假图像;
在这两个部分中,分别获取训练过程中的损失,并在每个周期结束时进行统计。

通过MindSpore进行训练:

import numpy as np
import os
import datetime
from mindspore import value_and_grad, Tensorepoch_num = 3
ckpt_dir = "results/ckpt"
dataset_size = 400
val_pic_size = 256
lr = 0.0002
n_epochs = 100
n_epochs_decay = 100def get_lr():lrs = [lr] * dataset_size * n_epochslr_epoch = 0for epoch in range(n_epochs_decay):lr_epoch = lr * (n_epochs_decay - epoch) / n_epochs_decaylrs += [lr_epoch] * dataset_sizelrs += [lr_epoch] * dataset_size * (epoch_num - n_epochs_decay - n_epochs)return Tensor(np.array(lrs).astype(np.float32))dataset = ds.MindDataset("./dataset/dataset_pix2pix/train.mindrecord", columns_list=["input_images", "target_images"], shuffle=True, num_parallel_workers=1)
steps_per_epoch = dataset.get_dataset_size()
loss_f = nn.BCEWithLogitsLoss()
l1_loss = nn.L1Loss()def forword_dis(reala, realb):lambda_dis = 0.5fakeb = net_generator(reala)pred0 = net_discriminator(reala, fakeb)pred1 = net_discriminator(reala, realb)loss_d = loss_f(pred1, ops.ones_like(pred1)) + loss_f(pred0, ops.zeros_like(pred0))loss_dis = loss_d * lambda_disreturn loss_disdef forword_gan(reala, realb):lambda_gan = 0.5lambda_l1 = 100fakeb = net_generator(reala)pred0 = net_discriminator(reala, fakeb)loss_1 = loss_f(pred0, ops.ones_like(pred0))loss_2 = l1_loss(fakeb, realb)loss_gan = loss_1 * lambda_gan + loss_2 * lambda_l1return loss_gand_opt = nn.Adam(net_discriminator.trainable_params(), learning_rate=get_lr(),beta1=0.5, beta2=0.999, loss_scale=1)
g_opt = nn.Adam(net_generator.trainable_params(), learning_rate=get_lr(),beta1=0.5, beta2=0.999, loss_scale=1)grad_d = value_and_grad(forword_dis, None, net_discriminator.trainable_params())
grad_g = value_and_grad(forword_gan, None, net_generator.trainable_params())def train_step(reala, realb):loss_dis, d_grads = grad_d(reala, realb)loss_gan, g_grads = grad_g(reala, realb)d_opt(d_grads)g_opt(g_grads)return loss_dis, loss_ganif not os.path.isdir(ckpt_dir):os.makedirs(ckpt_dir)g_losses = []
d_losses = []
data_loader = dataset.create_dict_iterator(output_numpy=True, num_epochs=epoch_num)for epoch in range(epoch_num):for i, data in enumerate(data_loader):start_time = datetime.datetime.now()input_image = Tensor(data["input_images"])target_image = Tensor(data["target_images"])dis_loss, gen_loss = train_step(input_image, target_image)end_time = datetime.datetime.now()delta = (end_time - start_time).microsecondsif i % 2 == 0:print("ms per step:{:.2f}  epoch:{}/{}  step:{}/{}  Dloss:{:.4f}  Gloss:{:.4f} ".format((delta / 1000), (epoch + 1), (epoch_num), i, steps_per_epoch, float(dis_loss), float(gen_loss)))d_losses.append(dis_loss.asnumpy())g_losses.append(gen_loss.asnumpy())if (epoch + 1) == epoch_num:mindspore.save_checkpoint(net_generator, ckpt_dir + "Generator.ckpt")

在这里插入图片描述

6、模型推理

导入模型训练保存的权重:

from mindspore import load_checkpoint, load_param_into_netparam_g = load_checkpoint(ckpt_dir + "Generator.ckpt")
load_param_into_net(net_generator, param_g)
dataset = ds.MindDataset("./dataset/dataset_pix2pix/train.mindrecord", columns_list=["input_images", "target_images"], shuffle=True)
data_iter = next(dataset.create_dict_iterator())
predict_show = net_generator(data_iter["input_images"])
plt.figure(figsize=(10, 3), dpi=140)
for i in range(10):plt.subplot(2, 10, i + 1)plt.imshow((data_iter["input_images"][i].asnumpy().transpose(1, 2, 0) + 1) / 2)plt.axis("off")plt.subplots_adjust(wspace=0.05, hspace=0.02)plt.subplot(2, 10, i + 11)plt.imshow((predict_show[i].asnumpy().transpose(1, 2, 0) + 1) / 2)plt.axis("off")plt.subplots_adjust(wspace=0.05, hspace=0.02)
plt.show()

图像翻译效果如下:
在这里插入图片描述

Reference

昇思官方文档-Pix2Pix实现图像转换
昇思大模型平台
AI 助你无码看片,生成对抗网络(GAN)大显身手

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/39572.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python基础003

Python流程控制基础 1.条件语句 内置函数input a input("请输入一段内容:") print(a) print(type(a))代码执行的时候遇到input函数,就会等键盘输入结果,已回车为结束标志,也就时说输入回车后代码才会执行 2.顺序执行…

pandas数据分析(5)

pandas使用Numpy的np.nan代表缺失数据,显示为NaN。NaN是浮点数标准中地Not-a-Number。对于时间戳,则使用pd.NaT,而文本使用的是None。 首先构造一组数据: 使用None或者np.nan来表示缺失的值: 清理DataFrame时&#xf…

解决数据库PGSQL,在Mybatis中创建临时表报错TODO IDENTIFIER,连接池用的Druid。更换最新版本Druid仍然报错解决

Druid版本1.1.9报错Caused by: java.sql.SQLException: sql injection violation, syntax error: TODO IDENTIFIER : CREATE TEMPORARY TABLE temp_ball_classify (id int8 NOT NULL,create_time TIMESTAMP,create_by VARCHAR,classify_name VARCHAR) 代码如下: 测…

四川蔚澜时代电子商务有限公司打造抖音电商服务新高地

在数字化浪潮汹涌澎湃的今天,电商行业以其独特的魅力和强大的市场潜力,成为了推动经济增长的新引擎。四川蔚澜时代电子商务有限公司,作为这个领域的佼佼者,正以其专业的服务、创新的理念和卓越的实力,引领抖音电商服务…

用AI,每天创作200+优质内容,2分钟教会你操作!

前段时间发布了这篇“寻找爆款文案及标题的9大渠道,直接搬运都能搞流量!”,里面我讲到如何寻找爆款标题。最近不少朋友问我,如何创作这个标题相关的内容。 多数平台都有风控规则,有些平台内容也会有字数要求。为了让大…

SpringBoot 项目整合 MyBatis 框架,附带测试示例

文章目录 一、创建 SpringBoot 项目二、添加 MyBatis 依赖三、项目结构和数据库表结构四、项目代码1、application.yml2、TestController3、TbUser4、TbUserMapper5、TestServiceImpl6、TestService7、TestApplication8、TbUserMapper.xml9、MyBatisTest 五、浏览器测试结果六、…

JavaScript实现时钟计时

会动的时钟 1.目标 2.分析 1.最开始页面不显示时间,有两个按钮 开始 暂停。开始按钮是可以点击的,暂停按钮不能点击 2.当点击开始按钮后,设置开始按钮不可用,暂停按钮可用。然后将当前系统时间放到按钮上面。每隔1秒中更新一下…

TransMIL:基于Transformer的多实例学习

MIL是弱监督分类问题的有力工具。然而,目前的MIL方法通常基于iid假设,忽略了不同实例之间的相关性。为了解决这个问题,作者提出了一个新的框架,称为相关性MIL,并提供了收敛性的证明。基于此框架,还设计了一…

3.js - 反射率(reflectivity) 、折射率(ior)

没啥太大的感觉 反射率 reflectivity 概念 反射率:指的是,材质表面反射光线的能力反射率,用于控制材质对环境光,或光源的反射程度反射率越高,材质表面反射的光线越多,看起来就越光亮使用 适用于&#xff0…

【PYG】Cora数据集分类任务计算损失,cross_entropy为什么不能直接替换成mse_loss

cross_entropy计算误差方式,输入向量z为[1,2,3],预测y为[1],选择数为2,计算出一大坨e的式子为3.405,再用-23.405计算得到1.405MSE计算误差方式,输入z为[1,2,3],预测向量应该是[1,0,0]&#xff0…

Dify入门指南

一.Dify介绍 生成式 AI 应用创新引擎,开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力,轻松构建和运营生成式 AI 原生应用,比 LangChain 更易用。一个平台,接入全球大型语言模型。不同…

德璞资本:桥水公司如何利用AI实现投资决策的精准提升?

摘要: 在金融科技的浪潮中,桥水公司推出了一只依靠机器学习决策的创新基金,吸引了大量投资者的关注。本文将深入探讨该基金的背景、AI技术的应用、对桥水公司转型的影响,以及未来发展的前景。 新基金背景:桥水公司的创…

2024年7月2日 (周二) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键,实现一键唤起、一键隐藏的 Windows 工具,并且支持窗口动态绑定快捷键(无需设置自动实现)。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 经典名作30周年新篇《恐怖惊魂夜…

MyBatis入门案例

实施前的准备工作: 1.准备数据库表2.创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)3.在application.properties文件中引入数据库连接信息4.创建对应的实体类Emp(实体类属性采用驼峰…

补浏览器环境

一,导言 // global是node中的关键字(全局变量),在node中调用其中的元素时,可以直接引用,不用加global前缀,和浏览器中的window类似;在浏览器中可能会使用window前缀:win…

校园水质信息化监管系统——水质监管物联网系统

随着物联网技术的发展越来越成熟,它不断地与人们的日常生活和工作深入融合,推动着社会的进步。其中物联网系统集成在高校实践课程中可以应用到许多项目,如环境气象检测、花卉种植信息化监管、水质信息化监管、校园设施物联网信息化改造、停车…

springboot项目jar包修改数据库配置运行时异常

一、背景 我将软件成功打好jar包了,到部署的时候发现jar包中数据库配置写的有问题,不想再重新打包了,打算直接修改配置文件,结果修改配置后,再通过java -jar运行时就报错了。 二、问题描述 本地项目是springBoot项目…

【计算机图形学 | 基于MFC三维图形开发】期末考试知识点汇总(上)

文章目录 视频教程第一章 计算机图形学概述计算机图形学的定义计算机图形学的应用计算机图形学 vs 图像处理 vs模式识别图形显示器的发展及工作原理理解三维渲染管线 第二章 基本图元的扫描转换扫描转换直线的扫描转换DDA算法Bresenham算法中点画线算法圆的扫描转换中点画圆算法…

【Godot4.2】Godot中的贝塞尔曲线

概述 通过指定平面上的多个点,然后顺次连接,我们可以得到折线段,如果闭合图形,就可以获得多边形。通过向量旋转我们可以获得圆等特殊图形。 但是对于任意曲线,我们无法使用简单的方式来获取其顶点,好在计…

mac上使用finder时候,显示隐藏的文件或者文件夹

默认在finder中是不显示隐藏的文件和文件夹的,但是想创建.gitignore文件,并向里面写入内容,即便是打开xcode也是不显示这几个隐藏文件的,那有什么办法呢? 使用快捷键: 使用finder打开包含隐藏文件的文件夹…