5.基于PyQt的GUI界面设计

前言

这节实验课来学习基于PyQt的GUI界面设计,有了这个可视化界面,我们可以更加方便地在同一个界面里实现对图像的旋转、平滑、锐化等多种处理。

5.1 各模块介绍 

整个页面分为三个部分:顶部区域:“加载图片”和“保存图像”按钮,用于导入待处理的图片并把处理后的图片存储起来。图像显示区域:左侧为原始图像区域,右侧为处理后图像区域。底部区域:为图像各处理操作的按钮,现在包括“灰度化”“去噪”和“锐化”等功能。

 下面展示这三个部分的代码构成:

 顶部区域:

# 创建顶部布局(加载和保存按钮)
top_layout = QHBoxLayout()  # 水平布局
load_btn = QPushButton("📂 加载图片")  # 加载按钮,带图标
save_btn = QPushButton("💾 保存图像")  # 保存按钮,带图标
load_btn.clicked.connect(self.load_image)  # 连接加载按钮点击信号
save_btn.clicked.connect(self.save_image)  # 连接保存按钮点击信号
top_layout.addWidget(load_btn)  # 添加加载按钮到布局
top_layout.addWidget(save_btn)  # 添加保存按钮到布局
top_layout.addStretch()  # 添加伸缩项,使按钮靠左
main_layout.addLayout(top_layout)  # 将顶部布局添加到主布局
main_layout.addWidget(self._h_line())  # 添加水平分割线

图像显示区域:

# 创建第一行图像显示布局
img_layout = QHBoxLayout()  # 水平布局
self.original_label = QLabel()  # 显示原始图像的标签
self.processed_label = QLabel()  # 显示处理后图像的标签

 底部区域:

# 创建底部按钮布局(图像处理功能)
bottom_layout = QHBoxLayout()
# 按钮列表:文本与对应的处理模式
for text, func in [("⚫灰度化", "gray"), ("🔍去噪", "denoise"),
                   ("✨锐化", "sharpen")]:
    btn = QPushButton(text)  # 创建按钮
    # 连接按钮点击信号到处理函数,lambda传递模式参数
    btn.clicked.connect(lambda _, f=func: self.process(f))
    bottom_layout.addWidget(btn)  # 添加按钮到底部布局
bottom_layout.addStretch()  # 添加伸缩项,使按钮靠左
main_layout.addLayout(bottom_layout)  # 将底部布局添加到主布局

 5.2 扩展功能

首先我想对顶部区域完善,在“加载图片”和“保存图像”之间增加“图片复原”功能,这样可以反复对同一张图像进行不同的操作。把如下代码添加到源代码中,并在ImageProcessor中增加该功能的代码:

    restore_btn = QPushButton("↩️ 图片复原")
    restore_btn.clicked.connect(self.restore_image)#连接复原按钮的事件
    top_layout.addWidget(restore_btn)

def restore_image(self):
    """将处理后的图像恢复为原始图像"""
    if 'original' not in self.image_data:
        QMessageBox.warning(self, "提示", "请先加载图片")  # 提示用户加载图像
        return
    # 将处理后的图像恢复为原始图像
    self.image_data['processed'] = self.image_data['original'].copy()
    # 更新处理后图像显示
    self.show_image(self.image_data['processed'], self.processed_label)

 下面展示功能运行结果:点击“图片复原”按钮,右侧的处理后图像恢复为原始图像。 

观察上图我们发现:代码的原有功能只能对原始图像进行处理,不能在已处理的图像上进行二次处理。我们在process图像功能代码中增加约束条件,使得如果图像已被处理过,则在处理后的图像上进行处理。 

    def process(self, mode):
        """根据选择的模式处理图像"""
        if 'processed' not in self.image_data:  # 如果没有处理过的图像,默认使用原始图像进行处理
            if 'original' not in self.image_data:
                QMessageBox.warning(self, "提示", "请先加载图片")  # 提示用户加载图像
                return
            img = self.image_data['original']  # 获取原始图像
        else:
            img = self.image_data['processed']  # 获取处理后的图像作为基础图像
        result = None  # 初始化处理结果

 运行结果如下:先进行灰度处理,再进行锐化,我们发现一直点“锐化”图像会一直不断加深锐化程度。  

