图像处理:基础——使用形态学操作提取水平线和垂直线 OpenCV v4.8.0

上一个教程Hit-or-Miss

下一个教程图像金字塔

原作者Theodore Tsesmelis
兼容性OpenCV >= 3.0

目标

在本教程中,你将学习如何

  • 通过创建自定义内核,应用两种非常常见的形态学运算符(即扩张和侵蚀),以提取水平轴和垂直轴上的直线。为此,您将使用以下 OpenCV 函数:

  • erode()

  • dilate()

  • getStructuringElement()

在一个例子中,您的目标是从乐谱中提取音符。

理论

形态学操作

形态学是一套图像处理操作,根据预定义的结构元素(也称为内核)处理图像。输出图像中每个像素的值是基于输入图像中相应像素与其邻近像素的比较。通过选择核的大小和形状,可以构建出对输入图像的特定形状敏感的形态学运算。

两种最基本的形态学操作是扩张和侵蚀。扩张是在图像中的物体边界上增加像素,而侵蚀则恰恰相反。添加或删除的像素数量分别取决于处理图像时所用结构元素的大小和形状。一般来说,这两种操作所遵循的规则如下:

  1. 膨胀: 输出像素的值是结构元素大小和形状范围内所有像素的最大值。例如,在二进制图像中,如果输入图像中位于内核范围内的任何一个像素被设置为 1,那么输出图像中相应的像素也将被设置为 1。后者适用于任何类型的图像(如灰度图像、二值图像等)。

在这里插入图片描述

在二值图像上放大

在这里插入图片描述

灰度图像上的扩张
  1. 腐蚀: 侵蚀操作也是如此。输出像素的值是结构元素大小和形状范围内所有像素的最小值。请看下面的示例图:

在这里插入图片描述

二值图像上的侵蚀

在这里插入图片描述

灰度图像上的侵蚀

结构元素

如上图所示,一般来说,在任何形态学操作中,用于探测输入图像的结构元素都是最重要的部分。

结构元素是一个仅由 0 和 1 组成的矩阵,可以有任意的形状和大小。通常情况下,结构元素要比被处理的图像小得多,而值为 1 的像素则定义了邻域。结构元素的中心像素称为原点,它标识了感兴趣的像素,也就是正在处理的像素。

例如,下图展示了一个 7x7 大小的菱形结构元素。

在这里插入图片描述

菱形结构元素及其原点
结构元素可以有很多常见的形状,如直线、菱形、圆盘、周期线、圆形和各种尺寸。您通常会选择与输入图像中要处理/提取的对象大小和形状相同的结构元素。例如,要查找图像中的线条,请创建一个线性结构元素,稍后您将会看到。 # **代码**

C++
本教程代码如下所示。

