【Image mixing techniques】[sum up] mixup、Cutmix、Saliencymix、PuzzleMix、Stylemix、PixMix、Guidedmixup....

[Sum up]Image mixing techniques

【mixup、Manifold mixup、Cutmix、Saliencymix、PuzzleMix、Stylemix、Co-mixup、PixMix、Guidedmixup】

[ICLR2018] mixup: Beyond empirical risk minimization

Mixup 的核心思想是通过在训练时线性插值两个随机样本及其标签来生成新的训练样本。具体来说,对于两个样本 ( x i , y i ) (x_i, y_i) (xi,yi) ( x j , y j ) (x_j, y_j) (xj,yj),生成的新样本 ( x ~ , y ~ ) (\tilde{x}, \tilde{y}) (x~,y~)如下:
x ~ = λ x i + ( 1 − λ ) x j \tilde{x} = \lambda x_i + (1 - \lambda) x_j x~=λxi+(1λ)xj

y ~ = λ y i + ( 1 − λ ) y j \tilde{y} = \lambda y_i + (1 - \lambda) y_j y~=λyi+(1λ)yj

其中,λ是一个从 Beta 分布中采样的混合系数,通常 Beta 分布的参数 α \alpha α被设置为一个小于1的值,如 0.2。

1.采样混合系数: 从 Beta 分布 B e t a ( α , α ) Beta(\alpha, \alpha) Beta(α,α) 中采样一个混合系数 λ \lambda λ。当 α \alpha α 越大时, λ \lambda λ 更接近 0.5,当 α \alpha α 越小时, λ \lambda λ 更接近 0 或 1。

import numpy as np

def sample_lambda(alpha):
    return np.random.beta(alpha, alpha)

2.生成新样本: 对于训练集中每一对样本 ( x i , y i ) (x_i, y_i) (xi,yi) ( x j , y j ) (x_j, y_j) (xj,yj),生成新的训练样本 ( x ~ , y ~ ) (\tilde{x}, \tilde{y}) (x~,y~)

def mixup_data(x_i, y_i, x_j, y_j, alpha=0.2):
    lambda_ = sample_lambda(alpha)
    x_tilde = lambda_ * x_i + (1 - lambda_) * x_j
    y_tilde = lambda_ * y_i + (1 - lambda_) * y_j
    return x_tilde, y_tilde

3.训练模型: 使用生成的新样本 ( x ~ , y ~ ) (\tilde{x}, \tilde{y}) (x~,y~) 训练模型,而不是原始样本。

def train_with_mixup(model, optimizer, criterion, data_loader, alpha=0.2):
    model.train()
    for (x_i, y_i), (x_j, y_j) in data_loader:
        x_tilde, y_tilde = mixup_data(x_i, y_i, x_j, y_j, alpha)
        optimizer.zero_grad()
        outputs = model(x_tilde)
        loss = criterion(outputs, y_tilde)
        loss.backward()
        optimizer.step()
[PMLR2019]Manifold mixup: Better representations by interpolating hidden states

与 Mixup 仅在输入层进行线性插值不同,Manifold Mixup 在神经网络的隐藏层(中间层)进行线性插值。具体来说,对于隐藏层的激活值 h i h_i hi h j h_j hj 以及对应的标签 y i y_i yi y j y_j yj,生成新的混合样本 ( h ~ , y ~ ) (\tilde{h}, \tilde{y}) (h~,y~) 如下:
h ~ = λ h i + ( 1 − λ ) h j \tilde{h} = \lambda h_i + (1 - \lambda)h_j h~=λhi+(1λ)hj

y ~ = λ y i + ( 1 − λ ) y j \tilde{y} = \lambda y_i + (1 - \lambda) y_j y~=λyi+(1λ)yj

其中, λ \lambda λ同样从 Beta 分布中采样。

