EmguCV学习笔记 C# 6.3 轮廓外接多边形

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。

EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。

教程VB.net版本请访问:EmguCV学习笔记 VB.Net 目录-CSDN博客

教程C#版本请访问:EmguCV学习笔记 C# 目录-CSDN博客

笔者的博客网址:https://blog.csdn.net/uruseibest

教程配套文件及相关说明以及如何获得pdf教程和代码,请移步:EmguCV学习笔记

学习VB.Net知识,请移步: vb.net 教程 目录_vb中如何用datagridview-CSDN博客

 学习C#知识,请移步:C# 教程 目录_c#教程目录-CSDN博客

6.3 轮廓外接多边形

6.3.1 最大外接矩形BoundingRectangle 

CvInvoke.BoundingRectangle方法用于获得轮廓的最大外接矩形。该方法其中一个声明:

public static Rectangle BoundingRectangle(

           IInputArray points

)

参数说明:

  1. points:要计算最大外接矩形的轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。

返回值:

返回一个Rectangle对象,表示计算得到的最大矩形的位置和大小。

代码参看6.3.2节【minAreaRect】中的示例。

6.3.2 最小外接矩形minAreaRect

在Emgu.CV中,CvInvoke.MinAreaRect函数用于计算轮廓的最小外接矩形。它的用法如下:

public static RotatedRect MinAreaRect(

           IInputArray points

)

参数说明:

  1. points:要计算最大外接矩形的轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。

返回值:

返回一个RotatedRect对象,表示计算得到的最小外接矩形。通过RotatedRect的GetVertices方法返回四个顶点,然后可使用CvInvoke.Polylines绘制。

【代码位置:frmChapter6】Button8_Click

       private void Button8_Click(object sender, EventArgs e)

        {

            Mat msrc = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Color);

            //转灰度

            Mat mgray = new Mat();

            CvInvoke.CvtColor(msrc, mgray, ColorConversion.Bgr2Gray);

            //二值化

            Mat mmid = new Mat();

            CvInvoke.Threshold(mgray, mmid, 150, 255, ThresholdType.BinaryInv);

            ImageBox1.Image = mmid;

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            VectorOfRect hierarchy = new VectorOfRect();

            //获得轮廓

            CvInvoke.FindContours(mmid, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);

            //绘制最大外接矩形的图像

            Mat mmax = new Mat();

            mmax = msrc.Clone();

            //绘制最小外接矩形的图像

            Mat mmin = new Mat();

            mmin = msrc.Clone();

            for(int i = 0;i< contours.Size;i++)

            {

                //获得最大外接矩形

                Rectangle rectmax = CvInvoke.BoundingRectangle(contours[i]);

                CvInvoke.Rectangle(mmax, rectmax, new MCvScalar(255), 2);

                //获得最小外接矩形

                RotatedRect rectmin = CvInvoke.MinAreaRect(contours[i]);

                PointF[] pf = rectmin.GetVertices();

                //实现数组PointF到数组Point的转换

                Point[] pf1= Array.ConvertAll(pf, new Converter<PointF, Point>(PointFToPoint));

                //不能按照矩形绘制,只能绘制多边形

                CvInvoke.Polylines(mmin, pf1, true, new MCvScalar(255, 0, 0), 2);

            }

            ImageBox2.Image = mmax;

            ImageBox3.Image = mmin;

        }

运行后如下图所示:

 

图6-9 绘制轮廓的最大外接矩形和最小外接矩形