您也可以从此处下载。

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream
void show_wait_destroy(const char* winname, cv::Mat img)using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
 CommandLineParser parser(argc, argv, "{@input | notes.png | input image}");
 Mat src = imread( samples::findFile( parser.get<String>("@input") ), IMREAD_COLOR)if (src.empty())
 {
 cout << "Could not open or find the image! << endl;
 cout << "Usage: " << argv[0] << " <输入图像>" << endl;
 return -1}
 // 显示源图像
 imshow("src", src)// 如果源图像尚未变为灰色,则将其变为灰色
 Mat Gray;
 if (src.channels() == 3)
 {
 cvtColor(src, gray, COLOR_BGR2GRAY)}
 else
 {
 gray = src;
 }
 // 显示灰色图像
 show_wait_destroy("gray", gray)// 将自适应阈值(adaptiveThreshold)应用于 gray 的 bitwise_not,注意 ~ 符号
 Mat bw;
 adaptiveThreshold(~gray, bw, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2)// 显示二进制图像
 show_wait_destroy("binary", bw)// 创建用于提取水平线和垂直线的图像
 Mat horizontal = bw.clone();
 Mat vertical = bw.clone()// 指定水平轴的尺寸
 int horizontal_size = horizontal.cols / 30// 创建结构元素,用于通过形态学操作提取水平线条
 Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontal_size, 1))// 应用形态操作
 erode(horizontal, horizontal, horizontalStructure, Point(-1, -1))dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1))// 显示提取的水平线条
 show_wait_destroy("horizontal", horizontal)// 指定纵轴尺寸
 int vertical_size = vertical.rows / 30// 创建结构元素,用于通过形态学操作提取垂直线条
 Mat verticalStructure = getStructuringElement(MORPH_RECT, Size(1, vertical_size))// 应用形态操作
 erode(vertical, vertical, verticalStructure, Point(-1, -1))dilate(vertical, vertical, verticalStructure, Point(-1, -1))// 显示提取的垂直线条
 show_wait_destroy("vertical", vertical)// 反转垂直图像
 bitwise_not(vertical, vertical)show_wait_destroy("vertical_bit", vertical)// 根据逻辑提取边缘并平滑图像
 // 1.
 // 2.
 3. src.copyTo(smooth) // 4.
 // 4. 模糊平滑图像
 // 5. smooth.copyTo(src, edges)
 // 第 1 步
 Mat edges;
 adaptiveThreshold(vertical, edges, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2)show_wait_destroy("edges", edges)// 第 2 步
 Mat kernel = Mat::ones(2, 2, CV_8UC1)dilate(edges, edges, kernel)show_wait_destroy("dilate", edges)// 第三步
 Mat smooth;
 vertical.copyTo(smooth)// 第四步
 blur(smooth, smooth, Size(2, 2))// 第五步
 smooth.copyTo(vertical, edges)// 显示最终结果
 show_wait_destroy("smooth - final", vertical)return 0}
void show_wait_destroy(const char* winname, cv::Mat img) {
 imshow(winname, img)moveWindow(winname, 500, 0)waitKey(0)destroyWindow(winname)}

Java
本教程代码如下所示。

您也可以从此处下载。

import org.opencv.core.*import org.opencv.highgui.HighGuiimport org.opencv.imgcodecs.Imgcodecsimport org.opencv.imgproc.Imgprocclass Morphology_3Run {
 public void run(String[] args) { // Check the number of arguments.
 // 检查参数数
 if (args.length == 0){
 System.out.println("Not enough parameters!")System.out.println("Program Arguments: [image_path]")System.exit(-1)}
 // 加载图像
 Mat src = Imgcodecs.imread(args[0])// 检查图片加载是否正常
 if( src.empty() ) {
 System.out.println("Error opening image: " + args[0])System.exit(-1)}
 // 显示源图像
 HighGui.imshow("src", src)// 将源图像转换为灰色(如果尚未转换的话
 Mat gray = new Mat()if (src.channels() == 3)
 {
 Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY)}
 else
 {
 gray = src;
 }
 // 显示灰色图像
 showWaitDestroy("gray" , gray)// 在灰色的比特阈值上应用自适应阈值
 Mat bw = new Mat();
 将自适应阈值应用于灰色图像的 bitwise_not(gray, gray);
 Imgproc.adaptiveThreshold(gray, bw, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 15, -2)// 显示二进制图像
 showWaitDestroy("binary" , bw)// 创建用于提取水平线和垂直线的图像
 Mat horizontal = bw.clone()Mat vertical = bw.clone()// 指定水平轴的尺寸
 int horizontal_size = horizontal.cols() / 30// 创建结构元素,用于通过形态学操作提取水平线条
 Mat horizontalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(horizontal_size,1))// 应用形态操作
 Imgproc.erode(horizontal, horizontal, horizontalStructure)Imgproc.dilate(horizontal, horizontal, horizontalStructure)// 显示提取的水平线条
 showWaitDestroy("horizontal" , horizontal)// 指定纵轴尺寸
 int vertical_size = vertical.rows() / 30// 创建结构元素,用于通过形态学操作提取垂直线条
 Mat verticalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size( 1,vertical_size))// 应用形态操作
 Imgproc.erode(vertical, vertical, verticalStructure)Imgproc.dilate(vertical, vertical, verticalStructure)// 显示提取的垂直线条
 showWaitDestroy("vertical", vertical)// 反转垂直图像
 Core.bitwise_not(vertical, vertical)showWaitDestroy("vertical_bit" , vertical)// 根据逻辑提取边缘并平滑图像
 // 1.
 // 2.
 src.copyTo(smooth) // 4.
 // 4. 模糊平滑图像
 // 5. smooth.copyTo(src, edges)
 // 第 1 步
 Mat edges = new Mat()Imgproc.adaptiveThreshold(vertical, edges, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 3, -2)showWaitDestroy("edges", edges)// 第 2 步
 Mat kernel = Mat.ones(2, 2, CvType.CV_8UC1)Imgproc.dilate(edges, edges, kernel)showWaitDestroy("dilate", edges)// 第三步
 Mat smooth = new Mat();
 vertical.copyTo(smooth)// 第四步
 Imgproc.blur(smooth, smooth, new Size(2, 2))// 第 5 步
 smooth.copyTo(vertical, edges)// 显示最终结果
 ShowWaitDestroy("smooth - final", vertical)System.exit(0)}
 private void showWaitDestroy(String winname, Mat img) {
 HighGui.imshow(winname, img)HighGui.moveWindow(winname, 500, 0)HighGui.waitKey(0)HighGui.destroyWindow(winname)}
}
public class Morphology_3 {
 public static void main(String[] args) { 
 // 加载本地库。
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME)new Morphology_3Run().run(args)}
}

