百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

「百战GAN」StyleGAN人脸属性(表情年龄性别)编辑代码实战

toyiye 2024-06-21 12:39 13 浏览 0 评论

大家好,欢迎来到专栏《百战GAN》,在这个专栏里,我们会进行GAN相关项目的核心思想讲解,代码的详解,模型的训练和测试等内容。

作者&编辑 | 言有三

视频加载中...

本文篇幅:7000字

背景要求:会使用Python和Pytorch

附带资料:参考论文和项目,视频讲解

1 项目背景

作为一门新兴的技术,GAN在很多人脸图像任务中有着广泛的应用,下面列举了一些典型的方向。

比如使用SRGAN进行人脸超分辨,使用BeautyGAN进行人脸美颜,使用Deepfake进行换脸。如今,基于人脸属性编辑的技术在抖音快手等应用中都是非常受欢迎的技术,比如可以体验让人变老变小,如下图年龄的更改。

年龄编辑

这背后的核心技术之一就是在生成模型的Latent空间进行属性向量的编辑,然后通过生成模型来获取最终结果图,其中最典型的代表性框架就是StyleGAN,本次我们来实战使用StyleGAN进行人脸属性编辑。

在阅读接下来的内容之前,请大家务必回顾之前StyleGAN人脸生成的内容,没有这一部分内容基础,无法学习接下来的内容。

【百战GAN】StyleGAN原理详解与人脸图像生成代码实战

【项目实战课】基于Pytorch的StyleGAN v1人脸图像生成实战

2 原理简介

现在我们要使用StyleGAN进行真实人脸的编辑,需要解决2个关键问题。

(1) 如何获得真实人脸的潜在编码向量,它对应StyleGAN中的映射网络(mapping network)的输入Z或者输出W。

(2) 如何通过修改Z或者W向量,控制生成人脸图像的高层语义属性。

接下来我们重点介绍潜在编码向量的求解,并在下一节中实验基于潜在编码向量的属性编辑。

当前对真实人脸编码向量的求取基本上基于两种思路,一种是学习一个编码器来实现映射,一种是直接对向量进行优化求解。

2.1 基于编码器的求解

基于编码器的求解框架如下图所示,由两部分模块组成。

Encoder表示需要训练的编码器,Decoder表示已经训练完的生成模型,如StyleGAN的生成器部分。真实图像输入编码器得到Z或者W,再输入生成器得到生成的人脸,完成人脸图像的重建。

通过直接学习一个编码器,可以不需要对每一张图都进行优化,实现一次训练,对任意图像都能提取潜在编码向量,但是也容易在训练数据集上发生过拟合。

2.2 基于优化求解的方法

另外一种方法就是基于优化求解,直接对每一张图片优化出对应的W,在StyleGAN v2,Image2StyleGAN等框架中采用了这种方案,并且512维的W被拓展成W+, W+为18×512维的矩阵,这样就可以对每一个自适应实例归一化(AdaIN)风格模块都使用不同W,实现更自由的属性编辑。

基于优化求解的方法包括以下几步:

(1) 给定图片I,以及预训练好的生成器G。

(2) 初始化潜在编码向量,如W,其初始值可以使用计算得到的统计平均值。

(3) 根据优化目标进行反复迭代,直到达到预设的终止条件。

优化目标的常见形式为:

其中Lpercept为特征空间中的感知损失距离,是很通用的问题,具体形式不再赘述。lamda用于平衡感知损失和MSE损失之间的权重比。可以使用梯度下降算法进行求解。

基于优化求解方法的优点是精度较高,但是优化速度慢,而且对每一个图片都必须进行优化迭代。

在求解得到了潜在编码向量后,我们就可以通过编辑向量来编辑人脸的高层语义属性,对于StyleGAN架构来说,潜在编码向量可以是Z也可以是W,一般基于W进行编码会有更好的效果。

3 人脸属性编辑实战

接下来我们实践基于StyleGAN模型的人脸属性编辑。

3.1 人脸重建

要使用StyleGAN来进行人脸编辑,首先我们需要将人脸投射到潜在编码向量空间,下面我们采用的方法是基于优化的方法,即对每一张人脸图片,单独优化求解出潜在编码向量,基本思想前面已经介绍过。

接下来我们来看人脸重建的求解,核心代码如下:

if __name__ == "__main__":
    ## 预训练模型权重
    parser.add_argument(
        "--ckpt", type=str, required=True, help="path to the model checkpoint"
    )

    ## 输出图像尺寸
    parser.add_argument(
        "--size", type=int, default=256, help="output image sizes of the generator"
    )

    ## 学习率参数
    parser.add_argument(
        "--lr_rampup",
        type=float,
        default=0.05,
        help="duration of the learning rate warmup",
    )
    parser.add_argument(
        "--lr_rampdown",
        type=float,
        default=0.25,
        help="duration of the learning rate decay",
    )
    parser.add_argument("--lr", type=float, default=0.1, help="learning rate")


    ## 噪声相关参数,噪声水平,噪声衰减范围,噪声正则化
    parser.add_argument(
        "--noise", type=float, default=0.05, help="strength of the noise level"
    )
    parser.add_argument(
        "--noise_ramp",
        type=float,
        default=1.0,
        help="duration of the noise level decay",
    )
    parser.add_argument(
        "--noise_regularize",
        type=float,
        default=10000,
        help="weight of the noise regularization",
    )

    ## MSE损失
    parser.add_argument("--mse", type=float, default=0.5, help="weight of the mse loss")

    ## 迭代次数
    parser.add_argument("--step", type=int, default=1000, help="optimize iterations")

    ## 重建图像
    parser.add_argument(
        "--files", type=str, help="path to image files to be projected"
    )

    ## 重建结果
    parser.add_argument(
        "--results", type=str, help="path to results files to be stored"
    )

## 计算学习率
def get_lr(t, initial_lr, rampdown=0.25, rampup=0.05):
    lr_ramp = min(1, (1 - t) / rampdown)
    lr_ramp = 0.5 - 0.5 * math.cos(lr_ramp * math.pi)
lr_ramp = lr_ramp * min(1, t / rampup)

return initial_lr * lr_ramp

## 合并latent向量和噪声
def latent_noise(latent, strength):
noise = torch.randn_like(latent) * strength

    return latent + noise

## 生成图片
def make_image(tensor):
    return (
        tensor.detach()
        .clamp_(min=-1, max=1)
        .add(1)
        .div_(2)
        .mul(255)
        .type(torch.uint8)
        .permute(0, 2, 3, 1)
        .to("cpu")
        .numpy()
    )

## 生成与图像大小相等的噪声
def make_noise(device,size):
    noises = []
    step = int(math.log(size, 2)) - 2
    for i in range(step + 1):
            size = 4 * 2 ** i
            noises.append(torch.randn(1, 1, size, size, device=device))
return noises

## 噪声归一化
def noise_normalize_(noises):
    for noise in noises:
        mean = noise.mean()
        std = noise.std()
        noise.data.add_(-mean).div_(std)

args = parser.parse_args()

    device = "cpu"
    ## 计算latent向量的平均次数
    n_mean_latent = 10000

    ## 获得用于计算损失的最小图像尺寸
    resize = min(args.size, 256)

    ## 预处理函数
    transform = transforms.Compose(
        [
            transforms.Resize(resize),
            transforms.CenterCrop(resize),
            transforms.ToTensor(),
            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),
        ]
    )

    ## 投影的人脸图片,将图片处理成一个batch
    imgs = []
    imgfiles = os.listdir(args.files)
    for imgfile in imgfiles:
        img = transform(Image.open(os.path.join(args.files,imgfile)).convert("RGB"))
        imgs.append(img)

    imgs = torch.stack(imgs, 0).to(device)

    ## 载入模型
    netG = StyledGenerator(512,8)
    netG.load_state_dict(torch.load(args.ckpt,map_location=device)["g_running"], strict=False)
    netG.eval()
    netG = netG.to(device)
    step = int(math.log(args.size, 2)) - 2
    with torch.no_grad():
        noise_sample = torch.randn(n_mean_latent, 512, device=device)
        latent_out = netG.style(noise_sample) ##输入噪声向量Z,输出latent向量W=latent_out
        latent_mean = latent_out.mean(0)
        latent_std = ((latent_out - latent_mean).pow(2).sum() / n_mean_latent) ** 0.5

    ## 感知损失计算
    percept = lpips.PerceptualLoss(
        model="net-lin", net="vgg", use_gpu=device.startswith("cuda")
    )

    ## 构建噪声输入
    noises_single = make_noise(device,args.size)

    noises = []
    for noise in noises_single:
        noises.append(noise.repeat(imgs.shape[0], 1, 1, 1).normal_())

    ## 初始化Z向量
    latent_in = latent_mean.detach().clone().unsqueeze(0).repeat(imgs.shape[0], 1)
    latent_in.requires_grad = True

    for noise in noises:
        noise.requires_grad = True

    optimizer = optim.Adam([latent_in] + noises, lr=args.lr)

    pbar = tqdm(range(args.step))

    ## 优化学习Z向量
    for i in pbar:
        t = i / args.step ## t的范围是(0,1)
        lr = get_lr(t, args.lr)
        optimizer.param_groups[0]["lr"] = lr

