MFC 获取摄像头 实时显示并图像处理(光斑自动对焦)

第一次写CSDN论文,给自己的毕设做个总结

对于我这个常年用C语言的渣渣,突然接触需要C++的MFC和Opencv,就变成毕设困难户了,好在有CSDN上大神们的帮助和同班同学热心的指导,总算是上手了mfc和Opencv。

废话不多说,直接进入正题。

PART 1  :   MFC界面的组件认识

在VS2013中新建好mfc项目,给这个项目取个动人的 名字“HuaQ_mfc”,然后在dialog中找到了对应的

:英文直译,按钮;

:英文直译,静态的字,也就是是说,这玩意加到上面去后不能再程序中就行修改;

:这个组件是用来放摄像头捕获的图像

:可编辑,在这里用来放所需显示的(清晰度 and 重心位置坐标);

还有好多的组件,因为用不到所以没有多做介绍。

PART 2 :  mfc组件的参数修改

对于button(按钮)

摄像头部分:        1.打开摄像头   2.关闭摄像头 3.图像处理自动对焦 

串口部分(作为上位机和下位机通讯):    1.打开串口  2.关闭串口 3. X轴移动 4. Y轴移动 5.Z轴移动 

分别右键所对应的button,单击属性,只需要修改两个参数即可(1.Caption-在窗口上显示的name 2.ID-每个组件的only_name)

dialog  -------  Caption:光斑自动对焦系统   ID:IDD_HUAQ_MFC_DIALOG

打开摄像头(button) ------- Caption:打开摄像头   ID:IDC_OPEN_CAMERA

关闭摄像头 (button)------- Caption:关闭摄像头   ID:IDC_CLOSE_CAMERA

打开串口(button) -------    Caption:打开串口   ID:IDC_OPEN_PORT

关闭串口(button) -------    Caption:关闭串口   ID:IDC_CLOSE_PORT

图像处理自动对焦(button) ---------Caption:图像处理自动对焦   ID:IDC_IMAGE_DEAL

X,Y,Z轴三个移动的button,因为是做自动对焦,手动用到的不多,并未对其实的控制进行编程。

Caption什么的,不装逼,用中文,I love China ,how about you ?

ID:中文直译

对于static Text  :我弄了两个 

first --- Caption :清晰度  (ID不需要改)            second ------ Caption : 重心坐标值   (ID不需要改)

在static Text后面放上 对应的 Edit Control,用来显示数值的变化。

对于Edit Control :

清晰度所对应 --------- Caption: 不用改    ID:IDC_Definition_Edit

重心坐标值对应  ------ Caption: 不用改    ID:IDC_Radius_Edit(这里并没有用坐标的翻译,因为最初自己做的时候是判断图像半径的大小变化,但是后面又引入了其他类型的图像,用最小二乘元拟合法做失败了,最终使用能够一统江山的算法——重心坐标)

PART 3 :   摄像头的捕获显示

所做的一切,都是在opencv环境配置好,且在X64上运行

添加头文件

#include "stdafx.h"
#include "opencv_mfc_2.h"
#include "opencv_mfc_2Dlg.h"
#include "afxdialogex.h"
#include "opencv2/opencv.hpp"
#include "CvvImage.h"

添加命名空间

using namespace std;  
using namespace cv;

添加全局变量

CvCapture* Capture;
CvCapture* m_Video;
CRect rect;
CDC *pDC;
HDC hDC;
CWnd *pwnd;

VideoCapture cap;
Mat frame;   //定义Mat变量,用来存储每一帧

以上做好后,找到之前的dialog,右键“打开摄像头”这个button

选择“添加事件处理程序”

然后按下编辑代码即可

相应的,对另外的button也这么做,一个Button,可以看成一个中断程序,按一下,进去了,代码跑一跑。

而我们要做的就是在这个button中,添加处理函数。

老铁,双击     “打开摄像头”这个button

进入这个button的代码区,添加如下代码:

 /*****************************************       打开摄像头            ********************************************************/
/*********************由于利用mfc显示摄像头需要用到CvvImage,所以我们需要首先将图片转到IplImage格式******************************/
/*****************************  再将图片转到CvvImage格式,然后将其显示到picture控件上   ********************************************************/