【代码位置:frmChapter6】Button9_Click

        //外接多边形:最大外接矩形 最小外接矩形

        //Image来代替mat完成最后的绘制

        private void Button9_Click(object sender, EventArgs e)

        {

            Mat m1 = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Grayscale);

            ImageBox1.Image = m1;

            Mat mid1 = new Mat();

            CvInvoke.Threshold(m1, mid1, 150, 255, ThresholdType.BinaryInv);

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            VectorOfRect hierarchy = new VectorOfRect();

            CvInvoke.FindContours(mid1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);

            Image<Bgr, byte> m2 = new Image<Bgr, byte>("C:\\learnEmgucv\\shape2.jpg");

            //最大外接矩形

            Image<Bgr, byte> mmax = new Image<Bgr, byte>(m2.Width, m2.Height);

            mmax = m2.Clone();

            //最小外接矩形

            Image<Bgr, byte> mmin = new Image<Bgr, byte>(m2.Width, m2.Height);

            mmin = m2.Clone();

            for (int i = 0; i < contours.Size;i++)

            {

                //最大外接矩形

                Rectangle rectmax = CvInvoke.BoundingRectangle(contours[i]);

                mmax.Draw(rectmax, new Bgr(0, 255, 0), 2);

                //或者以下代码:

                //CvInvoke.Rectangle(mmax, rectmax, new MCvScalar(255), 2);

                RotatedRect rectmin = CvInvoke.MinAreaRect(contours[i]);

                mmin.Draw(rectmin, new Bgr(0, 255, 0), 2);

                //或者以下代码:

                //PointF[] pf = rectmin.GetVertices();

                //Point[] pf1 = Array.ConvertAll(pf, new Converter<PointF, Point>(PointFToPoint));

                //CvInvoke.Polylines(mmin, pf1, true, new MCvScalar(255, 0, 0), 2);

            }

            ImageBox2.Image = mmax;

            ImageBox3.Image = mmin;

        }

运行后如下图所示:

 

图6-10 绘制轮廓的最大外接矩形和最小外接矩形

6.3.3 多边形逼近ApproxPolyDP       

CvInvoke.ApproxPolyDP方法用于对轮廓进行多边形逼近。声明如下:

public static void ApproxPolyDP(

           IInputArray curve,

                    IOutputArray approxCurve,

                    double epsilon,

           bool closed

)

参数说明:

  1. curve:要进行逼近的轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。
  2. approxCurve:输出逼近后的多边形曲线。
  3. epsilon:逼近精度,即逼近后的多边形曲线与原轮廓的最大距离。
  4. closed:表示逼近后的多边形曲线是否是闭合的。如果为True,表示闭合;如果为False,表示开放。

【代码位置:frmChapter6】Button10_Click

        //外接多边形

        private void Button10_Click(object sender, EventArgs e)

        {

            Mat msrc = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Grayscale);

            ImageBox1.Image = msrc;

            Mat mid1 = new Mat();

            CvInvoke.Threshold(msrc, mid1, 150, 255, ThresholdType.BinaryInv);

            ImageBox2.Image = mid1;

            //查找轮廓

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            VectorOfRect hierarchy = new VectorOfRect();

            CvInvoke.FindContours(mid1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);

            Mat mpoly = new Mat(msrc.Size, DepthType.Cv8U, 3);

            mpoly.SetTo(new MCvScalar(255, 255, 255));

        for(int i = 0;i< contours.Size;i++)

            {

                VectorOfPoint result = new VectorOfPoint();

                //获得逼近后的多边形曲线

                CvInvoke.ApproxPolyDP(contours[i], result, 4, true);

                Point[] p = result.ToArray();

                //绘制多边形

                CvInvoke.Polylines(mpoly, p, true, new MCvScalar(255, 0, 0), 2);

                //输出多边形的边数

                CvInvoke.PutText(mpoly, p.Length.ToString(), new Point(p[p.Length - 1].X - 20, p[p.Length - 1].Y), FontFace.HersheyComplex, 1, new MCvScalar(0, 0, 255), 1);

            }

            ImageBox3.Image = mpoly;

        }

运行后如下图所示:

 

图6-11 绘制轮廓外接多边形

6.3.4 最小外接圆MinEnclosingCircle

CvInvoke.MinEnclosingCircle方法用于计算轮廓的最小外接圆。它的声明之一:

public static CircleF MinEnclosingCircle(

           IInputArray points

)

示例参看6.3.6节 【FitEllipse】代码。

6.3.5 最小外接三角形MinEnclosingTriangle

CvInvoke.MinEnclosingTriangle方法用于计算轮廓的最小外接三角形。它的声明如下:

public static double MinEnclosingTriangle(

    IInputArray points,

    IOutputArray triangles

)

示例参看6.3.6节 【FitEllipse】代码。

6.3.6 拟合椭圆FitEllipse     

CvInvoke.FitEllipse方法用于拟合给定点集的椭圆。它的声明如下:

public static RotatedRect FitEllipse(

           IInputArray points

)

【代码位置:frmChapter6】Button11_Click

        //最小外接三角形、最小外接圆、拟合椭圆

        private void Button11_Click(object sender, EventArgs e)

        {

            Mat m1 = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Grayscale);

            //ImageBox1.Image = m1;

            Mat mid1 = new Mat();

            CvInvoke.Threshold(m1, mid1, 150, 255, ThresholdType.BinaryInv);

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            VectorOfRect hierarchy = new VectorOfRect();

            CvInvoke.FindContours(mid1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);

            Mat m2 = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.AnyColor);

            //外接三角形

            Mat mtriangle = new Mat();

            mtriangle = m1.Clone();

            //外接圆形

            Mat mcircle = new Mat();

            mcircle = m1.Clone();

            //拟合椭圆

            Mat mEllipse = new Mat();

            mEllipse = m1.Clone();

            VectorOfPoint triangle = new VectorOfPoint();

            for (int i = 0; i < contours.Size; i++)

            {

                //获得外接三角形

                Double trianglearea = CvInvoke.MinEnclosingTriangle(contours[i], triangle);

                //使用封闭多边形来绘制三角形,也可以用line来绘制

                CvInvoke.Polylines(mtriangle, triangle, true, new MCvScalar(0), 2);

                CircleF cf = CvInvoke.MinEnclosingCircle(contours[i]);

                //获得外接圆形

                CvInvoke.Circle(mcircle, new Point((int)(cf.Center.X), (int)(cf.Center.Y)), (int)cf.Radius, new MCvScalar(0), 2);

                //获得拟合椭圆

                RotatedRect rr = CvInvoke.FitEllipse(contours[i]);

                CvInvoke.Ellipse(mEllipse, rr, new MCvScalar(0), 2);

            }

            ImageBox1.Image = mtriangle;

            ImageBox2.Image = mcircle;

            ImageBox3.Image = mEllipse;

        }

运行后如下图所示:

 

图6-12 轮廓的最小外接三角形、最小外接圆、拟合椭圆

6.3.7 拟合直线FitLine

拟合直线是指利用图像中的点集,找到一条最匹配点集的直线,使得该直线能够尽可能地覆盖所有的点。通过拟合直线,可以得到图像中点的分布情况,从而可以进行轮廓提取、边缘检测等操作。例如,在目标检测中,可以通过拟合直线来定位目标对象的位置和方向;在图像处理中,可以通过拟合直线来进行图像的分割、去除噪声、边缘检测等。

在Emgu.CV中,CvInvoke.FitLine函数用于拟合给定点集的直线,它的声明如下:

public static void FitLine(

           IInputArray points,

                    IOutputArray line,

                    DistType distType,

                    double param,

                    double reps,

           double aeps

)

参数说明:

  1. input:输入的点集。
  2. line:输出的直线,这是一个VectorOfFloat类型,包含四个元素,其中line(0),line(1)是一个方向向量;line(2),line(3)是直线上一个点。可以通过此输出获得直线的斜率和截距:

斜率 = line (1) / line (0)