Python
本教程代码如下所示。

您也可以从此处下载。

"""
@file morph_lines_detection.py
@brief 使用形态变换提取水平线和垂直线 示例代码
"""
import numpy 为 np
import sys
import cv2 as cv
def show_wait_destroy(winname, img):
 cv.imshow(winname, img)
 cv.moveWindow(winname, 500, 0)
 cv.waitKey(0)
 cv.destroyWindow(winname)
def main(argv)# [load_image]
 # 检查参数数量
 if len(argv) < 1print ('Not enough parameters')
 print ('Usage:\nmorph_lines_detection.py < path_to_image >')
 return -1
 # 加载图像
 src = cv.imread(argv[0], cv.IMREAD_COLOR)
 # 检查图片加载是否正常
 if src is Noneprint ('Error opening image: ' + argv[0])
 return -1
 # 显示源图像
 cv.imshow("src", src)
 # [load_image]
 # [gray]
 # 如果源图像尚未变为灰色,则将其变为灰色
 if len(src.shape) != 2:
 gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
 else
 gray = src
 # 显示灰色图像
 show_wait_destroy("gray", gray)
 # [gray]
 # [bin]
 # 在灰色的 bitwise_not 处应用自适应阈值,注意 ~ 符号
 gray = cv.bitwise_not(gray)
 bw = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, \
 cv.THRESH_BINARY, 15, -2)
 # 显示二进制图像
 show_wait_destroy("binary", bw)
 # [bin]
 # [init]
 # 创建用于提取水平线和垂直线的图像
 horizontal = np.copy(bw)
 vertical = np.copy(bw)
 # [init]
 # [horiz]
 # 指定水平轴的尺寸
 cols = horizontal.shape[1]
 horizontal_size = cols // 30
 # 创建结构元素,用于通过形态学操作提取水平线条
 horizontalStructure = cv.getStructuringElement(cv.MORPH_RECT, (horizontal_size, 1))
 # 应用形态学操作
 horizontal = cv.erode(horizontal, horizontalStructure)
 horizontal = cv.dilate(horizontal, horizontalStructure)
 # 显示提取的水平线条
 show_wait_destroy("horizontal", horizontal)
 # [horiz]
 # [vert]
 # 指定纵轴尺寸
 rows = vertical.shape[0]
 verticalsize = rows // 30
 # 创建结构元素,用于通过形态学操作提取垂直线条
 verticalStructure = cv.getStructuringElement(cv.MORPH_RECT, (1, verticalsize))
 # 应用形态学操作
 vertical = cv.erode(vertical, verticalStructure)
 vertical = cv.dilate(vertical, verticalStructure)
 # 显示提取的垂直线
 show_wait_destroy("vertical", vertical)
 # [vert]
 # [smooth]
 # 反垂直图像
 vertical = cv.bitwise_not(vertical)
 show_wait_destroy("vertical_bit", vertical)
 '''
 根据逻辑提取边缘并平滑图像
 1. 提取边缘
 2. dilate(edges)
 3. src.copyTo(smooth)
 4. 模糊平滑图像
 5. smooth.copyTo(src, edges)
 '''
 # 第 1 步
 edges = cv.adaptiveThreshold(vertical, 255, cv.ADAPTIVE_THRESH_MEAN_C,\)
 cv.THRESH_BINARY, 3, -2)
 show_wait_destroy("edges", edges)
 # 第 2 步
 kernel = np.ones((2, 2), np.uint8)
 edges = cv.dilate(edges, kernel)
 show_wait_destroy("dilate", edges)
 # 第三步
 smooth = np.copy(vertical)
 # 第四步
 smooth = cv.blur(smooth, (2, 2))
 # 第五步
 (rows, cols) = np.where(edges != 0)
 vertical[rows, cols] = smooth[rows, cols] # 显示最终结果
 # 显示最终结果
 show_wait_destroy("smooth - final", vertical)
 # [smooth]
 return 0