void Copencv_mfc_2Dlg::OnClickedOpenCamera()
{ 
	// TODO: Add your control notification handler code here  

	//Mat frame;   //定义Mat变量,用来存储每一帧

	cap.open(0);   //VideoCapture cap;(已经在前面做了全局变量)    cap.open(“1.avi”);       这是第一种种方法
	               //VideoCapture cap(“1.avi”);                                            这是第二种方法  
	
	
	//读取摄像头  ——  这就是第一个,未能成功运行的代码中的方法
	//CvCapture *capture = cvCreateCameraCapture(0);


	cap >> frame;                     //读取当前帧方法一      Function:   读取一帧
	               //cap.read(frame); //读取当前帧方法二



	CDC* pDC = GetDlgItem(IDC_CAMERA_SHOW)->GetDC();  //获取IDC_CAMERA_SHOW这个控件的设备环境,然后就可以对这个控件进行图形方面的操作了。
	HDC hDC = pDC->GetSafeHdc();   //获取显示控件的句柄 

	IplImage img = frame;
	CvvImage cimg;
	cimg.CopyOf(&img);            //复制该帧图像
	CRect rect;   //矩形类,用于记录一个矩形
	GetDlgItem(IDC_CAMERA_SHOW)->GetClientRect(&rect);   //GetClientRect是得到窗口句柄的用户坐标。 获取控件的坐标范围
	cimg.DrawToHDC(hDC, &rect);   //显示到设备的矩形框内
	ReleaseDC(pDC);

	SetTimer(1, 10, NULL);
}

这里也是需要重点说的,首先我们利用opencv的库函数打开摄像头并且获取到mat格式的图片,但是由于利用mfc显示摄像头需要用到CvvImage,所以我们需要首先将图片转到IplImage格式,再将图片转到CvvImage格式,然后将其显示到picture控件上。在控制台程序中,我们可以很简单的通过for(;;)的空循环来不停的实现获取摄像头的每一帧,但是我发现这么做在MFC里面是不可行的。一个是因为MFC是用户界面程序,如果这么写的话,所有的界面都会卡住,而且这么写的话其他的功能按钮就失去作用了。这里为了实现获取摄像头的每一帧,我们要通过设定一个时间事件,让每隔一定时间,比如20ms,就调用一个函数,通过这个时间调用来获取摄像头的帧。

添加onTimer函数    ,右键dialog

选择类向导


选中后 选择 “添加处理程序”

/**********************************************定时器部分******************************************************/

void Copencv_mfc_2Dlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: Add your message handler code here and/or call default  

	//  Mat frame;
	//cap.open(0);  

	cap >> frame;   //读取当前帧

	CDC* pDC = GetDlgItem(IDC_CAMERA_SHOW)->GetDC();     //根据ID获得窗口指针再获取与该窗口关联的上下文指针
	HDC hDC = pDC->GetSafeHdc();      // 获取设备上下文句柄 
	IplImage img = frame;
	CvvImage cimg;
	cimg.CopyOf(&img);    //复制该帧图像
	CRect rect;   // 矩形类
	GetDlgItem(IDC_CAMERA_SHOW)->GetClientRect(&rect);
	cimg.DrawToHDC(hDC, &rect);   //显示到设备的矩形框内
	ReleaseDC(pDC);

	/********************************图像清晰度的判断**********************************/

	Mat imageGrey;
	cvtColor(frame, imageGrey, CV_RGB2GRAY);
	Mat imageSobel;
	Sobel(imageGrey, imageSobel, CV_16U, 1, 1);

	//图像的平均灰度  
	double meanValue = 0.0;
	meanValue = mean(imageSobel)[0];

	//double to string  
	stringstream meanValueStream;
	string meanValueString;
	meanValueStream << meanValue;
	meanValueStream >> meanValueString;
	//meanValueString = "Articulation(Sobel Method): " + meanValueString;

	image_definition = meanValue;

	UpdateData(FALSE);


	CDialogEx::OnTimer(nIDEvent);  //这是,把当前函数没有被处理的消息id,用默认的处理函数来处理。类似与 switch里的最后哪个default
}

这段代码是从最初的版本上拷下来的,在定时器函数中同时进行图像的清晰度的判断。

其中会涉及变量的显示-----也就是将程序中的变量在对应的Edit Control中显示出,或者是将Edit Control的变量发送给代码区

右键对应的Edit Control 


选择 “添加变量”


类别改成“Value”,变量类型“it is up to you ”变量名自定义,单击 “完成”。

关闭摄像头,这里采取了比较巧妙的办法,我们只是将时间函数停掉,并且关闭摄像头就ok了

          //***************************************     关闭摄像头        ****************************************************//
void Copencv_mfc_2Dlg::OnClickedCloseCamera()
{
	// TODO: Add your control notification handler code here  

	KillTimer(1);            //这里关闭摄像头的方式  ,只是将时间函数停掉
	cap.release();
}

PART 4  :图像处理的算法

首先是预处理:图像中值滤波,亮度增强,对比度变换,形态学变换,阈值二值化.........