## 噪声衰减
        noise_strength = latent_std * args.noise * max(0, 1 - t / args.noise_ramp) ** 2
        latent_n = latent_noise(latent_in, noise_strength.item())
        latent_n.to(device)
        img_gen = netG([latent_n], noise=noises, step=step) ## 生成的图片
        batch, channel, height, width = img_gen.shape

## 在不超过256的分辨率上计算损失
        if height > 256:
            factor = height // 256

            img_gen = img_gen.reshape(
                batch, channel, height // factor, factor, width // factor, factor
            )
            img_gen = img_gen.mean([3, 5])

        p_loss = percept(img_gen, imgs).sum()  ## 感知损失
        n_loss = noise_regularize(noises)      ## 噪声损失
        mse_loss = F.mse_loss(img_gen, imgs)   ## MSE损失

        loss = p_loss + args.noise_regularize * n_loss + args.mse * mse_loss

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        noise_normalize_(noises)

        pbar.set_description(
            (
                f"perceptual: {p_loss.item():.4f}; noise regularize: {n_loss.item():.4f};"
                f" mse: {mse_loss.item():.4f}; loss: {loss.item():.4f}; lr: {lr:.4f}"
            )
        )

    ## 重新生成高分辨率图片
    img_gen = netG([latent_in], noise=noises,step=step)
    img_ar = make_image(img_gen)

    result_file = {}
    for i, input_name in enumerate(imgfiles):
        noise_single = []
        for noise in noises:
            noise_single.append(noise[i : i + 1])

        print("i="+str(i)+"; len of imgs:"+str(len(img_gen)))
        result_file[input_name] = {
            "img": img_gen[i],
            "latent": latent_in[i],
            "noise": noise_single,
        }

        img_name = os.path.join(args.results,input_name)
        pil_img = Image.fromarray(img_ar[i])
        pil_img.save(img_name) ##存储图片
        np.save(os.path.join(args.results,input_name.split('.')[0]+'.npy'),latent_in[i].detach().numpy())##存储latent向量

在上面的代码中,latent_in就是要优化学习的潜在变量,当使用netG([latent_n], noise=noises, step=step)的调用方式时,latent_n是映射网络(mapping network)的向量Z,它由latent_in和随着迭代不断衰减的噪声向量组成,此时没有输入平均风格向量。

根据之前我们对生成器模型结构的解读,此时只有latent_n会影响生成的风格,因此为基于向量Z的重建方法。

当然我们也可以通过将latent_n设为平均风格向量,将混合权重style_weight设置为0,这样也只有latent_n会影响生成的风格,并且它会作为生成网络(synthetis network)的输入,即向量W,此时就是基于向量W的重建方法。

关于感知损失的计算,以及预训练模型的解读,请大家自行完成,或者阅读文后的讲解视频。

下面我们会比较这两种重建方法的差异。

学习率采用warmup策略,即先增大再减小,最大不超过0.1。感知损失、MSE损失、噪声正则化损失的权重分别为1.0,1.0,10000,噪声相关的幅度、衰减范围因子分别为0.05,1。

下图展示了随机选取的图像的人脸重建结果图。

上图中第一行是原图,第二行是基于向量Z的重建图,第三行是基于向量W的重建图。

下图展示了一幅图像基于Z和基于W向量的3部分训练损失曲线图,其中实线对应W,虚线对应Z,需要注意的是噪声损失没有乘以对应的权重,如果乘以对应的权重,最终收敛的时候三部分损失的幅度相当。

从结果上来看,两种方法中人脸图像的总体姿态、肤色、发型、脸型、背景重建效果都比较好,基于向量Z的重建方法中人脸的清晰度更高,但是身份没有得到保持。这主要是因为所学习的特征向量为Z,它还需要经过非线性的映射网络(mapping network)得到W,相比于直接学习W的难度更高,许多研究都表明使用W向量可以得到更好的重建效果。

从损失曲线可以看出,基于W向量的重建训练损失可以获得更低的值,但是感知损失的收敛更慢,MSE的实际损失更低,可见在获得了更精确的身份重建的基础上,牺牲了一定的感知质量,使得当编辑W向量时会比较敏感。