if __name__ == "__main__":
 main(sys.argv[1:])

说明/结果

此处获取图像。

加载图片
C++

 CommandLineParser parser(argc, argv, "{@input | notes.png | input image}");
 Mat src = imread( samples::findFile( parser.get<String>("@input") ), IMREAD_COLOR)if (src.empty())
 {
 cout << "Could not open or find the image! << endl;
 cout << "Usage: " << argv[0] << " <Input Image>" << endl;
 return -1}
 // 显示源图像
 imshow("src", src)

Java

 // 检查参数数量
 if (args.length == 0){
 System.out.println("Not enough parameters!")System.out.println("Program Arguments: [image_path]")System.exit(-1)}
 // 加载图像
 Mat src = Imgcodecs.imread(args[0])// 检查图片加载是否正常
 if( src.empty() ) {
 System.out.println("Error opening image: " + args[0])System.exit(-1)}
 // 显示源图像
 HighGui.imshow("src", src)

Python

 # 检查参数数量
 如果 len(argv) < 1print ('Not enough parameters')
 print ('Usage:\nmorph_lines_detection.py < path_to_image >')
 return -1
 # 加载图像
 src = cv.imread(argv[0], cv.IMREAD_COLOR)
 # 检查图片加载是否正常
 if src 为 Noneprint ('Error opening image: ' + argv[0])
 return -1
 # 显示源图像
 cv.imshow("src", src)

在这里插入图片描述

灰度

C++

 // 如果源图像尚未变为灰色,则将其变为灰色
 Mat Gray;
 if (src.channels() == 3)
 {
 cvtColor(src, gray, COLOR_BGR2GRAY)}
 else
 {
 gray = src;
 }
 // 显示灰色图像
 show_wait_destroy("gray", gray)

Java

 // 如果源图像尚未变为灰色,则将其变为灰色
 Mat gray = new Mat()if (src.channels() == 3)
 {
 Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY)}
 else
 {
 gray = src;
 }
 // 显示灰色图像
 showWaitDestroy("gray" , gray)

Python

 # 如果源图像尚未转换为灰色,则将其转换为灰色
 if len(src.shape) != 2:
 gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
 else
 gray = src
 # 显示灰色图像
 show_wait_destroy("gray", gray)