截距 = line (3) – 斜率 * line (2)

  1. distType:距离类型,可以是DistType.L1或者DistType.L2。
  2. param:距离类型的参数,和所选的距离类型有关,值可以设置为0,FitLine本身会自动选择最优化的值。
  3. reps:拟合直线的径向精度,默认为0.01。
  4. aeps:拟合直线的角度精度,默认为0.01。

【代码位置:frmChapter6】Button12_Click

        //FitLine

        private void Button12_Click(object sender, EventArgs e)

        {

            Mat m = new Mat(500, 500, DepthType.Cv8U, 3);

            Random rand = new Random();

            Point[] pts = new Point[20];

            //设置随机点

            for (int i = 0; i < 20; i++)

            {

                int x = rand.Next(0, 500);

                int y = rand.Next(0, 500);

                pts[i] = new Point(x, y);

                CvInvoke.Circle(m, pts[i], 3, new MCvScalar(0, 0, 255), -1);

            }

            VectorOfPoint vop = new VectorOfPoint(pts);

            VectorOfFloat vof = new VectorOfFloat();

            CvInvoke.FitLine(vop, vof, DistType.L2, 0, 0.01, 0.01);

            //计算斜率

            Double k = vof[1] / vof[0];

            //计算截距

            Double b = vof[3] - k * vof[2];

            //获得直线起点

            Point startpoint = new Point(0, (int)(b));

            //获得直线终点

            Point endpoint = new Point(m.Width - 1, (int)(k * (m.Width - 1) + b));

            CvInvoke.Line(m, startpoint, endpoint, new MCvScalar(255, 0, 0), 2);

            ImageBox1.Image = m;

        }

运行后如下图所示:

 

图6-13 获得拟合直线

6.3.8 最短距离pointPolygonTest      

CvInvoke.PointPolygonTest方法用于计算给定点到多边形的最短距离,或判断点是否在多边形内部。该方法声明如下:

public static double PointPolygonTest(

           IInputArray contour,

                    PointF pt,

           bool measureDist

)

参数说明:

  1. contour:轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。
  2. pt:要计算的点的坐标。
  3. measureDist:如果设置为true,则计算最短距离;如果设置为false,则仅判断点是否在多边形内部。

返回值:

如果measureDist设置为true,则返回给定点到多边形的最短距离,正值表示点在多边形外部,负值表示点在多边形内部。

如果measureDist设置为false,则返回一个整数值:1表示点在多边形内部,0表示点在多边形边界上,-1表示点在多边形外部。

【代码位置:frmChapter6】Button13_Click

        //PointPolygonTest 轮廓外一点到轮廓的距离

        private void Button13_Click(object sender, EventArgs e)

        {

            Mat m = new Mat(400, 400, DepthType.Cv8U, 1);

            m.SetTo(new MCvScalar(0));

            //绘制一个矩形轮廓和一个圆形轮廓

            CvInvoke.Rectangle(m, new Rectangle(50, 50, 100, 100), new MCvScalar(255), -1);

            CvInvoke.Circle(m, new Point(300, 300), 100, new MCvScalar(255), -1);

            Mat m1 = new Mat();

            m1 = m.Clone();

            Point p1 = new Point(240, 100);

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            VectorOfRect hierarchy = new VectorOfRect();

            CvInvoke.FindContours(m1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);

            List<Double> dis = new List<Double>();

            for (int i = 0; i < contours.Size; i++)

            {

                Double dis1 = CvInvoke.PointPolygonTest(contours[i], p1, true);

                dis.Add(dis1);

                //只看在轮廓的内外

                //点在轮廓上,返回0,不计算距离返回0

                //点在轮廓内,返回正数,不计算距离返回1

                //点在轮廓外,返回负数,不计算距离返回-1

                //Double dis2 = CvInvoke.PointPolygonTest(contours[i], p1, false);

                //Console.WriteLine(dis2);

            }

            CvInvoke.Circle(m1, p1, 2, new MCvScalar(255), -1);

            ImageBox1.Image = m1;

            //使用获得的距离绘制圆形

            Mat m3 = new Mat(400, 400, DepthType.Cv8U, 3);

            m3.SetTo(new MCvScalar(0));

            CvInvoke.Rectangle(m3, new Rectangle(50, 50, 100, 100), new MCvScalar(255, 255, 255), -1);

            CvInvoke.Circle(m3, new Point(300, 300), 100, new MCvScalar(255, 255, 255), -1);

            CvInvoke.Circle(m3, p1, 2, new MCvScalar(0, 0, 255), -1);

            for (int i = 0; i < dis.Count; i++)

                CvInvoke.Circle(m3, p1, (int)Math.Abs(dis[i]), new MCvScalar(0, 255, 0), 2);

            ImageBox2.Image = m3;

        }

