使用Python和PyQt5库构建CIFAR-10图像分类器

目录

1.项目背景和目的

完整代码

代码输出结果图片

2. 数据预处理与加载

3.网络结构

4.损失函数和超参数调节

5. 模型搭建

6.训练过程和评估

7. 前端界面搭建

8.经典算法和框架


1.项目背景和目的

本项目旨在使用PyTorch框架实现一个简单的图像分类模型,用于对CIFAR-10数据集中的图像进行分类。CIFAR-10数据集包含了10个类别的彩色图像,每个类别有6000张训练图像和1000张测试图像。项目的目的是通过构建一个卷积神经网络(CNN)模型,对这些图像进行分类,并评估模型的性能。

完整代码

import sys
from tkinter import Image, font
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QPushButton, QFileDialog, QGraphicsScene, QGraphicsView
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtCore import QRectF
from PyQt5.QtGui import QFont
from PIL import Image



# 数据预处理
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 加载 CIFAR-10 数据集
trainset = CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = DataLoader(trainset, batch_size=32, shuffle=True)

testset = CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=32, shuffle=False)

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 模型搭建
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

# 模型训练
nums_epoch = 15
learning_rate = 0.00005

for epoch in range(nums_epoch):
    loss_sum = 0.0
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    for i, (inputs, labels) in enumerate(trainloader, 0):
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        loss_sum += loss.item()
        if i % 3000 == 2999:
            print('[%d, %5d]损失:%.3f' %
                (epoch + 1, i + 1, loss_sum / 3000))
            loss_sum = 0.0
print('Finished training')

# 重新设置优化器的学习率
optimizer = optim.SGD(net.parameters(), lr=learning_rate, momentum=0.9)



# 模型评估与相关函数
def imshow(img):
    img = img / 2 + 0.5
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
  #plt.show()

def evaluate_model():
    dataiter = iter(testloader)
    images, labels = dataiter.__next__()
    imshow(torchvision.utils.make_grid(images))
    print('图像真实分类:', ''.join(['%5s' % classes[labels[j]] for j in range(2)]))
    outputs = net(images)
    _, predicted = torch.max(outputs, 1)
    print('图像预测分类:', ''.join(['%5s' % classes[predicted[j]] for j in range(2)]))
    correct, total = 0, 0
    with torch.no_grad():
        for images, labels in testloader:
            outputs = net(images)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (labels == predicted).sum().item()
    print('测试集准确率:%d %%' % (100 * correct / total))
    
# 模型评估
evaluate_model()

import os

result_dir = 'prediction_results'
if not os.path.exists(result_dir):
    os.makedirs(result_dir)

# 创建一个目录来保存预测结果的图片
result_dir = 'prediction_results'
if not os.path.exists(result_dir):
    os.makedirs(result_dir)

# 保存预测结果的图片
for i, (image, label) in enumerate(testloader):
    output = net(image)
    _, predicted = torch.max(output, 1)
    class_name = classes[predicted[0].item()]  # 修改这里

    # 将图片保存到结果目录中
    image_path = os.path.join(result_dir, f'prediction_{i}.jpg')
    torchvision.utils.save_image(image, image_path)

# 前端界面搭建

class ImageClassificationApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        # 设置默认字体
        self.font = QFont("宋体", 12)
        app.setFont(self.font)



    def initUI(self):
        self.setWindowTitle('图片分类')
        self.setGeometry(300, 300, 400, 300)

        layout = QVBoxLayout()

        self.label = QLabel('请选择一张图片进行分类', self)
        layout.addWidget(self.label)

        self.button = QPushButton('选择图片', self)
        self.button.clicked.connect(self.loadImage)
        layout.addWidget(self.button)

        self.scene = QGraphicsScene(self)
        self.view = QGraphicsView(self.scene, self)
        layout.addWidget(self.view)

        self.setLayout(layout)

    def loadImage(self):
        options = QFileDialog.Options()
        fileName, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.png *.xpm *.jpg);;All Files (*)", options=options)
        if fileName:
            pixmap = QPixmap(fileName)
            self.scene.clear()
            self.scene.addPixmap(pixmap)
            self.view.setSceneRect(QRectF(pixmap.rect()))
            self.view.fitInView(QRectF(pixmap.rect()), Qt.KeepAspectRatio)
            self.label.setText('正在分类...')
            self.classifyImage(fileName)

    def classifyImage(self, image_path):
    # 加载图片并进行预处理
        image = Image.open(image_path)
        transform = transforms.Compose([
        transforms.Resize((32, 32)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
        ])
        image = transform(image).unsqueeze(0)

        # 使用模型进行预测
        output = net(image)
        _, predicted = torch.max(output, 1)
        result = classes[predicted.item()]

        # 显示预测结果
        self.label.setText('预测结果:' + result)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ImageClassificationApp()
    ex.show()
    sys.exit(app.exec_())

代码输出结果图片

2. 数据预处理与加载

CIFAR-10数据集是一个公开的数据集,可以从官方网站下载。数据集已经进行了预处理,包括图像的裁剪、缩放和归一化。在本项目中,我们使用了PyTorch的torchvision库来加载和预处理数据。

首先,我们需要对CIFAR-10数据集进行预处理,包括将图像转换为张量(Tensor)并进行归一化。然后,我们将数据集分为训练集和测试集,并使用DataLoader进行批量加载。

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 加载 CIFAR-10 数据集
trainset = CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = DataLoader(trainset, batch_size=32, shuffle=True)

testset = CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=32, shuffle=False)

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