接着我们将所学的图像处理操作都添加到底部区域当中。在“去噪”的前面增加“添噪”的功能,并能够选择添加“椒盐噪声”“高斯噪声”“均匀噪声”三种:在底部按钮中添加“添噪”按钮,并在process图像功能代码中添加添噪处理(注意一定要创建图像副本,对副本进行噪声处理),并在代码最后添加添噪的相关代码。 

#“添噪”按钮
        for text, func in [("⚫灰度化", "gray"),("⚡添噪", "add_noise"), ("🔍去噪", "denoise"), ("✨锐化", "sharpen")]:
            btn = QPushButton(text)
 
#process中的代码
            elif mode == "add_noise":  # 添噪处理
                # 弹出对话框让用户选择噪声类型
                noise_type, ok = QInputDialog.getItem(
                    self, "选择噪声类型", "噪声类型", ["椒盐噪声", "高斯噪声", "均匀噪声"], 0, False)
                if ok and noise_type:
                    img_copy = img.copy()  # 使用副本进行操作
                    if noise_type == "椒盐噪声":
                        result = self.add_salt_pepper_noise(img_copy)
                    elif noise_type == "高斯噪声":
                        result = self.add_gaussian_noise(img_copy)
                    elif noise_type == "均匀噪声":
                        result = self.add_uniform_noise(img_copy)
                else:
                    return
 
#三种噪声代码
    def add_salt_pepper_noise(self, image):
        """添加椒盐噪声"""
        # 添加椒盐噪声
        salt_pepper_prob = 0.05  # 椒盐噪声的概率
        salt_prob = 0.5  # 椒盐比例
        noisy_image = image.copy()  # 创建图像副本进行操作
        # 随机生成与图像大小相同的掩码
        noise_mask = np.random.random(noisy_image.shape[:2])
        salt_mask = noise_mask < (salt_pepper_prob * salt_prob)  # 椒噪声掩码
        pepper_mask = noise_mask > (1 - salt_pepper_prob * (1 - salt_prob))  # 盐噪声掩码
        # 添加椒噪声(白色像素)
        noisy_image[salt_mask] = 255
        # 添加盐噪声(黑色像素)
        noisy_image[pepper_mask] = 0
        return noisy_image.astype(np.uint8)  # 确保返回的图像是uint8类型
 
    def add_gaussian_noise(self, image):
        """添加高斯噪声"""
        mean = 0  # 均值
        sigma = 25  # 标准差
        # 生成与图像大小相同的高斯噪声
        gaussian_noise = np.random.normal(mean, sigma, image.shape).astype(np.float32)
        # 将图像转换为浮点数进行运算,避免溢出
        noisy_image = cv2.add(image.astype(np.float32), gaussian_noise.astype(np.float32))
        # 将图像值裁剪到[0, 255]并转换为uint8类型
        return np.clip(noisy_image, 0, 255).astype(np.uint8)
 
    def add_uniform_noise(self, image):
        """添加均匀噪声"""
        low = -50  # 均匀噪声的下限
        high = 50  # 均匀噪声的上限
        # 生成与图像大小相同的均匀噪声
        uniform_noise = np.random.uniform(low, high, image.shape).astype(np.float32)
        # 将图像转换为浮点数进行运算,避免溢出
        noisy_image = cv2.add(image.astype(np.float32), uniform_noise.astype(np.float32))
        # 将图像值裁剪到[0, 255]并转换为uint8类型
        return np.clip(noisy_image, 0, 255).astype(np.uint8)

 注意:不要忘记在开头导入QInputDialog

from PyQt5.QtWidgets import QInputDialog

下面分别展示这三种噪声的添加情况: 

 接着完善一下去噪的三种滤波方法,同样也是在process图像功能代码中添加“中值滤波”“高斯滤波”“均值滤波”三种去噪方法供用户选择(注意一定要创建图像副本,对副本进行处理),并在代码最后添加三种去噪的相关代码。

