OpenCV(9)几何形状识别、ROI操作、图像去噪 C++

18 篇文章 9 订阅

1.几何形状识别

#include <opencv2/opencv.hpp>
#include <iostream>
#define MATCHMETHOD TM_SQDIFF_NORMED//宏定义匹配模式
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
    Mat src = imread("./image/test10.jpg");
    Mat src_gray, binary;
    Mat Triangle = src.clone(), Rect = src.clone(), BigCircle = src.clone(), SmallCircle = src.clone();
    if (src.empty()) {
        printf("Could not load image...");
        return -1;
    }
    imshow("Input Image", src);
    //二值化
    cvtColor(src, src_gray, COLOR_BGR2GRAY);
    threshold(src_gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
    binary = ~binary;
    imshow("binary", binary);
    //发现轮廓
    vector<vector<Point>> contours;
    vector<Point> point;
    vector<Vec4i> hireachy;
    findContours(binary, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    //绘制出所有轮廓
    for (size_t t = 0; t < contours.size(); t++)
    {
        int epsilon = 0.01 * arcLength(contours[t], true);
        approxPolyDP(contours[t], point, epsilon, true);
        if (point.size() == 3)
        {
            drawContours(Triangle, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
        }
        else if (point.size() == 4)
        {
            drawContours(Rect, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
        }
        else
        {
            double area = contourArea(contours[t]);
            if (area < 15000)
            {
                drawContours(SmallCircle, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
            }
            else
            {
                drawContours(BigCircle, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
            }
        }
        cout << "边的数目:" << point.size() << endl;
    }
    imshow("Triangle", Triangle);
    imshow("BigCircle", BigCircle);
    imshow("Rect", Rect);
    imshow("SmallCircle", SmallCircle);
    waitKey(0);
    return 0;
}

执行结果:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

1.1.(cv :: approxPolyDP)多边形逼近

对指定的点集进行多边形逼近的函数,其逼近的精度可通过参数设置。

void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
  • curve:输入的点集。
  • approxCurve:输出的点集,当前点集是能最小包容指定点集的,draw出来即是一个多边形;
  • epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离。
  • closed:若为true,则说明近似曲线是闭合的,它的首位都是相连,反之,若为false,则断开。

1.2.(cv :: drawContours)绘制轮廓或者填充轮廓

说明:该函数用于绘制图像中的轮廓(若thickness >= 0)或者填充轮廓所包围的区域(若thickness < 0)

CV_EXPORTS_W void drawContours( InputOutputArray image, InputArrayOfArrays contours,
                              int contourIdx, const Scalar& color,
                              int thickness = 1, int lineType = LINE_8,
                              InputArray hierarchy = noArray(),
                              int maxLevel = INT_MAX, Point offset = Point() );
  • image:目标图像
  • contours:输入的所有轮廓(每个轮廓以点集的方式存储)
  • contoursIdx:指定绘制轮廓的下标(若为负数,则绘制所有轮廓)
  • color:绘制轮廓的颜色
  • thickness:绘制轮廓的线的宽度(若为负数,则填充轮廓内部)
  • lineType:绘制轮廓的线型(4连通、8连通或者反锯齿)
  • hierarchy:关于层级的可选信息,仅用于当你想要绘制部分轮廓的时候
  • maxLevel:绘制轮廓的最大层级,若为0,则仅仅绘制指定的轮廓;若为1,则绘制该轮廓及其内嵌轮廓,若为2,则绘制该轮廓、其内嵌轮廓以及内嵌轮廓的内嵌轮廓,依次类推。该参数只有在有层级信息输入时才被考虑。
  • offset:可选的轮廓偏移参数,所有的轮廓将会进行指定的偏移

注:
当thickness = FILLED,即使没有提供层级信息也可以正确处理带孔洞的连通域情况(分析轮廓时采用奇偶规则),但如果是单独检索的轮廓合并则可能会出现错误的情况,该情况下则需要分开处理。

1.3.(cv :: arcLength)计算封闭轮廓的周长或曲线的长度

1.4.(cv :: contourArea)计算轮廓面积

double contourArea(InputArray contour, bool oriented = false);
  • contour:输入的二维点集(轮廓顶点),可以是 vector 或 Mat 类型。
  • oriented:面向区域标识符。有默认值 false。若为
    true,该函数返回一个带符号的面积值,正负取决于轮廓的方向(顺时针还是逆时针)。若为 false,表示以绝对值返回。

在这里插入图片描述
寻找轮廓时,取的点为像素中心点,连接起来的黑线就是这个图形的轮廓,那么计算的周长应该是4个对角+4个三角(42+42*≈19.313708499),与计算机输出的结果一致。

double arcLength(InputArray curve, bool closed);
  • curve:输入的二维点集(轮廓顶点),可以是 vector 或 Mat 类型。
  • closed:用于指示曲线是否封闭。
#include<iostream>
#include<vector>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main() 
{
	Mat A = Mat::zeros(300, 350, CV_8UC1);
	rectangle(A, Point(10, 100), Point(50, 140), Scalar(255, 0, 0));//画矩形 draw rectangle
	circle(A, Point2i(100, 100), 3, 255, -1);
	circle(A, Point2i(250, 100), 50, 255, -1);
	//寻找轮廓
	vector<vector<Point> >contours;
	findContours(A, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
	//计算并输出面积周长
	Mat dst = Mat::zeros(A.size(), A.type());
	RNG rngs = { 12345 };
	for (int i = 0; i < contours.size(); i++) 
	{
		Scalar colors = Scalar(rngs.uniform(0, 255), rngs.uniform(0, 255), rngs.uniform(0, 255));
		drawContours(dst, contours, i, colors, 1);
		cout << i << " 的面积:" << contourArea(contours[i]) << endl;
		cout << "  周长:" << arcLength(contours[i], true) << endl;
	}
	imshow("A", A);
	waitKey(0);
}

执行结果:
在这里插入图片描述

#include <opencv2/opencv.hpp>
#include <iostream>
#define MATCHMETHOD TM_SQDIFF_NORMED//宏定义匹配模式
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
    Mat src = imread("../data/test6.jpg");
    Mat src_gray, binary;
    Mat Triangle = src.clone(), Rect = src.clone(), BigCircle = src.clone(), SmallCircle = src.clone();
    if (src.empty()) {
        printf("Could not load image...");
        return -1;
    }
    imshow("Input Image", src);
    //二值化
    cvtColor(src, src_gray, COLOR_BGR2GRAY);
    threshold(src_gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
    binary = ~binary;
    imshow("binary", binary);
    //发现轮廓
    vector<vector<Point>> contours;
    vector<Point> point;
    vector<Vec4i> hireachy;
    findContours(binary, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    //绘制出所有轮廓
    for (size_t t = 0; t < contours.size(); t++)
    {
        int epsilon = 0.01 * arcLength(contours[t], true);
        approxPolyDP(contours[t], point, epsilon, true);
        if (point.size() == 3)
        {
            drawContours(Triangle, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
        }
        else if (point.size() == 4)
        {
            drawContours(Rect, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
        }
        else
        {
            double area = contourArea(contours[t]);
            if (area < 15000)
            {
                drawContours(SmallCircle, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
            }
            else
            {
                drawContours(BigCircle, contours, t, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point());//dst必须先初始化
            }
        }
        cout << "边的数目:" << point.size() << endl;
    }
    imshow("Triangle", Triangle);
    imshow("BigCircle", BigCircle);
    imshow("Rect", Rect);
    imshow("SmallCircle", SmallCircle);
    waitKey(0);
    return 0;
}

执行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.图像ROI与ROI操作

在图像处理的领域,我们常常需要去设置自己感兴趣的区域(ROI,region of interest),来专注或者简化工作过程。也就是从图像中选择的一个图像区域,这个区域是图像分析所关注的重点。我们圈定这个区域,以便进行下一步的处理。而且,使用ROI指定想读入的目标,可以减少处理时间,增加精度,给图像处理带来不小的便利。

2.1.(cv :: inRange)提取图像中在阈值中间的部分

void cvInRange( const CvArr* src, const CvArr* lower,  const CvArr* upper,CvArr* dst  )
  • src:目标图像
  • lower:阈值下限
  • upper:阈值上限
  • dst:结果图像
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
    Mat src = imread("E:../data/test8.jpg");
    namedWindow("input", WINDOW_AUTOSIZE);
    imshow("input", src);
    int h = src.rows;
    int w = src.cols;
    //get ROI
    int cy = h / 2;
    int cx = w / 2;
    Rect rect(cx - 100, cy - 100, 200, 200);
    Mat roi = src(rect);
    imshow("roi", roi);
    Mat image = roi.clone();
    // modify ROI
    roi.setTo(Scalar(255, 0, 0));
    imshow("result", src);
    //modif copy roi
    image.setTo(Scalar(255, 0, 0));
    imshow("result", src);
    imshow("copy roi", image);
    //example with ROI - generate mask
    Mat src2 = imread("E:../data/test3.jpg");
    imshow("src2", src2);
    Mat hsv, mask;
    cvtColor(src2, hsv, COLOR_BGR2HSV);
    //That is, dst (I) is set to 255 (all 1 -bits) if src (I) is within the
    //specified 1D, 2D, 3D, ... box and 0 otherwise.
    inRange(hsv, Scalar(100, 43, 46), Scalar(124, 255, 255), mask);//within the wise set to 255,if not,set 0
    imshow("mask", mask);//get the mask exclusive person
    //extract person ROI
    Mat person;
    bitwise_not(mask, mask);
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    morphologyEx(mask, mask, MORPH_CLOSE, kernel);
    bitwise_and(src2, src2, person, mask);
    imshow("person", person);
    //generate background
    Mat result = Mat::zeros(src2.size(), src2.type());
    result.setTo(Scalar(0, 0, 255));
    //combine background + person
    Mat dst;
    bitwise_not(mask, mask);
    bitwise_or(person, result, dst, mask);
    add(dst, person, dst);
    imshow("dst", dst);
    waitKey(0);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

2.2.(cv :: Rect)

原文链接:https://kings.blog.csdn.net/article/details/83857914

 Rect(int x, int y, int width, int height);

参数含义: Rect(左上角x坐标 , 左上角y坐标,矩形的宽,矩形的高)
例如我们画一个图 Rect(20,50,30,40), 我用matlab画了一下,比较直观
在这里插入图片描述
那对于Rect(20,50,30,40)有哪些常用的操作?

rect.area(); //返回面积,1200
rect.size();//返回尺寸,30x40
rect.tl();// 返回左上角坐标(20,50)
rect.br();//返回右下角坐标(50,10)
rect.width();//返回宽度30
rect.height();//返回高度40
rect.contains(Point(x,y)) ; //返回布尔true/false, 判断x,y是否在这个矩形中

交集、并集, 矩阵对比,很像C语言

rect = rect1 & rect2;
rect = rect1 | rect2;
rect1 == rect2; //返回布尔值
rect1 != rect2 ; //返回布尔值

Rectangle用法

void cvRectangle( CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color,
                 int thickness=1, int line_type=8, int shift=0 );
  • img: 图像.
  • pt1 :矩形的一个顶点。
  • pt2:矩形对角线上的另一个顶点
  • color:线条颜色 (RGB) 或亮度(灰度图像 )(grayscale image)。
    //后面这三个都是可有可没有的
  • thickness:组成矩形的线条的粗细程度。取负值时(如 CV_FILLED)函数绘制填充了色彩的矩形。
  • line_type:线条的类型。见cvLine的描述
  • shift:坐标点的小数点位数。
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
    Mat src = imread("../data/image2.png");
    namedWindow("input", WINDOW_AUTOSIZE);
    imshow("input", src);
    char file_img[100];
    float x = 5, y=25, w=37, h=80 ;
    String num[11] = { " ","1","2","3","4","5","6","7","8","9","0"};
    for (int i = 1; i <= 10; i++)
    {
        Rect rect(x, y, w, h);
        x += 38.5;
        Mat roi = src(rect);
        imshow(num[i], roi); 
        sprintf_s(file_img, "../data/number/%s.jpg",num[i]);// 给file_img赋值:1.jpg 2.jpg等
        imwrite(file_img, roi);
    }
    waitKey(0);
    return 0;
}

执行结果:
在这里插入图片描述
在这里插入图片描述

3.图像去噪

原文链接:https://www.cnblogs.com/Vince-Wu/p/11855101.html

3.1.(cv :: blur)均值滤波

void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
  • src:输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U,
    CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
  • dst:即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
  • ksize:内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度,
    h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
  • anchor:表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
  • borderType:用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。

3.2.(cv :: medianBlur)中值滤波

将图像的每个像素用邻域 (以当前像素为中心的正方形区域)像素的 中值 代替 。

void medianBlur( const Mat& src, Mat& dst, int ksize );
  • src:输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U,
  • dst:即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
  • ksize:内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度,

3.3.(cv :: GaussianBlur)高斯滤波

将输入数组的每一个像素点与 高斯内核 卷积,将卷积和当作输出像素值。

void GaussianBlur( const Mat& src, Mat& dst, Size ksize,double sigmaX, double sigmaY=0,int borderType=BORDER_DEFAULT );
  • src:输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U,
  • dst:即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
  • ksize:内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度,
  • sigmaX:x方向的标准方差。可设置为0让系统自动计算。
  • sigmaY:y方向的标准方差。可设置为0让系统自动计算。

3.4.(cv :: fastNlMeansDenoisingColored)非局部均值去噪声

(cv2 :: fastNlMeansDenoising)使用单个灰度图像
(cv2 :: fastNlMeansDenoisingColored)使用彩色图像。
(cv2 :: fastNlMeansDenoisingMulti)用于在短时间内捕获的图像序列(灰度图像)
(cv2 :: fastNlMeansDenoisingColoredMulti)与上面相同,但用于彩色图像。

fastNlMeansDenoisingColored( InputArray src, 
				OutputArray dst,
                  float h = 3, float hColor = 3,
         int templateWindowSize = 7, int searchWindowSize = 21)
  • src:输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U,
  • dst:即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
  • h:决定过滤器强度。h 值高可以很好的去除噪声,但也会把图像的细节抹去。(取 10 的效果不错)
  • hColor :与 h 相同,但使用与彩色图像。(与 h 相同,10)
  • templateWindowSize:奇数。(推荐值为 7)
  • searchWindowSize:奇数。(推荐值为 21)
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void add_salt_pepper_noise(Mat& image);
void gaussian_noise(Mat& image);
int main(int artc, char** argv) {
	Mat src = imread("E:../data/test6.jpg");
	if (src.empty()) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", src);
	gaussian_noise(src);
	Mat result1, result2, result3, result4;
	blur(src, result1, Size(5, 5));
	imshow("result-1", result1);
	GaussianBlur(src, result2, Size(5, 5), 0);
	imshow("result-2", result2);
	medianBlur(src, result3, 5);
	imshow("result-3", result3);
	fastNlMeansDenoisingColored(src, result4, 15, 15, 10, 30);
	imshow("result-4", result4);
	waitKey(0);
	return 0;
}
void add_salt_pepper_noise(Mat& image) {
	RNG rng(12345);
	int h = image.rows;
	int w = image.cols;
	int nums = 10000;
	for (int i = 0; i < nums; i++) {
		int x = rng.uniform(0, w);
		int y = rng.uniform(0, h);
		if (i % 2 == 1) {
			image.at<Vec3b>(y, x) = Vec3b(255, 255, 255);
		}
		else {
			image.at<Vec3b>(y, x) = Vec3b(0, 0, 0);
		}
	}
	imshow("salt pepper", image);
}
void gaussian_noise(Mat& image) {
	Mat noise = Mat::zeros(image.size(), image.type());
	randn(noise, (15, 15, 15), (30, 30, 30));
	Mat dst;
	add(image, noise, dst);
	imshow("gaussian noise", dst);
	dst.copyTo(image);
}

执行结果:
在这里插入图片描述
在这里插入图片描述

3.5.快速的图像边缘滤波算法

高斯双边模糊与mean shift均值模糊两种边缘保留滤波算法,都因为计算量比较大,无法实时实现图像边缘保留滤波,限制了它们的使用场景,OpenCV中还实现了一种快速的边缘保留滤波算法。高斯双边与mean shift均值在计算时候使用五维向量是其计算量大速度慢的根本原因,该算法通过等价变换到低纬维度空间,实现了数据降维与快速计算。

CV_EXPORTS_W void edgePreservingFilter(InputArray src, OutputArray dst, int flags = 1,
        float sigma_s = 60, float sigma_r = 0.4f);
  • src:输入8位3通道图像。
  • dst:输出8位3通道图像。
  • flags:保持边缘的过滤器:
    ** recurs_filter ** = 1
    ** normconv_filter ** = 2
  • sigma_s:范围0到200。
  • sigma_r:0到1之间的范围。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int artc, char** argv) {
    Mat src = imread("./image/test3.jpg");
    if (src.empty()) {
        printf("could not load image...\n");
        return -1;
    }
    namedWindow("input", WINDOW_AUTOSIZE);
    imshow("input", src);
    Mat dst;
    double tt = getTickCount();
    edgePreservingFilter(src, dst, 1, 60, 0.44);
    double end = (getTickCount() - tt) / getTickFrequency();
    printf("time consume : %f\n ", end);
    imshow("result", dst);
    waitKey(0);
    return 0;
}

执行结果:
在这里插入图片描述

3.6.自定义滤波器

图像卷积最主要功能有图像模糊、锐化、梯度边缘等,前面已经分享图像卷积模糊的相关知识点,OpenCV除了支持上述的卷积模糊(均值与边缘保留)还支持自定义卷积核,实现自定义的滤波操作。自定义卷积核常见的主要是均值、锐化、梯度等算子。下面的三个自定义卷积核分别可以实现卷积的均值模糊、锐化、梯度功能。
在这里插入图片描述

CV_EXPORTS_W void filter2D( InputArray src, OutputArray dst, int ddepth,
                            InputArray kernel, Point anchor = Point(-1,-1),
                            double delta = 0, int borderType = BORDER_DEFAULT );
  • src:输入图像。
  • dst:输出与src相同大小和通道数的图像。
  • ddepth:目标图像的期望深度,参见@ref filter_depth“组合”
  • kernel:卷积核(或者说是相关核),一个单通道浮点数矩阵;如果您想将不同的内核应用到不同的通道,请将映像拆分为使用split来分离颜色平面并单独处理它们。
  • anchor:内核的锚点,它指示被过滤点的相对位置内核;锚应该位于内核中;默认值(-1,-1)表示锚是在核心中心。
  • delta:可选值添加到过滤像素之前存储他们在dst。
  • borderType:像素外推方法,参见#BorderTypes
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int artc, char** argv) {
    Mat src = imread("./image/test3.jpg");
    if (src.empty()) {
        printf("could not load image...\n");
        return -1;
    }
    namedWindow("input", WINDOW_AUTOSIZE);
    imshow("input", src);
    Mat kernel1 = Mat::ones(5, 5, CV_32F) / (float)(25);
    Mat kernel2 = (Mat_<char>(3, 3) << 0, -1, 0,
        -1, 5, -1,
        0, -1, 0);
    Mat kernel3 = (Mat_<int>(2, 2) << 1, 0, 0, -1);
    Mat dst1, dst2, dst3;
    filter2D(src, dst1, -1, kernel1);
    filter2D(src, dst2, -1, kernel2);
    filter2D(src, dst3, CV_32F, kernel3);
    convertScaleAbs(dst3, dst3);
    imshow("blur=5x5", dst1);
    imshow("shape=3x3", dst2);
    imshow("gradient=2x2", dst3);
    waitKey(0);
    return 0;
}

执行结果:
在这里插入图片描述

  • 0
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~晓广~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值