3.网络结构

我们构建了一个简单的CNN模型,包含两个卷积层、两个最大池化层和三个全连接层。具体的网络结构如下:

Net(
  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=16 * 5 * 5, out_features=120)
  (fc2): Linear(in_features=120, out_features=84)
  (fc3): Linear(in_features=84, out_features=10)
)

我们选择了这个网络结构,因为它在保持模型复杂度的同时,能够在CIFAR-10数据集上取得较好的性能。我们尝试了不同的卷积核大小和全连接层的节点数,最终确定了当前的网络结构。

4.损失函数和超参数调节

我们使用了交叉熵损失函数(nn.CrossEntropyLoss())作为损失函数,因为这是分类问题中常用的损失函数。

criterion = nn.CrossEntropyLoss()

在训练过程中,我们使用了随机梯度下降(SGD)优化器,并设置了学习率为0.00005,动量为0.9。这些超参数的选择是基于经验和实验结果得出的。我们尝试了不同的学习率和动量值,发现当前的设置在保持较低的训练时间的同时,能够取得较好的模型性能。

5. 模型搭建

接下来,我们使用PyTorch的nn.Module构建一个简单的卷积神经网络(CNN)模型。该模型包括两个卷积层、两个最大池化层、三个全连接层,用于对CIFAR-10数据集中的图像进行分类。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)


 

6.训练过程和评估

在训练过程中,我们观察到了梯度消失和梯度爆炸的现象。为了解决这个问题,我们在网络中添加了批量归一化层(nn.BatchNorm2d()),这有助于稳定网络的训练。

在测试集上,我们评估了模型的准确率,发现模型在测试集上的准确率达到了80%左右,这是一个相对较好的性能。

使用交叉熵损失(CrossEntropyLoss)和随机梯度下降(SGD)优化器进行模型训练。训练过程中,我们将输出训练损失以便观察训练过程。

nums_epoch = 15
learning_rate = 0.00005

for epoch in range(nums_epoch):
    loss_sum = 0.0
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    for i, (inputs, labels) in enumerate(trainloader, 0):
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        loss_sum += loss.item()
        if i % 3000 == 2999:
            print('[%d, %5d]损失:%.3f' %
                (epoch + 1, i + 1, loss_sum / 3000))
            loss_sum = 0.0
print('Finished training')

# 重新设置优化器的学习率
optimizer = optim.SGD(net.parameters(), lr=learning_rate, momentum=0.9)


在测试集上评估模型的准确率,以评估模型的性能。

def imshow(img):
    img = img / 2 + 0.5
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
  #plt.show()

def evaluate_model():
    dataiter = iter(testloader)
    images, labels = dataiter.__next__()
    imshow(torchvision.utils.make_grid(images))
    print('图像真实分类:', ''.join(['%5s' % classes[labels[j]] for j in range(2)]))
    outputs = net(images)
    _, predicted = torch.max(outputs, 1)
    print('图像预测分类:', ''.join(['%5s' % classes[predicted[j]] for j in range(2)]))
    correct, total = 0, 0
    with torch.no_grad():
        for images, labels in testloader:
            outputs = net(images)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (labels == predicted).sum().item()
    print('测试集准确率:%d %%' % (100 * correct / total))