运行后如下图所示:

 

图6-14 点到轮廓的外接圆

上述代码只能获得最小距离,如果要得到轮廓外一点到轮廓最小距离的轮廓上一点的坐标,可以采用以下方法:

1、图像M1上绘制图形A

2、获得图形A的轮廓A(M1上只有一个轮廓)

3、获得点A到图形A的最小距离d

4、图像M2上使用点A为圆心,最小距离d为半径画圆A

5、M2和M1做异或,得到圆A

6、获得圆A的轮廓B(M2上只有一个圆形的轮廓)

7、获得轮廓B上的每个点到轮廓A的距离,判断是否符合条件:理论上等于0就表示该点在轮廓A上,但是通常不会直接判断=0而是给定一个范围。

那么符合条件的点即为需要的坐标点。

实现代码如下:

【代码位置:frmChapter6】Button14_Click

        //获得点到轮廓的最短距离

        //以点为圆心,最短距离为半径画圆

        //获得圆的轮廓上的点,这里可能会出现误差

        //求出每个点到轮廓的最短距离

        //如果为0,那么这个点在轮廓上,

        //但是通常而言,会有偏差

        //感觉此方法误差比较大

        private void Button14_Click(object sender, EventArgs e)

        {

            Mat m = new Mat(400, 400, DepthType.Cv8U, 1);

            m.SetTo(new MCvScalar(0));

            CvInvoke.Rectangle(m, new Rectangle(50, 50, 100, 100), new MCvScalar(255), -1);

            ImageBox1.Image = m;

            Point p1 = new Point(240, 100);

            Mat m1 = new Mat();

            m1 = m.Clone();

            //求出点到形状的距离

            VectorOfVectorOfPoint contours1 = new VectorOfVectorOfPoint();

            CvInvoke.FindContours(m1, contours1, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);

            //这里只有一个形状轮廓,所以直接使用contours[0]

            Double dis1 = CvInvoke.PointPolygonTest(contours1[0], p1, true);

            //获得最短距离后画圆

            CvInvoke.Circle(m1, p1, (int)Math.Abs(dis1), new MCvScalar(255), 1);

            ImageBox2.Image = m1;

            //mm1异或,这里获得到刚才生成的圆形

            Mat m2 = new Mat();

            CvInvoke.BitwiseXor(m, m1, m2);

            VectorOfVectorOfPoint contours2 = new VectorOfVectorOfPoint();

            CvInvoke.FindContours(m2, contours2, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);

            //这里只有一个形状轮廓

            VectorOfPoint cirpoints = contours2[0];

            //保存形状上符合距离条件的点

            List<Point> shapes = new List<Point>();

            for(int i = 0;i< cirpoints.Size;i++)

            {

                Point cirp = new Point(cirpoints[i].X, cirpoints[i].Y);

                //不用计算距离,只需要获得是否在轮廓上

                Double dis = CvInvoke.PointPolygonTest(contours1[0], cirp, true);

                //小于2视为符合条件

                if (Math.Abs(dis) <= 1)

                    shapes.Add(cirp);

            }

            Mat m3 = new Mat();

            m3 = m.Clone();

            CvInvoke.CvtColor(m3, m3, ColorConversion.Gray2Bgr);

            //绘制点

            CvInvoke.Circle(m3, p1, 2, new MCvScalar(0, 0, 255), -1);

            //以点为圆心绘制圆,半径为最短距离

            CvInvoke.Circle(m3, p1, (int)Math.Abs(dis1), new MCvScalar(0, 255, 0), 1);

            //画出线段

            foreach (Point p in shapes)

                CvInvoke.Line(m3, p1, p, new MCvScalar(255, 0, 0), 1);

            ImageBox3.Image = m3;

        }