详细步骤

  1. 采样混合系数: 从 Beta 分布 B e t a ( α , α ) Beta(\alpha, \alpha) Beta(α,α) 中采样一个混合系数 λ \lambda λ

    import numpy as np
    
    def sample_lambda(alpha):
        return np.random.beta(alpha, alpha)
    
  2. 生成新样本: 在神经网络的中间层进行线性插值。

    def manifold_mixup(h_i, y_i, h_j, y_j, alpha=0.2):
        lambda_ = sample_lambda(alpha)
        h_tilde = lambda_ * h_i + (1 - lambda_) * h_j
        y_tilde = lambda_ * y_i + (1 - lambda_) * y_j
        return h_tilde, y_tilde
    
  3. 训练模型: 在每个训练批次中随机选择一层进行 Manifold Mixup,然后使用生成的新样本训练模型。

    import torch
    
    def train_with_manifold_mixup(model, optimizer, criterion, data_loader, alpha=0.2):
        model.train()
        for (x, y) in data_loader:
            optimizer.zero_grad()
    
            # Forward pass to get hidden states
            hidden_states = model.get_hidden_states(x)
            
            # Randomly choose a layer to apply Manifold Mixup
            layer_idx = np.random.randint(0, len(hidden_states))
            h_i, h_j = hidden_states[layer_idx]
            y_i, y_j = y, y
    
            h_tilde, y_tilde = manifold_mixup(h_i, y_i, h_j, y_j, alpha)
    
            # Replace the chosen layer's output with the mixed output
            hidden_states[layer_idx] = h_tilde
    
            # Forward pass to compute loss using mixed hidden states
            outputs = model.forward_from_hidden_states(hidden_states)
            loss = criterion(outputs, y_tilde)
            loss.backward()
            optimizer.step()
    
[ICCV2019]Cutmix: Regularization strategy to train strong classifiers with localizable features.

CutMix 通过在训练图像中剪切和混合不同图像的局部区域,改进了数据增强策略,从而提高了模型的泛化能力和分类性能。

方法概述

CutMix 的核心思想是将两张图像的局部区域进行混合,同时混合对应的标签。具体来说,对于两张图像 x i x_i xi x j x_j xj 及其对应的标签 y i y_i yi y j y_j yj,生成的新图像 x ~ \tilde{x} x~ 和新标签 y ~ \tilde{y} y~ 如下:
x ~ = M ⊙ x i + ( 1 − M ) ⊙ x j \tilde{x} = M \odot x_i + (1 - M) \odot x_j x~=Mxi+(1M)xj

y ~ = λ y i + ( 1 − λ ) y j \tilde{y} = \lambda y_i + (1 - \lambda) y_j y~=λyi+(1λ)yj

其中,M 是一个二值掩码矩阵,用于确定图像 x i x_i xi x j x_j xj 的哪部分将被保留, λ \lambda λ是由掩码覆盖的比例决定的混合系数, ⊙ \odot 表示逐元素乘法。