在这里插入图片描述

将灰度图像转换为二进制图像

C++

 // 将自适应阈值应用于比特灰度,注意 ~ 符号
 Mat bw;
 adaptiveThreshold(~gray, bw, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2)// 显示二进制图像
 show_wait_destroy("binary", bw)

Java

 // 在比特灰度级应用自适应阈值
 Mat bw = new Mat()Core.bitwise_not(gray, gray);
 Imgproc.adaptiveThreshold(gray, bw, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 15, -2)// 显示二进制图像
 showWaitDestroy("binary" , bw)

Python

 # 在灰色的 bitwise_not 处应用自适应阈值,注意 ~ 符号
 gray = cv.bitwise_not(gray)
 bw = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, \
 cv.THRESH_BINARY, 15, -2)
 # 显示二进制图像
 show_wait_destroy("binary", bw)

在这里插入图片描述

输出图像
现在,我们可以应用形态学操作来提取水平线和垂直线,从而将音符从乐谱中分离出来,但首先要初始化输出图像:

C++

 // 创建用于提取水平线和垂直线的图像
 Mat horizontal = bw.clone();
 Mat vertical = bw.clone()

Java

 // 创建用于提取水平线和垂直线的图像
 Mat horizontal = bw.clone()Mat vertical = bw.clone()

Python


结构元素
正如我们在理论中所述,为了提取我们想要的对象,我们需要创建相应的结构元素。由于我们要提取水平线条,因此相应的结构元素将具有如下形状:

在这里插入图片描述

在源代码中,这可以用下面的代码片段来表示:

C++

 // 指定水平轴的大小
 int horizontal_size = horizontal.cols / 30// 创建结构元素,用于通过形态学操作提取水平线条
 Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontal_size, 1))// 应用形态操作
 erode(horizontal, horizontal, horizontalStructure, Point(-1, -1))dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1))// 显示提取的水平线条
 show_wait_destroy("horizontal", horizontal)

Java

 // 指定横轴尺寸
 int horizontal_size = horizontal.cols() / 30// 创建结构元素,用于通过形态学操作提取水平线条
 Mat horizontalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(horizontal_size,1))// 应用形态操作
 Imgproc.erode(horizontal, horizontal, horizontalStructure)Imgproc.dilate(horizontal, horizontal, horizontalStructure)// 显示提取的水平线条
 showWaitDestroy("horizontal" , horizontal)

Python

 # 指定水平轴的尺寸
 cols = horizontal.shape[1]
 horizontal_size = cols // 30
 # 创建结构元素,用于通过形态学操作提取水平线条
 horizontalStructure = cv.getStructuringElement(cv.MORPH_RECT, (horizontal_size, 1))
 # 应用形态学操作
 水平 = cv.erode(horizontal, horizontalStructure)
 水平 = cv.dilate(horizontal, horizontalStructure)
 # 显示提取的水平线条
 show_wait_destroy("horizontal", horizontal)

在这里插入图片描述

同样的方法也适用于垂直线条,并使用相应的结构元素:

在这里插入图片描述

同样表示如下:

C++

 // 指定纵轴尺寸
 int vertical_size = vertical.rows / 30// 创建结构元素,用于通过形态学操作提取垂直线条
 Mat verticalStructure = getStructuringElement(MORPH_RECT, Size(1, vertical_size))// 应用形态操作
 erode(vertical, vertical, verticalStructure, Point(-1, -1))dilate(vertical, vertical, verticalStructure, Point(-1, -1))// 显示提取的垂直线条
 show_wait_destroy("vertical", vertical)

Java

 // 指定纵轴尺寸
 int vertical_size = vertical.rows() / 30// 创建结构元素,用于通过形态学操作提取垂直线条
 Mat verticalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size( 1,vertical_size))// 应用形态操作
 Imgproc.erode(vertical, vertical, verticalStructure)Imgproc.dilate(vertical, vertical, verticalStructure)// 显示提取的垂直线条
 showWaitDestroy("vertical", vertical)