实现3种清晰度评价方法,分别是Tenengrad梯度方法、Laplacian梯度方法和方差方法

Tenengrad梯度方法

Tenengrad梯度方法利用Sobel算子分别计算水平和垂直方向的梯度,同一场景下梯度值越高,图像越清晰。以下是具体实现,这里衡量的指标是经过Sobel算子处理后的图像的平均灰度值,值越大,代表图像越清晰。


  1. #include <highgui/highgui.hpp>  
  2. #include <imgproc/imgproc.hpp>  
  3.   
  4. using namespace std;  
  5. using namespace cv;  
  6.   
  7. int main()  
  8. {  
  9.     Mat imageSource = imread("2.jpg");  
  10.     Mat imageGrey;  
  11.   
  12.     cvtColor(imageSource, imageGrey, CV_RGB2GRAY);  
  13.     Mat imageSobel;  
  14.     Sobel(imageGrey, imageSobel, CV_16U, 1, 1);  
  15.   
  16.     //图像的平均灰度  
  17.     double meanValue = 0.0;  
  18.     meanValue = mean(imageSobel)[0];  
  19.   
  20.     //double to string  
  21.     stringstream meanValueStream;  
  22.     string meanValueString;  
  23.     meanValueStream << meanValue;  
  24.     meanValueStream >> meanValueString;  
  25.     meanValueString = "Articulation(Sobel Method): " + meanValueString;  
  26.     putText(imageSource, meanValueString, Point(20, 50), CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 25), 2);  
  27.     imshow("Articulation", imageSource);  
  28.     waitKey();  
  29. }  

Laplacian梯度方法:

Laplacian梯度是另一种求图像梯度的方法,在上例的OpenCV代码中直接替换Sobel算子即可

  1. #include <highgui/highgui.hpp>  
  2. #include <imgproc/imgproc.hpp>  
  3.   
  4. using namespace std;  
  5. using namespace cv;  
  6.   
  7. int main()  
  8. {  
  9.     Mat imageSource = imread("1.jpg");  
  10.     Mat imageGrey;  
  11.   
  12.     cvtColor(imageSource, imageGrey, CV_RGB2GRAY);  
  13.     Mat imageSobel;  
  14.   
  15.     Laplacian(imageGrey, imageSobel, CV_16U);  
  16.     //Sobel(imageGrey, imageSobel, CV_16U, 1, 1);  
  17.   
  18.     //图像的平均灰度  
  19.     double meanValue = 0.0;  
  20.     meanValue = mean(imageSobel)[0];  
  21.   
  22.     //double to string  
  23.     stringstream meanValueStream;  
  24.     string meanValueString;  
  25.     meanValueStream << meanValue;  
  26.     meanValueStream >> meanValueString;  
  27.     meanValueString = "Articulation(Laplacian Method): " + meanValueString;  
  28.     putText(imageSource, meanValueString, Point(20, 50), CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 25), 2);  
  29.     imshow("Articulation", imageSource);  
  30.     waitKey();  
  31. }  

方差方法:

方差是概率论中用来考察一组离散数据和其期望(即数据的均值)之间的离散(偏离)成都的度量方法。方差较大,表示这一组数据之间的偏差就较大,组内的数据有的较大,有的较小,分布不均衡;方差较小,表示这一组数据之间的偏差较小,组内的数据之间分布平均,大小相近。


对焦清晰的图像相比对焦模糊的图像,它的数据之间的灰度差异应该更大,即它的方差应该较大,可以通过图像灰度数据的方差来衡量图像的清晰度,方差越大,表示清晰度越好

  1. #include <highgui/highgui.hpp>  
  2. #include <imgproc/imgproc.hpp>  
  3.   
  4. using namespace std;  
  5. using namespace cv;  
  6.   
  7. int main()  
  8. {  
  9.     Mat imageSource = imread("2.jpg");  
  10.     Mat imageGrey;  
  11.   
  12.     cvtColor(imageSource, imageGrey, CV_RGB2GRAY);  
  13.     Mat meanValueImage;  
  14.     Mat meanStdValueImage;  
  15.   
  16.     //求灰度图像的标准差  
  17.     meanStdDev(imageGrey, meanValueImage, meanStdValueImage);  
  18.     double meanValue = 0.0;  
  19.     meanValue = meanStdValueImage.at<double>(0, 0);  
  20.   
  21.     //double to string  
  22.     stringstream meanValueStream;  
  23.     string meanValueString;  
  24.     meanValueStream << meanValue*meanValue;  
  25.     meanValueStream >> meanValueString;  
  26.     meanValueString = "Articulation(Variance Method): " + meanValueString;  
  27.   
  28.     putText(imageSource, meanValueString, Point(20, 50), CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 25), 2);  
  29.     imshow("Articulation", imageSource);  
  30.     waitKey();  
  31. }  