接下来我们使用基于向量Z的重建结果来进行人脸的属性编辑,因为这样可以比较基于Z和W来编辑属性的差异性,基于向量W的重建结果中无法获得对应的Z向量,因为映射网络是无法从输出获得输入的。

3.2 人脸属性混合与插值

接下来我们进行人脸属性的混合与插值,这是在多张图片之间进行属性的混合操作。

(1) 人脸属性样式混合

我们首先体验人脸属性样式混合,样式混合操作可以通过下式的向量运算实现。

其中[0:m]表示取向量的前m维,[m:n]表示取向量的第m到n维,两者通过拼接得到新的向量。

值得注意的是,这里的W并不是映射网络的输出向量,而是对于不同风格化层的AdaIN缩放和偏移系数,即风格系数。我们之前介绍过,不同分辨率的风格模块对应着不同层级的人脸特征,在这里我们使用两张人脸图像对应的风格系数进行混合,体验不同层级特征的样式混合。

图中第1列表示源图,第5列表示目标图,第2列、第3列、第4列表示在对应分辨率的风格层使用源图的风格,其他分辨率的风格层使用目标图的风格。

第2列表示分辨率为4×4,8×8的风格化模块的风格向量来自于源图,其他分辨率模块的风格向量来自于目标图。可以看出,结果图有源图的粗粒度特征,如人脸姿态、发型特征,以及目标图的细粒度特征,如发丝、眼睛颜色。

第3列表示分辨率为16×16,32×32的风格化模块的风格向量来自于源图,其他分辨率模块的风格向量来自于目标图。可以看出,保留了源图的中等粒度特征,如眼睛形态、嘴唇颜色等特征。

第4列表示分辨率为64×64到1024×1024之间的风格化模块的风格向量来自于源图,其他分辨率模块的风格向量来自于目标图。可以看出,结果图有源图的细粒度特征,如头发和皮肤的颜色与纹理等特征,以及目标图的粗粒度特征,如姿态、发型。

(2) 人脸样式插值

接下来我们体验人脸样式插值,样式插值操作可以通过下面的W向量运算实现。

下图分别展示了基于向量Z和向量W的人脸样式插值结果。

基于Z向量的人脸样式插值

基于W向量的人脸样式插值

第1列表示A域图像,对应n维的列向量 。第6列表示B域图像,对应n维的列向量,第2,3,4,5列表示在不同权重下对两张图片的向量进行加权后生成的图像,可以看出都可以实现了样式过度,但是基于W向量的结果明显要比基于Z向量的结果更加平滑。

3.2 人脸属性编辑

上面介绍的人脸样式混合可以直接通过两幅图片的潜在向量运算来实现人脸属性的混合,而如果想要对单张人脸的属性进行精确编辑,就需要首先找到潜在向量的编辑方向,称之为方向向量。下面我们介绍方向向量的求解与基于方向向量的属性编辑。

接下来的属性编辑建立在以下假设的基础上:在方向向量上,线性的改变潜在编码向量,则生成的图像及语义内容也是连续变化的,因此可以使用线性模型来进行属性更改:

W表示结果编码,W0表示人脸特征码,alpha表示偏移系数,n表示方向向量。

接下来需要解决的问题就是方向向量n的求解,具体的步骤为:

(1) 随机采样潜在编码向量,生成人脸图片,保存人脸图片和对应的潜在编码向量。

(2) 对生成的人脸图片,训练想要编辑的人脸属性CNN分类模型。任意一个二值语义都存在一个超平面可以作为语义类别的分类边界,在超平面的一侧改变潜在编码向量不会改变对应的语义类别,这个超平面可以用单位法线矢量表示。

(3) 根据CNN分类模型获得的标签,对潜在编码向量训练出线性回归模型,获得方向向量,如下图所示。

上图展示了0和1这两类样本,它就是潜在编码向量W。求解完线性回归模型的权重 ,它实际上就是方向向量。得到方向向量后,就可以进行人脸相关属性的编辑。

以人脸表情编辑为例:首先我们使用StyleGAN随机生成了50000张人脸图片,接下来我们需要将图片分为有表情和无表情两类,需要使用一个预训练好的2分类表情识别模型。为了保证模型有较高的准确率,训练时采用的方法是首先提取出人脸嘴唇区域,然后对嘴唇区域进行训练。

该表情识别模型大家可以参考阅读:

【项目实战课】AI零基础,人人免费可学!基于Pytorch的SimpleNet人脸表情识别实战

下图就是基于微笑表情模型分类出的有微笑和无微笑两类图片的一些样本展示。