详细步骤

  1. 生成二值掩码: 随机选择一个矩形区域,将其设置为1,其他区域设置为0。

    import numpy as np
    import random
    
    def generate_mask(image_shape, lambda_):
        width, height = image_shape
        cut_rat = np.sqrt(1. - lambda_)
        cut_w = np.int(width * cut_rat)
        cut_h = np.int(height * cut_rat)
    
        cx = np.random.randint(width)
        cy = np.random.randint(height)
    
        bbx1 = np.clip(cx - cut_w // 2, 0, width)
        bby1 = np.clip(cy - cut_h // 2, 0, height)
        bbx2 = np.clip(cx + cut_w // 2, 0, width)
        bby2 = np.clip(cy + cut_h // 2, 0, height)
    
        mask = np.ones((width, height), np.float32)
        mask[bbx1:bbx2, bby1:bby2] = 0.
    
        return mask
    
  2. 生成新图像和新标签: 使用生成的掩码矩阵混合两张图像和其标签。

    import torch
    
    def cutmix_data(x_i, y_i, x_j, y_j):
        lambda_ = np.random.beta(1.0, 1.0)
        mask = generate_mask(x_i.shape[1:], lambda_)
    
        x_tilde = x_i * mask + x_j * (1 - mask)
        y_tilde = lambda_ * y_i + (1 - lambda_) * y_j
    
        return x_tilde, y_tilde
    
  3. 训练模型: 在训练过程中使用 CutMix 增强的样本进行训练。

    def train_with_cutmix(model, optimizer, criterion, data_loader):
        model.train()
        for (x, y) in data_loader:
            optimizer.zero_grad()
            
            # Shuffle the indices
            indices = torch.randperm(x.size(0))
            x_j = x[indices]
            y_j = y[indices]
    
            # Apply CutMix
            x_tilde, y_tilde = cutmix_data(x, y, x_j, y_j)
            
            # Forward pass
            outputs = model(x_tilde)
            loss = criterion(outputs, y_tilde)
            loss.backward()
            optimizer.step()
    
[ICLR2020]Saliencymix: A saliency guided data augmentation strategy for better regularization

SaliencyMix 的核心思想是将显著性区域从一张图像粘贴到另一张图像上,从而生成新的训练样本。显著性区域指的是图像中对模型预测最重要的部分。

方法概述

SaliencyMix 方法的核心步骤包括:

  1. 计算显著性图:使用预训练模型计算输入图像的显著性图,表示每个像素对模型预测的贡献。
  2. 生成显著性掩码:根据显著性图,选择图像中的显著性区域作为掩码。
  3. 混合图像和标签:将显著性区域从一张图像粘贴到另一张图像上,同时混合对应的标签。

详细步骤

  1. 计算显著性图: 可以使用 Grad-CAM 或其他显著性检测方法来计算显著性图。

    import torch
    import torch.nn.functional as F
    
    def get_saliency_map(model, input_tensor, target_class=None):
        input_tensor.requires_grad_()
        output = model(input_tensor)
    
        if target_class is None:
            target_class = output.argmax(1)
    
        loss = F.nll_loss(output, target_class)
        model.zero_grad()
        loss.backward()
    
        saliency_map = input_tensor.grad.abs().squeeze().sum(dim=0)
        return saliency_map
    
  2. 生成显著性掩码: 根据显著性图,选择显著性最高的区域作为掩码。

    import numpy as np
    
    def generate_saliency_mask(saliency_map, area_fraction):
        sorted_indices = np.argsort(saliency_map.flatten())[::-1]
        num_pixels = int(area_fraction * len(sorted_indices))
        mask = np.zeros_like(saliency_map, dtype=np.float32)
        mask.flat[sorted_indices[:num_pixels]] = 1
        return mask
    
  3. 混合图像和标签: 使用生成的显著性掩码,将显著性区域从一张图像粘贴到另一张图像上。

    def saliency_mix_data(x_i, y_i, x_j, y_j, model, area_fraction=0.5):
        saliency_map = get_saliency_map(model, x_i.unsqueeze(0))
        mask = generate_saliency_mask(saliency_map, area_fraction)
        mask = torch.tensor(mask).unsqueeze(0).unsqueeze(0).float()
    
        x_tilde = mask * x_i + (1 - mask) * x_j
        lambda_ = mask.mean()
        y_tilde = lambda_ * y_i + (1 - lambda_) * y_j
    
        return x_tilde, y_tilde
    
  4. 训练模型: 在训练过程中使用 SaliencyMix 增强的样本进行训练。

    def train_with_saliency_mix(model, optimizer, criterion, data_loader, area_fraction=0.5):
        model.train()
        for (x, y) in data_loader:
            optimizer.zero_grad()
    
            # Shuffle the indices
            indices = torch.randperm(x.size(0))
            x_j = x[indices]
            y_j = y[indices]
    
            # Apply SaliencyMix
            x_tilde, y_tilde = saliency_mix_data(x, y, x_j, y_j, model, area_fraction)
    
            # Forward pass
            outputs = model(x_tilde)
            loss = criterion(outputs, y_tilde)
            loss.backward()
            optimizer.step()
    
[PMLR2020]PuzzleMix: Exploiting Saliency and Local Statistics for Optimal Mixup

PuzzleMix 是一种通过利用图像的显著性和局部统计特性来进行数据增强的方法。旨在通过拼图式的混合方式生成新的训练样本,从而提高模型的泛化能力和性能。

方法概述

PuzzleMix 的核心思想是通过以下步骤生成新的训练样本:

  1. 计算显著性图:利用预训练模型或显著性检测方法计算输入图像的显著性图。
  2. 生成拼图块:根据显著性图和局部统计特性,将图像分割成拼图块。
  3. 混合拼图块:将不同图像的拼图块进行拼接,生成新的图像。
  4. 混合标签:根据拼图块的混合比例,混合对应的标签。

PuzzleMix和SaliencyMix是两种数据增强技术,它们都利用图像的显著性信息来提高模型的泛化能力和性能。然而,它们的具体实现方法和策略有所不同。PuzzleMix 通过显著性和局部统计信息来生成新的训练样本,SaliencyMix 主要利用图像的显著性区域进行增强。

[CVPR2022]Stylemix: Separating content and style for enhanced data augmentation

在这里插入图片描述
StyleMix 的核心思想是将图像的内容和风格分开处理,从而生成新的样本。

  1. 提取内容和风格特征:使用预训练的神经网络(例如 VGG 网络)提取图像的内容和风格特征。
  2. 混合内容和风格:将不同图像的内容和风格特征进行组合,生成新的图像。
  3. 生成增强样本:将混合后的内容和风格特征合成新的图像,并用于训练。

详细步骤

  1. 提取内容和风格特征

    使用卷积神经网络(如 VGG)来提取图像的内容和风格特征。通常,内容特征是从较低层的卷积层提取的,而风格特征是从较高层的卷积层提取的。

    import torch
    import torchvision.models as models
    import torchvision.transforms as transforms
    from PIL import Image
    
    def load_image(image_path, size=256):
        image = Image.open(image_path)
        transform = transforms.Compose([
            transforms.Resize(size),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
        ])
        image = transform(image).unsqueeze(0)
        return image
    
    def get_features(image, model, layers):
        features = {}
        x = image
        for name, layer in model._modules.items():
            x = layer(x)
            if name in layers:
                features[name] = x
        return features
    
    # Example usage
    vgg = models.vgg19(pretrained=True).features.eval()
    image = load_image('example.jpg')
    content_layers = ['21']  # Example content layer
    style_layers = ['0', '5', '10', '19', '28']  # Example style layers
    content_features = get_features(image, vgg, content_layers)
    style_features = get_features(image, vgg, style_layers)
    
  2. 混合内容和风格

    将两个图像的内容和风格特征混合。可以使用加权平均的方法来合成新的内容和风格特征。

    def mix_content_style(content_features, style_features, alpha=0.5):
        mixed_features = {}
        for layer in content_features:
            content = content_features[layer]
            style = style_features[layer]
            mixed_features[layer] = alpha * content + (1 - alpha) * style
        return mixed_features
    
  3. 生成增强样本

    使用混合后的内容和风格特征生成新的图像。可以使用风格迁移算法(如 Gatys 风格迁移)来合成图像。

    from torch.optim import LBFGS
    
    def style_transfer(content_image, style_image, content_weight=1e5, style_weight=1e10, num_steps=300):
        model = models.vgg19(pretrained=True).features.eval()
        content_features = get_features(content_image, model, content_layers)
        style_features = get_features(style_image, model, style_layers)
        mixed_features = mix_content_style(content_features, style_features)
    
        target_image = content_image.clone().requires_grad_(True)
        optimizer = LBFGS([target_image])
    
        for step in range(num_steps):
            def closure():
                optimizer.zero_grad()
                target_features = get_features(target_image, model, content_layers)
                target_style_features = get_features(target_image, model, style_layers)
    
                content_loss = content_weight * torch.mean((target_features['21'] - content_features['21'])**2)
                style_loss = 0
                for layer in style_layers:
                    target_gram = gram_matrix(target_style_features[layer])
                    style_gram = gram_matrix(style_features[layer])
                    style_loss += torch.mean((target_gram - style_gram)**2)
                style_loss *= style_weight
    
                loss = content_loss + style_loss
                loss.backward()
                return loss
    
            optimizer.step(closure)
        return target_image
    
    def gram_matrix(tensor):
        batch_size, channels, height, width = tensor.size()
        features = tensor.view(batch_size * channels, height * width)
        gram = torch.mm(features, features.t())
        return gram / (channels * height * width)
    
[ICLR2020]Co-mixup: Saliency guided joint mixup with supermodular diversity

Co-Mixup 是一种通过显著性引导的联合混合数据增强方法,旨在利用图像中的显著性区域和超模多样性来提高模型的泛化能力和性能。
在这里插入图片描述

方法概述

Co-Mixup 的核心思想是通过显著性图指导,将多个图像的显著性区域进行联合混合,并引入超模多样性来生成新的训练样本。具体步骤如下:

  1. 计算显著性图:使用显著性检测方法(如Grad-CAM)计算输入图像的显著性图。
  2. 选择显著性区域:根据显著性图选择图像中最显著的区域。
  3. 区域交换与组合:在显著性区域内交换不同图像的内容,并引入超模多样性来确保生成样本的多样性。
  4. 混合标签:根据显著性区域的混合比例,混合对应的标签。

详细步骤

  1. 计算显著性图

    使用预训练模型(如Grad-CAM)计算输入图像的显著性图,获取图像中对分类最重要的部分。

    import torch
    import torch.nn.functional as F
    
    def get_saliency_map(model, input_tensor, target_class=None):
        input_tensor.requires_grad_()
        output = model(input_tensor)
    
        if target_class is None:
            target_class = output.argmax(1)
    
        loss = F.nll_loss(output, target_class)
        model.zero_grad()
        loss.backward()
    
        saliency_map = input_tensor.grad.abs().squeeze().sum(dim=0)
        return saliency_map
    
  2. 选择显著性区域

    根据显著性图选择图像中最显著的区域,可以根据显著性值的阈值来确定区域。

    def get_significant_region(saliency_map, threshold=0.5):
        mask = saliency_map > threshold * saliency_map.max()
        return mask
    
  3. 区域交换与组合

    将显著性区域进行交换和组合,通过引入超模多样性,确保生成样本的多样性和覆盖不同的显著性模式。

    import numpy as np
    
    def co_mixup(image1, image2, saliency1, saliency2, alpha=0.5):
        region1 = get_significant_region(saliency1)
        region2 = get_significant_region(saliency2)
    
        mixed_image = image1 * region1 + image2 * (1 - region1) * alpha + \
                      image2 * region2 + image1 * (1 - region2) * (1 - alpha)
    
        return mixed_image
    
  4. 混合标签

    根据显著性区域的混合比例,混合对应的标签。

    def mix_labels(label1, label2, alpha):
        return alpha * label1 + (1 - alpha) * label2
    
  5. 训练模型

    在训练过程中使用Co-Mixup增强的样本进行训练。

    def train_with_comixup(model, optimizer, criterion, data_loader, alpha=0.5):
        model.train()
        for (x, y) in data_loader:
            optimizer.zero_grad()
    
            # Shuffle the indices
            indices = torch.randperm(x.size(0))
            x_j = x[indices]
            y_j = y[indices]
    
            # Apply Co-Mixup
            saliency_map1 = get_saliency_map(model, x)
            saliency_map2 = get_saliency_map(model, x_j)
    
            x_tilde = co_mixup(x, x_j, saliency_map1, saliency_map2, alpha)
            y_tilde = mix_labels(y, y_j, alpha)
    
            # Forward pass
            outputs = model(x_tilde)
            loss = criterion(outputs, y_tilde)
            loss.backward()
            optimizer.step()
    
[CVPR2022]PixMix: Dreamlike Pictures Comprehensively Improve Safety Measures

PixMix 的核心思想是通过生成梦幻般的图像,将这些图像与原始图像混合,来创建新的训练样本。这些梦幻图像可以包括多样化的纹理、颜色和形状,旨在增强模型对不同场景和变化的适应能力,从而提高模型的安全性和泛化能力。
在这里插入图片描述

详细步骤

  1. 生成梦幻图像

    生成梦幻图像可以使用不同的图像生成技术,如GANs(生成对抗网络)、VQ-VAE(向量量化变分自编码器)等。这些生成图像具有丰富的纹理和颜色,可以有效地增加数据的多样性。

  2. 图像混合

    将原始图像与生成的梦幻图像进行混合。混合可以通过像素级别的加权求和来实现,或者使用更复杂的混合方法,如Alpha混合。

    def mix_images(original_img, dreamlike_img, alpha=0.5):
        mixed_img = alpha * np.array(original_img) + (1 - alpha) * np.array(dreamlike_img)
        mixed_img = np.clip(mixed_img, 0, 255).astype(np.uint8)
        return Image.fromarray(mixed_img)
    
  3. 训练模型

    在训练过程中使用PixMix增强的样本进行训练。

    def train_with_pixmix(model, optimizer, criterion, data_loader, dreamlike_model, alpha=0.5):
        model.train()
        for (x, y) in data_loader:
            optimizer.zero_grad()
    
            # 生成梦幻图像
            dreamlike_images = [generate_dreamlike_image(img, dreamlike_model) for img in x]
    
            # 混合图像
            mixed_images = [mix_images(orig, dream, alpha) for orig, dream in zip(x, dreamlike_images)]
    
            # 转换为Tensor
            mixed_images = torch.stack([transforms.ToTensor()(img) for img in mixed_images])
    
            # Forward pass
            outputs = model(mixed_images)
            loss = criterion(outputs, y)
            loss.backward()
            optimizer.step()
    
[AAAI2023]Guidedmixup: an efficient mixup strategy guided by saliency maps

GuidedMixup 是一种由显著性图引导的高效混合策略,通过利用显著性图来引导图像混合,从而提高模型的泛化能力和性能。在这里插入图片描述

方法概述

GuidedMixup 的核心思想是通过显著性图来指导混合过程,使得模型能够更好地关注图像中重要的部分,从而提高模型的学习能力和鲁棒性。具体步骤如下:

  1. 计算显著性图:使用显著性检测方法(如Grad-CAM)计算输入图像的显著性图。
  2. 选择显著性区域:根据显著性图选择图像中最显著的区域。
  3. 区域混合:将不同图像的显著性区域进行混合,生成新的图像。
  4. 混合标签:根据显著性区域的混合比例,混合对应的标签。

详细步骤

  1. 计算显著性图

    使用显著性检测方法(如Grad-CAM)计算输入图像的显著性图,获取图像中对分类最重要的部分。

    import torch
    import torch.nn.functional as F
    
    def get_saliency_map(model, input_tensor, target_class=None):
        input_tensor.requires_grad_()
        output = model(input_tensor)
    
        if target_class is None:
            target_class = output.argmax(1)
    
        loss = F.nll_loss(output, target_class)
        model.zero_grad()
        loss.backward()
    
        saliency_map = input_tensor.grad.abs().squeeze().sum(dim=0)
        return saliency_map
    
  2. 选择显著性区域

    根据显著性图选择图像中最显著的区域,可以根据显著性值的阈值来确定区域。

    def get_significant_region(saliency_map, threshold=0.5):
        mask = saliency_map > threshold * saliency_map.max()
        return mask
    
  3. 区域混合

    将不同图像的显著性区域进行混合,生成新的图像。

    import numpy as np
    from PIL import Image
    
    def guided_mixup(image1, image2, saliency1, saliency2, alpha=0.5):
        region1 = get_significant_region(saliency1)
        region2 = get_significant_region(saliency2)
    
        # Convert images to numpy arrays
        image1_array = np.array(image1)
        image2_array = np.array(image2)
    
        # Mix the images based on saliency regions
        mixed_image_array = alpha * image1_array * region1 + (1 - alpha) * image2_array * region2
        mixed_image_array = np.clip(mixed_image_array, 0, 255).astype(np.uint8)
    
        mixed_image = Image.fromarray(mixed_image_array)
        return mixed_image
    
  4. 混合标签

    根据显著性区域的混合比例,混合对应的标签。

    def mix_labels(label1, label2, alpha):
        return alpha * label1 + (1 - alpha) * label2
    
  5. 训练模型

    在训练过程中使用GuidedMixup增强的样本进行训练。

    def train_with_guidedmixup(model, optimizer, criterion, data_loader, alpha=0.5):
        model.train()
        for (x, y) in data_loader:
            optimizer.zero_grad()
    
            # Shuffle the indices
            indices = torch.randperm(x.size(0))
            x_j = x[indices]
            y_j = y[indices]
    
            # Apply GuidedMixup
            saliency_map1 = get_saliency_map(model, x)
            saliency_map2 = get_saliency_map(model, x_j)
    
            mixed_images = [guided_mixup(orig, shuffled, sal1, sal2, alpha) for orig, shuffled, sal1, sal2 in zip(x, x_j, saliency_map1, saliency_map2)]
    
            # Convert mixed images to tensor
            mixed_images_tensor = torch.stack([transforms.ToTensor()(img) for img in mixed_images])
    
            # Forward pass
            outputs = model(mixed_images_tensor)
            y_tilde = mix_labels(y, y_j, alpha)
            loss = criterion(outputs, y_tilde)
            loss.backward()
            optimizer.step()
    

更详细内容请参照原文。

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值