自己在用以上三种算法进行清晰度评测时候发现,方差算法应该是最垃圾的,因为他受噪声的干扰大

对于清晰度的判断,算法很多,将所有算法进行试验然后统计结果,我竟然发现不同形状的光斑得用不同的算法。

然后就是重心坐标的计算
#include <iostream>  
#include <opencv2/core/core.hpp>  
#include <opencv/cv.hpp>  
#include <opencv2/highgui/highgui.hpp>  

using namespace cv;
using namespace std;
Mat src;
int thresh = 30;
Mat src_gray;
Mat src_zzlvbo;
Mat src_lightup;

int max_thresh = 255;

double alpha; /**< 控制对比度 */
int beta;  /**< 控制亮度 */

int main()
{
	src = imread("E:\\opencv_demo\\light_definition\\s+10.jpg", CV_LOAD_IMAGE_COLOR);    //注意路径得换成自己的  

	//---将灰度化后的图像进行中值滤波-------------//
	//medianBlur(src, src, 5);

	//Mat new_image = Mat::zeros(src.size(), src.type());

	//cout << " Basic Linear Transforms " << endl;
	//cout << "-------------------------" << endl;
	//cout << " *Enter the alpha value [1.0-3.0]: ";
	//cin >> alpha;
	//cout << " *Enter the beta value [0-100]: ";
	//cin >> beta;
	/ 执行变换 new_image(i,j) = alpha    * image(i,j) + beta
	//for (int y = 0; y < src.rows; y++)
	//{
	//	for (int x = 0; x < src.cols; x++)
	//	{
	//		for (int c = 0; c < 3; c++)
	//		{
	//			new_image.at<Vec3b>(y, x)[c] = saturate_cast<uchar>(alpha * (src.at<Vec3b>(y, x)[c]) + beta);
	//		}
	//	}
	//}
	/ 创建显示窗口
	//namedWindow("Original Image", 1);
	//namedWindow("New Image", 1);

	/ 显示图像
	//imshow("Original Image", src);
	//imshow("New Image", new_image);

	cvtColor(src, src_gray, CV_BGR2GRAY);//灰度化     

	//GaussianBlur(src, src, Size(3, 3), 0.1, 0, BORDER_DEFAULT);
	//blur(src_gray, src_gray, Size(3, 3)); //滤波      

	//将灰度化后的图像进行中值滤波
	medianBlur(src_gray, src_gray, 9);

	namedWindow("灰度图", WINDOW_AUTOSIZE);
	imshow("灰度图", src_gray);

	//二值化
	threshold(src_gray, src_gray, 140, 255, CV_THRESH_BINARY);

	namedWindow("二值化", CV_WINDOW_AUTOSIZE);
	imshow("二值化", src_gray);
	//moveWindow("image", 20, 20);   //功能是改变指定窗口的位置和大小

	//输入图像  
	//输出图像  
	//定义操作:MORPH_OPEN为开操作,MORPH_CLOSE为闭操作  
	//单元大小,这里是3*3的8位单元  
	//开闭操作位置  
	//开闭操作次数  
	//Mat dst;//形态学处理后的图像
	morphologyEx(src_gray, src_gray, MORPH_OPEN, Mat(3, 3, CV_8U), Point(-1, -1), 1);//形态学处理
	//namedWindow("形态学", CV_WINDOW_AUTOSIZE);
	//imshow("形态学", src_gray);
//	moveWindow("形态学", 20, 20);   //功能是改变指定窗口的位置和大小


	//定义Canny边缘检测图像       
	Mat canny_output;
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	//利用canny算法检测边缘       
	Canny(src_gray, canny_output, thresh, thresh * 3, 3);

	namedWindow("canny", CV_WINDOW_AUTOSIZE);
	imshow("canny", canny_output);
	moveWindow("canny", 550, 20);   //功能是改变指定窗口的位置和大小

	//查找轮廓    
	findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
	//计算轮廓矩       
	vector<Moments> mu(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mu[i] = moments(contours[i], false);
	}
	//计算轮廓的质心     
	vector<Point2f> mc(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mc[i] = Point2d(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
	}
	//画轮廓及其质心并显示      
	Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
	for (int i = 0; i < contours.size(); i++)
	{
		Scalar color = Scalar(255, 0, 0);
		drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
		circle(drawing, mc[i], 5, Scalar(0, 0, 255), -1, 8, 0);
		rectangle(drawing, boundingRect(contours.at(i)), cvScalar(0, 255, 0));
		char tam[100];
		sprintf(tam, "(%0.0f,%0.0f)", mc[i].x, mc[i].y);
		putText(drawing, tam, Point(mc[i].x, mc[i].y), FONT_HERSHEY_SIMPLEX, 0.4, cvScalar(255, 0, 255), 1);
	}
	namedWindow("Contours", CV_WINDOW_AUTOSIZE);
	imshow("Contours", drawing);
	moveWindow("Contours", 1100, 20);
	waitKey(0);
	src.release();
	src_gray.release();
	return 0;
}

对于光斑的变化,我还想到了用光斑的面积和轮廓长度的变化来做吗,不过效果很差,当然, 附上代码:


这段代码可以进行轮廓长度的计算,但是最后的结果是显示多段轮廓,需要进行轮廓筛选,然后相加

这段代码中包含了图像质心的计算,还有图像矩,轮廓绘制

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <Windows.h>
#include "cv.h"                             //  OpenCV 文件头
#include "highgui.h"
#include "cvaux.h"
#include "cxcore.h"

using namespace std;
using namespace cv;

//定义全局变量
Mat srcImage, grayImage;
int thresh = 200;   //边缘检测的阈值,改变此阈值对边缘检测的结果哟普很大的影响,最初设定值为100
const int threshMaxValue = 255;
RNG rng(12345);

//声明回调函数
void thresh_callback(int, void*);

int main()
{
	//将彩色图像读入
	srcImage = imread("E:\\opencv_demo\\light_definition\\1.jpg");

	//判断文件是否加载成功
	if (!srcImage.data)
	{
		cout << "图像加载失败...";
		return -1;
	}
	else
		cout << "图像加载成功..." << endl << endl;

	namedWindow("原图像", WINDOW_AUTOSIZE);
	imshow("原图像", srcImage);


	//图像转化为灰度图并平滑
	cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
	// blur(grayImage, grayImage, Size(3, 3));

	//将灰度化后的图像进行中值滤波
	medianBlur(grayImage, grayImage, 7);

	namedWindow("灰度图", WINDOW_AUTOSIZE);
	imshow("灰度图", grayImage);

	//二值化
	threshold(grayImage, grayImage, 120, 255, CV_THRESH_BINARY);

	//创建轨迹条
	createTrackbar("Thresh:", "二值图", &thresh, threshMaxValue, thresh_callback);
	thresh_callback(thresh, 0);
	waitKey(0);

	return 0;
}

void thresh_callback(int, void*)
{
	Mat canny_output;
	vector<vector<Point>>contours;
	vector<Vec4i>hierarchy;

	//canny边缘检测
	Canny(grayImage, canny_output, thresh, thresh * 2, 3);
	//轮廓提取
	findContours(canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	//计算图像矩
	vector<Moments>mu(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mu[i] = moments(contours[i], false);
	}

	//计算图像的质心
	vector<Point2f>mc(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
	}

	//绘制轮廓
	Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
	for (int i = 0; i < contours.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
		circle(drawing, mc[i], 4, color, -1, 8, 0);
	}

	namedWindow("轮廓图", WINDOW_AUTOSIZE);
	imshow("轮廓图", drawing);

	//用moments矩集计算轮廓面积并与opencv函数计算结果进行比较
	printf("\t Info: Area and Contour Length \n");
	for (int i = 0; i < contours.size(); i++)
	{
		printf("* Contour[%d] - Area(M_00)=%.2f-Area OpenCV:%.2f - Length:%.2f\n", i, mu[i].m00, contourArea(contours[i]), arcLength(contours[i], true));
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
		circle(drawing, mc[i], 4, color, -1, 8, 0);
	}

	double lengthSum = 0;
	double areaSum = 0;
	char lengthFlag = 0;

	printf("\t Info: the sum of Area and Length is :\n");

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

		if (arcLength(contours[i], true) > 120)
		{
			lengthSum = lengthSum + arcLength(contours[i], true);
			areaSum = areaSum + contourArea(contours[i]);

		}
	}

	printf("Area OpenCV Sum : %.2f - Length Sum : %.2f\n", areaSum, lengthSum);
}

PART 5 : 串口控制下位机

这部分应该是最好做的BUT,我的导师竟然告诉我,做两个exe,一个发指令到内存去,然后另外一个exe去调用内存区的指令,控制运动控制器。

WTC?  我的单片机又要吃灰了?


剩下的就是整合了,等全部弄好了,会把工程发到CSDN上,骗一波积分,嘻嘻!!!!!



  • 14
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值