#process中的代码
            elif mode == "denoise":  # 去噪处理
                 # 弹出对话框让用户选择去噪方法
                    denoise_type, ok = QInputDialog.getItem(
                        self, "选择去噪方法", "去噪方法", ["中值滤波", "高斯滤波", "均值滤波"], 0, False)
                    if ok and denoise_type:
                        img_copy = img.copy()  # 使用副本进行操作
                        if denoise_type == "中值滤波":
                            result = self.median_filter(img_copy)
                        elif denoise_type == "高斯滤波":
                            result = self.gaussian_filter(img_copy)
                        elif denoise_type == "均值滤波":
                            result = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)
                    else:
                        return
 
#三种滤波方法
    def median_filter(self, image):
        """中值滤波"""
        return cv2.medianBlur(image, 5)  # 使用5x5的核进行中值滤波
 
    def gaussian_filter(self, image):
        """高斯滤波"""
        return cv2.GaussianBlur(image, (5, 5), 0)  # 使用5x5的核进行高斯滤波
 
    def mean_filter(self, image):
        """均值滤波"""
        return cv2.blur(image, (5, 5))  # 使用5x5的核进行均值滤波
    def show_image(self, img, label):
        """显示图像"""
        try:
            rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 将BGR转为RGB格式
            h, w, ch = rgb.shape
            bytes_per_line = ch * w
            q_img = QImage(rgb.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)  # 转为QImage格式
            label.setPixmap(QPixmap.fromImage(q_img).scaled(400, 400, Qt.AspectRatioMode.KeepAspectRatio))  # 设置显示图像
        except Exception as e:
            QMessageBox.critical(self, "错误", f"显示图像时发生错误:{e}")  # 捕获并显示异常信息

下面针对用非局部均值滤波处理椒盐噪声来进行功能展示:

 为了获得更丰富的数据库,我们再增加一个图像旋转功能:同样是在底部按钮中添加“旋转”按钮,并在process图像功能代码中添加旋转处理,并在代码最后添加旋转的相关代码。

#“旋转”按钮
for text, func in [("⚫灰度化", "gray"), ("⚡添噪", "add_noise"), ("🔍去噪", "denoise"), ("✨锐化", "sharpen"), ("🔄旋转", "rotate")]:
 
#process中的代码
            elif mode == "rotate":  # 旋转处理
                result = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)  # 顺时针旋转90度

5.3 最终代码展示  

下面展示的是添加上所有功能的代码,真的太多啦!快把我累瘫了 ,部分添加在上文中没有提及到的,下面的代码都进行了注释,想要继续学习的小伙伴可以探索一下注释掉的部分有什么作用

# 导入必要的库
import sys  # 系统相关操作
import cv2  # OpenCV库,用于图像处理
import numpy as np  # 数值计算库
from PyQt5.QtWidgets import (  # PyQt5界面组件
    QApplication, QMainWindow, QWidget, QLabel, QPushButton,
    QFileDialog, QMessageBox, QVBoxLayout, QHBoxLayout, QFrame
)
from PyQt5.QtGui import QPixmap, QImage  # 图像显示相关
from PyQt5.QtCore import Qt  # Qt核心功能
from PyQt5.QtWidgets import QInputDialog  # 需要添加这个导入