训练好模型后,我们就得到了图片的标签,然后对其对应的潜在向量学习一个线性分类器,得到方向向量。完整代码请大家参考文后资料。

因为潜在向量维度是1×512,所以得到的方向向量也是1×512维,然后我们就可以基于方向向量进行属性编辑,基于Z向量编辑的核心代码如下:

from model import StyledGenerator
if __name__ == "__main__":
    device = "cpu"
    parser.add_argument(
        "--ckpt", type=str, required=True, help="path to the model checkpoint"
    )
    parser.add_argument(
        "--size", type=int, default=1024, help="output image sizes of the generator"
    )
    parser.add_argument(
        "--files", type=str, help="path to image files to be projected"
    )
    parser.add_argument(
        "--direction", type=str, help="direction file to be read"
    )
    parser.add_argument(
        "--directionscale", type=float, help="direction scale"
    )
    args = parser.parse_args()

    ## 载入模型
    netG = StyledGenerator(512)
    netG.load_state_dict(torch.load(args.ckpt,map_location=device)["g_running"], strict=False)
    netG.eval()
    netG = netG.to(device)
    step = int(math.log(args.size, 2)) - 2

    ## 载入方向向量
    direction = np.load(args.direction)
    directiontype = args.direction.split('/')[-1].split('.')[0]
    editscale = args.directionscale
    npys = glob.glob(args.files+"*.npy")

    for npyfile in npys:
        latent = torch.from_numpy(np.load(npyfile))
        if len(latent.shape) == 1:
            latent = latent.unsqueeze(0)
        latent = latent + torch.from_numpy((editscale*direction[0]).astype(np.float32))
        latent.to(device)
        img_gen = netG([latent], step=step) ##生成的图片
        img_name = os.path.join(npyfile.replace('.npy','_'+directiontype+'_'+str(editscale)+'.jpg'))
        utils.save_image(img_gen, img_name, normalize=True)
        np.save(img_name.replace('.jpg','.npy'),latent)

下图分别展示了基于Z向量和W向量的编辑结果。

基于Z向量的人脸微笑属性编辑

基于W向量的人脸微笑属性编辑

第1行表示原图,第2行表示减小微笑表情幅度,第3行表示增大微笑表情幅度。

可以看出基于Z向量和W向量,都能够实现微笑表情的编辑。不过基于Z向量的模型,明显地更改了其他的属性,比如发型、人脸身份等,这说明基于向量Z的编辑不能很好地实现表情属性与其他属性的解耦合,存在非常大的改进空间,而基于W向量能够比较好的保护其他属性信息。

由于不同的人脸有不同的属性,我们也可以基于此进行属性的添加与移除,W向量运算如下式,

其中W3和W2来自于同一个人,相减得到某个属性的向量,然后将其添加到W1,表示给对应的人脸添加对应的属性。

下图分别展示了基于Z向量和W向量的人脸微笑属性添加与移除结果。

基于Z向量的表情添加与移除

基于W向量的表情添加与移除

第1列表示源图,对应n维的列向量 。第2列和第3列分别表示目标图,对应n维的列向量和。第4列表示生成的图像,可以看出基于Z向量虽然可以一定程度上实现微笑属性的添加与移除,但是会修改人脸的身份。而基于W向量可以很好地实现微笑属性的添加与移除。

本文参考的文献如下:

[1] Abdal R , Qin Y , Wonka P . Image2StyleGAN: How to Embed Images Into the StyleGAN Latent Space?[J]. IEEE, 2019.

[2] Shen Y , Gu J , Tang X , et al. Interpreting the Latent Space of GANs for Semantic Face Editing[C]// 2020 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR). IEEE, 2020.

本文视频讲解和代码,请大家移步:【项目实战课】基于Pytorch的StyleGAN人脸属性(表情、年龄、性别)编辑实战

总结

本次我们使用StyleGAN模型实践了人脸属性的编辑,取得了预期的实验结果,不过该方法还存在许多可以改进的地方,大家可以使用更新的StyleGAN模型,以及更新的属性编辑模型来进行改进,欢迎大家以后持续关注《百战GAN专栏》。

如何系统性地学习生成对抗网络GAN

欢迎大家关注有三AI-CV秋季划GAN小组,可以系统性学习GAN相关的内容,包括GAN的基础理论,《深度学习之图像生成GAN:理论与实践篇》,《深度学习之图像翻译GAN:理论与实践篇》以及各类GAN任务的实战。

介绍如下:【CV秋季划】生成对抗网络GAN有哪些研究和应用,如何循序渐进地学习好(2022年言有三一对一辅导)?

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码