运行后如下图所示:

 

图6-15 轮廓与轮廓外一点最小距离的坐标点

通过运行结果,可以看出此方法获得的坐标点太多,误差比较大,为此在下面的代码中进行改进,采用平均值来获得坐标点。

【代码位置:frmChapter6】Button15_Click

        //获得点到轮廓的最短距离

        //对上面代码上的改进,增加了平均值

        private void Button15_Click(object sender, EventArgs e)

        {

            Mat m = new Mat(400, 400, DepthType.Cv8U, 1);

            m.SetTo(new MCvScalar(0));

            //CvInvoke.Circle(m, new Point(300, 300), 100, new MCvScalar(255), -1);

            CvInvoke.Rectangle(m, new Rectangle(50, 50, 100, 100), new MCvScalar(255), -1);

            ImageBox1.Image = m;

            Point p1 = new Point(240, 100);

            Mat m1 = new Mat();

            m1 = m.Clone();

            //求出点到形状的距离

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            CvInvoke.FindContours(m1, contours, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);

            //这里只有一个形状轮廓

            Double dis1 = CvInvoke.PointPolygonTest(contours[0], p1, true);

            CvInvoke.Circle(m1, p1, (int)Math.Abs(dis1), new MCvScalar(255), 1);

            ImageBox2.Image = m1;

            Mat m2 = new Mat();

            CvInvoke.BitwiseXor(m, m1, m2);

            //ImageBox3.Image = m2;

            VectorOfVectorOfPoint contours1 = new VectorOfVectorOfPoint();

            CvInvoke.FindContours(m2, contours1, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);

            //这里只有一个形状轮廓

            VectorOfPoint cirpoints = contours1[0];

            List<Point> shapes = new List<Point>();

            for (int i = 0; i < cirpoints.Size; i++)

            {

                Point cirp = new Point(cirpoints[i].X, cirpoints[i].Y);

                //不用计算距离,只需要获得是否在轮廓上

                Double dis = CvInvoke.PointPolygonTest(contours[0], cirp, true);

                //距离是否在设置范围,这里是3,多少都没所谓,后面要计算平均值

                if (Math.Abs(dis) <= 3)

                    shapes.Add(cirp);

            }

            Mat m3 = new Mat();

            m3 = m.Clone();

            CvInvoke.CvtColor(m3, m3, ColorConversion.Gray2Bgr);

            CvInvoke.Circle(m3, p1, (int)Math.Abs(dis1), new MCvScalar(0, 255, 0), 1);

            CvInvoke.Circle(m3, p1, 2, new MCvScalar(0, 0, 255), -1);

            Double totalx = 0;

            Double totaly = 0;

            Double avgx;

            Double avgy;

            //平均值计算点坐标

            for (int i = 0; i < shapes.Count; i++)

            {

                totalx += shapes[i].X;

                totaly += shapes[i].Y;

            }

            avgx = totalx / shapes.Count;

            avgy = totaly / shapes.Count;

            CvInvoke.Line(m3, p1, new Point((int)(avgx), (int)(avgy)), new MCvScalar(255, 0, 0), 1);

            ImageBox3.Image = m3;

        }

运行后如下图所示:

 

图6-16 轮廓与轮廓外一点最小距离的坐标点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值