class ImageProcessor(QMainWindow):
    def __init__(self):
        super().__init__()  # 调用父类构造函数初始化
        self.setWindowTitle("PyQt演示")  # 设置窗口标题
        self.resize(900, 600)  # 设置窗口初始大小

        # 存储原始图像和处理后的图像数据(使用字典保存)
        self.image_data = {}

        # 创建主窗口的中心部件和布局
        main_widget = QWidget()  # 创建中心部件
        self.setCentralWidget(main_widget)  # 设置为主窗口的中心部件
        main_layout = QVBoxLayout(main_widget)  # 使用垂直布局

        # 设置主窗口的样式表(背景颜色、标签样式、按钮样式)
        main_widget.setStyleSheet("""
            QWidget {
                background-color: #f0f4f8;  # 浅蓝色背景
            }
            QLabel {
                border: 2px solid #aaa;  # 灰色边框
                border-radius: 10px;  # 圆角
                background-color: white;  # 白色背景
                padding: 5px;  # 内边距
                box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);  # 阴影效果
            }
            QPushButton {
                font-size: 15px;  # 按钮字体大小
                padding: 8px 18px;  # 按钮内边距
                min-width: 100px;  # 按钮最小宽度
            }
        """)

        # 创建顶部布局(加载和保存按钮)
        top_layout = QHBoxLayout()  # 水平布局
        load_btn = QPushButton("📂 加载图片")  # 加载按钮,带图标
        save_btn = QPushButton("💾 保存图像")  # 保存按钮,带图标
        restore_btn = QPushButton("↩️ 图片复原")
        load_btn.clicked.connect(self.load_image)  # 连接加载按钮点击信号
        save_btn.clicked.connect(self.save_image)  # 连接保存按钮点击信号
        restore_btn.clicked.connect(self.restore_image)  # 连接复原按钮的事件
        top_layout.addWidget(load_btn)  # 添加加载按钮到布局
        top_layout.addWidget(save_btn)  # 添加保存按钮到布局
        top_layout.addWidget(restore_btn)
        top_layout.addStretch()  # 添加伸缩项,使按钮靠左
        main_layout.addLayout(top_layout)  # 将顶部布局添加到主布局
        main_layout.addWidget(self._h_line())  # 添加水平分割线
        # 创建第一行图像显示布局
        img_layout = QHBoxLayout()  # 水平布局
        self.original_label = QLabel()  # 显示原始图像的标签
        self.processed_label = QLabel()  # 显示处理后图像的标签
        self.new_label = QLabel()  # 预留的新标签(可用于扩展功能)

        # 设置标签的固定大小和对齐方式
        for label in (self.original_label, self.processed_label, self.new_label):
            label.setFixedSize(400, 400)  # 固定尺寸400x400像素
            label.setAlignment(Qt.AlignmentFlag.AlignCenter)  # 居中对齐

        # 将标签和分割线添加到布局
        img_layout.addWidget(self.original_label)  # 原始图像标签
        img_layout.addWidget(self._v_line())  # 垂直分割线
        img_layout.addWidget(self.processed_label)  # 处理后的图像标签
        img_layout.addWidget(self._v_line())  # 垂直分割线
        img_layout.addWidget(self.new_label)  # 新标签
        img_layout.setSpacing(0)  # 设置布局内部件间距为0
        main_layout.addLayout(img_layout)  # 将图像布局添加到主布局
        main_layout.addWidget(self._h_line())  # 添加水平分割线

        '''# 创建第二行图像显示布局(结构与第一行相同)
        img_layout_row2 = QHBoxLayout()
        self.original_label2 = QLabel()
        self.processed_label2 = QLabel()
        self.new_label2 = QLabel()

        # 设置标签属性
        for label in (self.original_label2, self.processed_label2, self.new_label2):
            label.setFixedSize(400, 400)
            label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        # 添加部件到第二行布局
        img_layout_row2.addWidget(self.original_label2)
        img_layout_row2.addWidget(self._v_line())
        img_layout_row2.addWidget(self.processed_label2)
        img_layout_row2.addWidget(self._v_line())
        img_layout_row2.addWidget(self.new_label2)
        img_layout_row2.setSpacing(0)
        main_layout.addLayout(img_layout_row2)  # 添加到主布局
        main_layout.addWidget(self._h_line())  # 添加分割线'''

        # 创建底部按钮布局(图像处理功能)
        bottom_layout = QHBoxLayout()
        # 按钮列表:文本与对应的处理模式
        for text, func in [("⚫灰度化", "gray"), ("🔍去噪", "denoise"),("⚡添噪", "add_noise"),
                           ("✨锐化", "sharpen"), ("🔄旋转", "rotate")]:
            btn = QPushButton(text)  # 创建按钮
            # 连接按钮点击信号到处理函数,lambda传递模式参数
            btn.clicked.connect(lambda _, f=func: self.process(f))
            bottom_layout.addWidget(btn)  # 添加按钮到底部布局
        bottom_layout.addStretch()  # 添加伸缩项,使按钮靠左
        main_layout.addLayout(bottom_layout)  # 将底部布局添加到主布局

    # 创建水平分割线的方法
    def _h_line(self):
        line = QFrame()
        line.setFrameShape(QFrame.Shape.HLine)  # 设置为水平线
        line.setFrameShadow(QFrame.Shadow.Sunken)  # 凹陷阴影效果
        line.setStyleSheet("color: #ccc;")  # 浅灰色
        return line

    # 创建垂直分割线的方法
    def _v_line(self):
        line = QFrame()
        line.setFrameShape(QFrame.Shape.VLine)  # 设置为垂直线
        line.setFrameShadow(QFrame.Shadow.Sunken)
        line.setStyleSheet("color: #ccc;")
        return line

    def load_image(self):
        """加载图像文件"""
        # 弹出文件选择对话框,限定图片格式
        file, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "图片文件 (*.png *.jpg *.bmp)")
        if file:
            img = cv2.imread(file)  # 使用OpenCV读取图像
            if img is None:  # 读取失败处理
                QMessageBox.warning(self, "错误", "无法加载图像")
                return
            # 存储原始图像和处理后的图像(初始为原图副本)
            self.image_data['original'] = img
            self.image_data['processed'] = img.copy()
            self.show_image(img, self.original_label)  # 在原始标签显示图像

    def save_image(self):
        """保存处理后的图像"""
        if 'processed' not in self.image_data:  # 检查是否存在处理后的图像
            QMessageBox.warning(self, "提示", "没有可保存的图像")
            return
        # 弹出保存文件对话框,选择保存路径和格式
        file, _ = QFileDialog.getSaveFileName(self, "保存图像", "", "PNG (*.png);;JPG (*.jpg)")
        if file:
            cv2.imwrite(file, self.image_data['processed'])  # 保存图像
            QMessageBox.information(self, "成功", f"图像已保存:{file}")  # 提示保存成功

    def restore_image(self):
        """将处理后的图像恢复为原始图像"""
        if 'original' not in self.image_data:
            QMessageBox.warning(self, "提示", "请先加载图片")  # 提示用户加载图像
            return
        # 将处理后的图像恢复为原始图像
        self.image_data['processed'] = self.image_data['original'].copy()
        # 更新处理后图像显示
        self.show_image(self.image_data['processed'], self.processed_label)

    def add_salt_pepper_noise(self, image):
        """添加椒盐噪声"""
        # 添加椒盐噪声
        salt_pepper_prob = 0.05  # 椒盐噪声的概率
        salt_prob = 0.5  # 椒盐比例
        noisy_image = image.copy()  # 创建图像副本进行操作
        # 随机生成与图像大小相同的掩码
        noise_mask = np.random.random(noisy_image.shape[:2])
        salt_mask = noise_mask < (salt_pepper_prob * salt_prob)  # 椒噪声掩码
        pepper_mask = noise_mask > (1 - salt_pepper_prob * (1 - salt_prob))  # 盐噪声掩码
        # 添加椒噪声(白色像素)
        noisy_image[salt_mask] = 255
        # 添加盐噪声(黑色像素)
        noisy_image[pepper_mask] = 0
        return noisy_image.astype(np.uint8)  # 确保返回的图像是uint8类型

    def add_gaussian_noise(self, image):
        """添加高斯噪声"""
        mean = 0  # 均值
        sigma = 25  # 标准差
        # 生成与图像大小相同的高斯噪声
        gaussian_noise = np.random.normal(mean, sigma, image.shape).astype(np.float32)
        # 将图像转换为浮点数进行运算,避免溢出
        noisy_image = cv2.add(image.astype(np.float32), gaussian_noise.astype(np.float32))
        # 将图像值裁剪到[0, 255]并转换为uint8类型
        return np.clip(noisy_image, 0, 255).astype(np.uint8)

    def add_uniform_noise(self, image):
        """添加均匀噪声"""
        low = -50  # 均匀噪声的下限
        high = 50  # 均匀噪声的上限
        # 生成与图像大小相同的均匀噪声
        uniform_noise = np.random.uniform(low, high, image.shape).astype(np.float32)
        # 将图像转换为浮点数进行运算,避免溢出
        noisy_image = cv2.add(image.astype(np.float32), uniform_noise.astype(np.float32))
        # 将图像值裁剪到[0, 255]并转换为uint8类型
        return np.clip(noisy_image, 0, 255).astype(np.uint8)

    def median_filter(self, image):
        """中值滤波"""
        return cv2.medianBlur(image, 5)  # 使用5x5的核进行中值滤波

    def gaussian_filter(self, image):
        """高斯滤波"""
        return cv2.GaussianBlur(image, (5, 5), 0)  # 使用5x5的核进行高斯滤波

    def mean_filter(self, image):
        """均值滤波"""
        return cv2.blur(image, (5, 5))  # 使用5x5的核进行均值滤波


    def process(self, mode):
        """根据选择的模式处理图像"""
        if 'processed' not in self.image_data:  # 如果没有处理过的图像,默认使用原始图像进行处理
            if 'original' not in self.image_data:
                QMessageBox.warning(self, "提示", "请先加载图片")  # 提示用户加载图像
                return
            img = self.image_data['original']  # 获取原始图像
        else:
            img = self.image_data['processed']  # 获取处理后的图像作为基础图像
        result = None  # 初始化处理结果
        try:
        # 根据模式选择处理方式
            if mode == "gray":  # 灰度化
                result = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度图
                result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)  # 转回三通道(为了显示一致)
                self.image_data['original'] = result
            elif mode == "denoise":  # 去噪处理
                # 弹出对话框让用户选择去噪方法
                denoise_type, ok = QInputDialog.getItem(
                    self, "选择去噪方法", "去噪方法", ["中值滤波", "高斯滤波", "均值滤波"], 0, False)
                if ok and denoise_type:
                    img_copy = img.copy()  # 使用副本进行操作
                    if denoise_type == "中值滤波":
                        result = self.median_filter(img_copy)
                    elif denoise_type == "高斯滤波":
                        result = self.gaussian_filter(img_copy)
                    elif denoise_type == "均值滤波":
                        result = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)

            elif mode == "sharpen":  # 锐化
                # 定义锐化卷积核
                kernel = np.array([[-1, -1, -1],
                                   [-1, 9, -1],
                                   [-1, -1, -1]])
                result = cv2.filter2D(img, -1, kernel)  # 应用卷积核
            elif mode == "rotate":  # 旋转
                # 顺时针旋转90度
                result = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
            elif mode == "add_noise":  # 添噪处理
        # 弹出对话框让用户选择噪声类型
                noise_type, ok = QInputDialog.getItem(
                    self, "选择噪声类型", "噪声类型", ["椒盐噪声", "高斯噪声", "均匀噪声"], 0, False)
                if ok and noise_type:
                    img_copy = img.copy()  # 使用副本进行操作
                    if noise_type == "椒盐噪声":
                        result = self.add_salt_pepper_noise(img_copy)
                    elif noise_type == "高斯噪声":
                        result = self.add_gaussian_noise(img_copy)
                    elif noise_type == "均匀噪声":
                        result = self.add_uniform_noise(img_copy)

                else:
                    return
            self.image_data['processed'] = result
            self.show_image(result, self.processed_label)

        except Exception as e:  # 添加异常处理
            QMessageBox.critical(self, "错误", f"处理失败: {str(e)}")

    def show_image(self, img, label):
        """显示图像"""
        try:
            rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 将BGR转为RGB格式
            h, w, ch = rgb.shape
            bytes_per_line = ch * w
            q_img = QImage(rgb.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)  # 转为QImage格式
            label.setPixmap(QPixmap.fromImage(q_img).scaled(400, 400, Qt.AspectRatioMode.KeepAspectRatio))  # 设置显示图像
        except Exception as e:
            QMessageBox.critical(self, "错误", f"显示图像时发生错误:{e}")  # 捕获并显示异常信息




if __name__ == "__main__":
    app = QApplication(sys.argv)  # 创建应用实例
    window = ImageProcessor()  # 创建主窗口实例
    window.show()  # 显示窗口
    sys.exit(app.exec())  # 进入事件循环,等待用户操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值