Python

 # 指定纵轴尺寸= vertical.shape[0]
 verticalsize = rows // 30
 # 创建结构元素,用于通过形态学操作提取垂直线条
 verticalStructure = cv.getStructuringElement(cv.MORPH_RECT, (1, verticalsize))
 # 应用形态学操作
 vertical = cv.erode(vertical, verticalStructure)
 vertical = cv.dilate(vertical, verticalStructure)
 # 显示提取的垂直线
 show_wait_destroy("vertical", vertical)

在这里插入图片描述

细化边缘 / 结果
正如你所看到的,我们就快成功了。不过,这时你会发现音符的边缘有点粗糙。因此,我们需要细化边缘,以获得更平滑的效果:

C++

 // 反垂直图像
 bitwise_not(vertical, vertical)show_wait_destroy("vertical_bit", vertical)// 根据逻辑提取边缘并平滑图像
 // 1.
 // 2.
 3. src.copyTo(smooth) // 4.
 // 4. 模糊平滑图像
 // 5. smooth.copyTo(src, edges)
 // 第 1 步
 Mat edges;
 adaptiveThreshold(vertical, edges, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2)show_wait_destroy("edges", edges)// 第 2 步
 Mat kernel = Mat::ones(2, 2, CV_8UC1)dilate(edges, edges, kernel)show_wait_destroy("dilate", edges)// 第三步
 Mat smooth;
 vertical.copyTo(smooth)// 第四步
 blur(smooth, smooth, Size(2, 2))// 第五步
 smooth.copyTo(vertical, edges)// 显示最终结果
 show_wait_destroy("smooth - final", vertical)

Java

 // 反垂直图像
 Core.bitwise_not(vertical, vertical)showWaitDestroy("vertical_bit" , vertical)// 根据逻辑提取边缘并平滑图像
 // 1.
 // 2.
 src.copyTo(smooth) // 4.
 // 4. 模糊平滑图像
 // 5. smooth.copyTo(src, edges)
 // 第 1 步
 Mat edges = new Mat()Imgproc.adaptiveThreshold(vertical, edges, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 3, -2)showWaitDestroy("edges", edges)// 第 2 步
 Mat kernel = Mat.ones(2, 2, CvType.CV_8UC1)Imgproc.dilate(edges, edges, kernel)showWaitDestroy("dilate", edges)// 第三步
 Mat smooth = new Mat();
 vertical.copyTo(smooth)// 第四步
 Imgproc.blur(smooth, smooth, new Size(2, 2))// 第 5 步
 smooth.copyTo(vertical, edges)// 显示最终结果
 showWaitDestroy("smooth - final", vertical)

Python

 # 反垂直图像
 vertical = cv.bitwise_not(vertical)
 show_wait_destroy("vertical_bit", vertical)
 '''
 根据逻辑提取边缘并平滑图像
 1. 提取边缘
 2. dilate(edges)
 3. src.copyTo(smooth)
 4. 模糊平滑图像
 5. smooth.copyTo(src, edges)
 '''
 # 第 1 步
 edges = cv.adaptiveThreshold(vertical, 255, cv.ADAPTIVE_THRESH_MEAN_C,\)
 cv.THRESH_BINARY, 3, -2)
 show_wait_destroy("edges", edges)
 # 第 2 步
 kernel = np.ones((2, 2), np.uint8)
 edges = cv.dilate(edges, kernel)
 show_wait_destroy("dilate", edges)
 # 第三步
 smooth = np.copy(vertical)
 # 第四步
 smooth = cv.blur(smooth, (2, 2))
 # 第五步
 (rows, cols) = np.where(edges != 0)
 vertical[rows, cols] = smooth[rows, cols] # 显示最终结果
 # 显示最终结果
 show_wait_destroy("smooth - final", vertical)


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值