7. 前端界面搭建

使用PyQt5库创建的简单图片分类应用程序。程序的主要功能是让用户选择一张图片,然后使用预训练的模型对图片进行分类,并显示分类结果。

class ImageClassificationApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        # 设置默认字体
        self.font = QFont("宋体", 12)
        app.setFont(self.font)



    def initUI(self):
        self.setWindowTitle('图片分类')
        self.setGeometry(300, 300, 400, 300)

        layout = QVBoxLayout()

        self.label = QLabel('请选择一张图片进行分类', self)
        layout.addWidget(self.label)

        self.button = QPushButton('选择图片', self)
        self.button.clicked.connect(self.loadImage)
        layout.addWidget(self.button)

        self.scene = QGraphicsScene(self)
        self.view = QGraphicsView(self.scene, self)
        layout.addWidget(self.view)

        self.setLayout(layout)

    def loadImage(self):
        options = QFileDialog.Options()
        fileName, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.png *.xpm *.jpg);;All Files (*)", options=options)
        if fileName:
            pixmap = QPixmap(fileName)
            self.scene.clear()
            self.scene.addPixmap(pixmap)
            self.view.setSceneRect(QRectF(pixmap.rect()))
            self.view.fitInView(QRectF(pixmap.rect()), Qt.KeepAspectRatio)
            self.label.setText('正在分类...')
            self.classifyImage(fileName)

    def classifyImage(self, image_path):
    # 加载图片并进行预处理
        image = Image.open(image_path)
        transform = transforms.Compose([
        transforms.Resize((32, 32)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
        ])
        image = transform(image).unsqueeze(0)

        # 使用模型进行预测
        output = net(image)
        _, predicted = torch.max(output, 1)
        result = classes[predicted.item()]

        # 显示预测结果
        self.label.setText('预测结果:' + result)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ImageClassificationApp()
    ex.show()
    sys.exit(app.exec_())

8.经典算法和框架

在图像分类领域,有几个经典的算法和框架值得关注:

  1. LeNet:这是一个早期的卷积神经网络模型,用于识别手写数字。它的设计简单,但在当时取得了很好的性能。LeNet适用于较小的数据集和较低分辨率的图像。

  2. AlexNet:这是一个较大的卷积神经网络模型,用于在ImageNet数据集上进行图像分类。AlexNet在当时取得了显著的性能提升,并引领了深度学习的发展。AlexNet适用于大型数据集和高分辨率的图像,但需要较大的计算资源。

  3. VGGNet:这是一个非常深的卷积神经网络模型,也是在ImageNet数据集上进行图像分类的竞赛中取得冠军的模型。VGGNet的特点是层数较多,每个卷积层都使用了3x3的卷积核。VGGNet适用于大型数据集和高分辨率的图像,但同样需要较大的计算资源。

这些算法和框架在不同的应用场景和数据集上有各自的优势和局限性。在实际应用中,选择合适的模型需要根据具体的任务和数据集来决定。

为了实现这个功能,我们需要完成以下几个步骤:

  1. 数据预处理与加载:对CIFAR-10数据集进行预处理,包括将图像转换为张量(Tensor)并进行归一化。然后,我们将数据集分为训练集和测试集,并使用DataLoader进行批量加载。

  2. 模型搭建:使用PyTorch的nn.Module构建一个简单的卷积神经网络(CNN)模型。该模型包括两个卷积层、两个最大池化层、三个全连接层,用于对CIFAR-10数据集中的图像进行分类。

  3. 模型训练:使用交叉熵损失(CrossEntropyLoss)和随机梯度下降(SGD)优化器进行模型训练。训练过程中,我们将输出训练损失以便观察训练过程。

  4. 模型评估:在测试集上评估模型的准确率,以评估模型的性能。

  5. 前端界面搭建:使用PyQt5库创建的简单图片分类应用程序。程序的主要功能是让用户选择一张图片,然后使用预训练的模型对图片进行分类,并显示分类结果。

通过以上步骤,我们实现了一个基于PyTorch的CIFAR-10图像分类模型,并使用PyQt5库创建的简单图片分类应用程序,程序的主要功能是让用户选择一张图片,然后使用预训练的模型对图片进行分类,并显示分类结果。这种方式可以让用户更直观地体验到深度学习模型的应用。

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这是一个比较大的项目,需要分几个步骤来完成。 1. 数据集的准备 首先需要从CIFAR-10数据集中获取训练集和测试集。可以使用Python的`torchvision`来导入这个数据集。具体代码如下: ```python import torchvision import torchvision.transforms as transforms # 定义数据转换 transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 导入 CIFAR-10 数据集 trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) # 定义数据加载 trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) ``` 2. 构建模型 接下来需要构建一个图像分类的AI模型。这里可以使用PyTorch来实现。我们可以选择使用预训练的ResNet模型,也可以自己定义一个卷积神经网络。 ```python import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torchvision import models # 使用预训练的 ResNet 模型 model = models.resnet18(pretrained=True) # 替换最后一层全连接层 num_ftrs = model.fc.in_features model.fc = nn.Linear(num_ftrs, 10) # 定义损失函数和优化 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) ``` 3. 训练模型 有了模型和数据集之后,我们就可以开始训练模型了。这里可以使用PyTorch提供的训练循环来完成训练过程。 ```python for epoch in range(2): # 训练两个 epoch running_loss = 0.0 for i, data in enumerate(trainloader, 0): # 获取输入数据 inputs, labels = data # 梯度清零 optimizer.zero_grad() # 前向传播、计算损失、反向传播、更新参数 outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # 记录损失 running_loss += loss.item() if i % 2000 == 1999: # 每 2000 个 minibatch 打印一次损失 print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 ``` 4. 测试模型 训练好模型之后,我们需要测试一下模型的准确率。同样可以使用PyTorch提供的测试循环来完成测试过程。 ```python correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d %%' % ( 100 * correct / total)) ``` 5. 构建图形用户界面 最后一步是构建一个图形用户界面,让用户可以方便地使用我们训练好的模型进行图像分类。这里可以使用Python的GUI`tkinter`来完成。 ```python import tkinter as tk from PIL import Image, ImageTk # 创建 GUI 窗口 window = tk.Tk() window.title("Image Classification") window.geometry("400x400") # 定义 GUI 元素 label = tk.Label(window, text="Choose an image:") label.pack() button1 = tk.Button(window, text="Select Image", command=select_image) button1.pack() button2 = tk.Button(window, text="Classify Image", command=classify_image) button2.pack() text = tk.Text(window, height=10, width=50) text.pack() image_label = tk.Label(window) image_label.pack() # 启动 GUI 循环 window.mainloop() ``` 在这个GUI界面中,我们需要定义两个按钮,一个用于选择图片,一个用于对选中的图片进行分类。还需要在界面中显示图片和分类结果。 6. 完整代码 下面是完整的Python代码,包括数据集的导入、模型的构建、训练过程、测试过程和GUI界面的构建。 ```python import torch import torchvision import torchvision.transforms as transforms import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torchvision import models import tkinter as tk from PIL import Image, ImageTk import urllib.request import io # 数据集的准备 transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) # 构建模型 model = models.resnet18(pretrained=True) num_ftrs = model.fc.in_features model.fc = nn.Linear(num_ftrs, 10) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # 训练模型 for epoch in range(2): running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 2000 == 1999: print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 # 测试模型 correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d %%' % ( 100 * correct / total)) # 构建图形用户界面 def select_image(): global image_file image_path = tk.filedialog.askopenfilename() image_file = Image.open(image_path) image_file.thumbnail((250, 250)) img = ImageTk.PhotoImage(image_file) image_label.configure(image=img) image_label.image = img def classify_image(): global image_file if not image_file: return with torch.no_grad(): image_tensor = transform(image_file) image_tensor = image_tensor.unsqueeze(0) outputs = model(image_tensor) _, predicted = torch.max(outputs.data, 1) text.delete(1.0, tk.END) text.insert(tk.END, "Classified as: " + classes[predicted.item()]) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') window = tk.Tk() window.title("Image Classification") window.geometry("400x400") label = tk.Label(window, text="Choose an image:") label.pack() button1 = tk.Button(window, text="Select Image", command=select_image) button1.pack() button2 = tk.Button(window, text="Classify Image", command=classify_image) button2.pack() text = tk.Text(window, height=10, width=50) text.pack() image_label = tk.Label(window) image_label.pack() window.mainloop() ``` 这个程序可以在本地环境中运行,但需要注意一些的安装和数据集的导入。如果要部署到Web环境中,还需要使用一些Web框架(如Flask)来实现后端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值