opencv+hog训练和识别0——9数字成功的代码

102 篇文章 30 订阅
52 篇文章 2 订阅

1、资料:

只是训练数据集:

链接:https://pan.baidu.com/s/1Wvr54HDOHSd4Qs6g2syN4Q
提取码:2ozv

总工程文件:(包括训练工程、测试工程、训练数据集)

链接:https://pan.baidu.com/s/10JiJfJawtye8s9x1dGkvNQ
提取码:dhri

今天弄出了10个的数字识别

2、训练程序

/**********************************************************************
2018.5.23:SVM训练  //在  配置SVM训练器参数CvSVMParams SVM_params;这里有问题
2018.5.25:1、因为例程是opencv2的,opencv3里的svm的一些声明格式有了些许变化,修改了
           2、批量读取图片出现错误,原来是一个参数变量出现错误,说明警告也可能造成致命错误
2018.05.31: 之前的svm训练只是二分类,而且特征提取是简单的像素值特征,现在训0-9的数字,且hog特征
2018.6.13  重新训练,成功了,但是自己本身带的训练不行
	          
***************************************************************/

#include "opencv2/opencv.hpp"  
#include <opencv2/objdetect/objdetect.hpp>  //hog特征的c文件
#include <opencv/cv.h>  
#include <iostream> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <io.h> //查找文件相关函数
#include <stdio.h>  
#include <time.h>  //测试程序运行时间
#include "windows.h"  
#include "fstream" 

using namespace std;
using namespace cv;
using namespace cv::ml;




int main()
{

	检测窗口(128,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9  
	//HOGDescriptor hog(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);

	HOG检测器,用来计算HOG描述子的  
	//int DescriptorDim;//HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定  

	//Mat sampleFeatureMat;//所有训练样本的特征向量组成的矩阵,行数等于所有样本的个数,列数等于HOG描述子维数  
	//Mat sampleLabelMat;//训练样本的类别向量,行数等于所有样本的个数,列数等于1;1表示有人,-1表示无人  
	Mat classes;
	vector<string> img_path;//输入文件名变量     
	vector<int> img_catg;
	int nLine = 0;
	string buf;

	ifstream svm_data("E:/OCR_Recognition/opencv_project/SVM_train_data/hb.txt");//训练样本图片的路径都写在这个txt文件中,使用bat批处理文件可以得到这个txt文件       
	unsigned long n;
	while (svm_data)//将训练样本文件依次读取进来      
	{
		if (getline(svm_data, buf))
		{
			nLine++;
			if (nLine % 2 == 0)//注:奇数行是图片全路径,偶数行是标签   
			{
				//标签
				img_catg.push_back(atoi(buf.c_str()));//atoi将字符串转换成整型,标志(0,1,2,...,9),注意这里至少要有两个类别,否则会出错      
			}
			else
			{
				//图片
				img_path.push_back(buf);//图像路径      
			}
		}
	}
	svm_data.close();//关闭文件      

	Mat data_mat, labels_mat;
   int	nImgNum = nLine / 2; //nImgNum是样本数量,只有文本行数的一半,另一半是标签    
   cout << " 共有样本个数为: " << nImgNum << endl;
	//data_mat为所有训练样本的特征向量组成的矩阵,行数等于所有样本的个数,列数等于HOG描述子维数
	data_mat =Mat(nImgNum, 8100, CV_32FC1);  //行、列、类型;第二个参数,即矩阵的列是由下面的descriptors的大小决定的,可以由descriptors.size()得到,且对于不同大小的输入训练图片,这个值是不同的    

	//类型矩阵,存储每个样本的类型标志      
	//labels_mat为训练样本的类别向量,行数等于所有样本的个数,列数等于1;暂时,后面会修改,比如样本0,就为0,样本1就为1
	labels_mat = Mat(nImgNum, 1, CV_32SC1);
	
	
	Mat src;
	Mat trainImg = Mat(Size(128, 128), CV_8UC1);//需要分析的图片,这里默认设定图片是28*28大小,所以上面定义了324,如果要更改图片大小,可以先用debug查看一下descriptors是多少,然后设定好再运行      

	//处理HOG特征    
	for (string::size_type i = 0; i <img_path.size(); i++)
	{
		cout << " \n第 " << i << "  次循环\n" << endl;
		src = imread(img_path[i].c_str(),-1);
		if (src.empty())
		{
			cout << " can not load the image: " << img_path[i].c_str() << endl;
			continue;
		}

		cout << " 处理: " << img_path[i].c_str() << endl;

		resize(src, trainImg, trainImg.size());
		imshow("调整后的图片128*128", trainImg);
		waitKey(1);

	
		//检测窗口(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9  ,需要修改
		HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
		vector<float>descriptors;//存放结果    为HOG描述子向量    
		hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0)); //Hog特征计算,检测窗口移动步长(1,1)     

		cout << "HOG描述子向量维数    : " << descriptors.size() << endl;

	
		for (vector<float>::size_type j = 0; j < descriptors.size(); j++)
		{
			data_mat.at<float>(i, j) = descriptors[j];
		}



		labels_mat.at<int>(i, 0) = img_catg[i];
		cout << " 处理完毕: " << img_path[i].c_str() << " " << img_catg[i] << endl;
	}

	


		// 创建分类器并设置参数
		Ptr<SVM> SVM_params = SVM::create();
		SVM_params->setType(SVM::C_SVC);
		SVM_params->setKernel(SVM::LINEAR);  //注意必须使用线性SVM进行训练,因为HogDescriptor检测函数只支持线性检测!!!
		SVM_params->setDegree(10.0);
		SVM_params->setGamma(0.09);
		SVM_params->setCoef0(1.0);
		SVM_params->setC(10.0);
		SVM_params->setNu(0.5);
		SVM_params->setP(1.0);
		//SVM_params->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));

			//SVM_params->setKernel(SVM::LINEAR);  //核函数
			/*SVM_params->setDegree(0);
			SVM_params->setGamma(1);
			SVM_params->setCoef0(0);
			SVM_params->setC(1);
			SVM_params->setNu(0);
			SVM_params->setP(0);*/
			SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
	
				cout << "开始训练..." << endl;

		
		// 训练分类器
				SVM_params->train(data_mat, ROW_SAMPLE, labels_mat);
	
		//保存模型
		SVM_params->save("svm6_13.xml");
		cout << "训练好了!!!" << endl;


	/**********************************************************************************************************************/





	//检测样本      
	Mat test_img;
	char result[512];
	vector<string> img_tst_path;
	//
	ifstream img_tst("E:\\OCR_Recognition\\opencv_project\\SVM_train_data\\4\\num.txt");  //加载需要预测的图片集合,这个文本里存放的是图片全路径,不要标签  
	while (img_tst)
	{
		if (getline(img_tst, buf))
		{
			img_tst_path.push_back(buf);
		}
	}
	img_tst.close();

	ofstream predict_txt("SVM_PREDICT.txt");//把预测结果存储在这个文本中     
	for (string::size_type j = 0; j != img_tst_path.size(); j++)//依次遍历所有的待检测图片      
	{
		test_img = imread(img_tst_path[j].c_str(), -1);
		namedWindow("测试图片", 0);
		imshow("测试图片", test_img);
		waitKey(10);

		if (test_img.empty())
		{
			cout << " can not load the image: " << img_tst_path[j].c_str() << endl;
			continue;
		}

		Mat trainTempImg = Mat::zeros(Size(128, 128), CV_8UC1);
	
		resize(test_img, trainTempImg, trainTempImg.size());

		HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
		vector<float>descriptors;//结果数组         
		hog->compute(trainTempImg, descriptors, Size(1, 1), Size(0, 0));
		cout << "HOG描述子向量维数    " << descriptors.size() << endl;
		Mat SVMtrainMat = Mat(1, descriptors.size(), CV_32FC1);
	//	int n = 0;

		int	 number1 = descriptors.size();
		//将计算好的HOG描述子复制到样本特征矩阵data_mat  

		for (int i = 0; i < number1; i++)
		{

			data_mat.at<float>(0, i) = descriptors[i];//第i个样本的特征向量中的第n个元素  
		//	n++;
		}




		int ret = (int)SVM_params->predict(SVMtrainMat);//检测结果  
		cout << "识别的数字为:" << ret << endl;
		sprintf_s(result, "%s  %d\r\n", img_tst_path[j].c_str(), ret);
		predict_txt << result;  //输出检测结果到文本  
	}
	predict_txt.close();

	return 0;
}



//
//
//
//
//
//
//#include <stdio.h>  
//#include <time.h>  
//#include <opencv2/opencv.hpp>  
//#include <opencv/cv.h>  
//#include <iostream> 
//#include <opencv2/core/core.hpp>  
//#include <opencv2/highgui/highgui.hpp>  
//#include <opencv2/ml/ml.hpp>  
//#include <io.h> //查找文件相关函数
//
//using namespace std;
//using namespace cv;
//
//using namespace cv::ml;
//
//void getFiles(string path, vector<string>& files);
//void getBubble(Mat& trainingImages, vector<int>& trainingLabels);
//void getNoBubble(Mat& trainingImages, vector<int>& trainingLabels);
//
//int main()
//{
//	//获取训练数据
//	Mat classes;
//	Mat trainingData;
//	Mat trainingImages;
//
//
//
//	vector<int> trainingLabels;
//
//	//getBubble()与getNoBubble()将获取一张图片后会将图片(特征)写入
//	//	到容器中,紧接着会将标签写入另一个容器中,这样就保证了特征
//	//	和标签是一一对应的关系push_back(0)或者push_back(1)其实就是
//	//	我们贴标签的过程。
//	getBubble(trainingImages, trainingLabels);
//	getNoBubble(trainingImages, trainingLabels);
//
//	//在主函数中,将getBubble()与getNoBubble()写好的包含特征的矩阵拷贝给trainingData,将包含标签的vector容器进行类
//	//型转换后拷贝到trainingLabels里,至此,数据准备工作完成,trainingData与trainingLabels就是我们要训练的数据。
//	Mat(trainingImages).copyTo(trainingData);
//	trainingData.convertTo(trainingData, CV_32FC1);
//	Mat(trainingLabels).copyTo(classes);
//	//classes.convertTo(classes, CV_32SC1);
//
//	// 创建分类器并设置参数
//	Ptr<SVM> SVM_params = SVM::create();
//	SVM_params->setType(SVM::C_SVC);
//	SVM_params->setKernel(SVM::LINEAR);  //核函数
//
//	SVM_params->setDegree(0);
//	SVM_params->setGamma(1);
//	SVM_params->setCoef0(0);
//	SVM_params->setC(1);
//	SVM_params->setNu(0);
//	SVM_params->setP(0);
//	SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
//
//	
//	Ptr<TrainData> tData = TrainData::create(trainingData, ROW_SAMPLE, classes);
//
//	// 训练分类器
//	SVM_params->train(tData);
//
//	//保存模型
//	SVM_params->save("svm.xml");
//	cout << "训练好了!!!" << endl;
//	getchar();
//	return 0;
//}
//
//
//
//void getFiles(string path, vector<string>& files)
//{
//	intptr_t   hFile = 0;
//	struct _finddata_t fileinfo;
//	string p;
//	int i = 30;
//	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
//	{
//		
//		do
//		{
//			if ((fileinfo.attrib &  _A_SUBDIR))
//			{
//				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
//					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
//			}
//			else
//			{
//				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
//			}
//
//		}
//		while (_findnext(hFile, &fileinfo) == 0);
//	
//		_findclose(hFile);
//	}
//}
//
//
//
//
获取正样本
并贴标签为1
//void getBubble(Mat& trainingImages, vector<int>& trainingLabels)
//{
//	
//	
//	char * filePath = "E:\\SVM_train_data\\positive\\train"; //正样本路径
//	vector<string> files;
//	getFiles(filePath, files);
//	int number = files.size();
//	for (int i = 0; i < number; i++)
//	{
//		Mat  SrcImage = imread(files[i].c_str());
//		SrcImage = SrcImage.reshape(1, 1);
//		trainingImages.push_back(SrcImage);
//		trainingLabels.push_back(1);//该样本为数字5
//	}
//
//}
//
获取负样本
并贴标签为0
//void getNoBubble(Mat& trainingImages, vector<int>& trainingLabels)
//{
//	//char * filePath = "D:\\train\\no\\train"; //负样本路径
//	//char * filePath = "E:\\OCR_Recognition\\opencv_project\\SVM_train_data\\negative\\train"; //负样本路径
//	char * filePath = "E:\\SVM_train_data\\negative\\train"; //负样本路径
//	vector<string> files;
//	getFiles(filePath, files);
//	int number = files.size();
//	for (int i = 0; i < number; i++)
//	{
//		Mat  SrcImage = imread(files[i].c_str());
//		SrcImage = SrcImage.reshape(1, 1);
//		trainingImages.push_back(SrcImage);
//		trainingLabels.push_back(0); //该样本不是数字5
//	}
//}

#测试程序一:单个图片


#include <stdio.h>  
#include <time.h>  
#include <math.h>  
#include <opencv2/opencv.hpp>  
#include <opencv/cv.h>  
#include <iostream> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <io.h>

#include "windows.h"  
#include "fstream"  

using namespace std;
using namespace cv;
using namespace cv::ml;
int main()
{
	Ptr<ml::SVM>SVM_params = ml::SVM::load("svm6_13.xml");
	//Ptr<SVM> SVM_params = SVM::create();

	///SVM_params->load("svm6_1.xml");//加载训练好的xml文件,这里训练的是10K个手写数字  
	//检测样本      
	Mat test;
	char result[300]; //存放预测结果   
	test = imread("11.jpg", 1); //你自己随便在绘图板里写一个程序  
	if (test.empty())
	{
		MessageBox(NULL, TEXT("待预测图像不存在!"), TEXT("提示"), MB_ICONWARNING);
		return -1;
	}
	Mat trainTempImg = Mat::zeros(Size(128, 128), CV_8UC3);

	resize(test, trainTempImg, trainTempImg.size());
	HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
	vector<float>descriptors;//存放结果         
	hog->compute(trainTempImg, descriptors, Size(1, 1), Size(0, 0)); //Hog特征计算        
	cout << "HOG dims: " << descriptors.size() << endl;  //打印Hog特征维数  ,这里是324  
	Mat  SVMtrainMat = Mat(1, descriptors.size(), CV_32FC1);
	int n = 0;
	for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++)
	{
		SVMtrainMat.at<float>(0, n) = *iter;//第i个样本的特征向量中的第n个元素  
	
		n++;
	}


			SVMtrainMat.convertTo(SVMtrainMat, CV_32FC1);
			int response = (int)SVM_params->predict(SVMtrainMat);
			cout << "识别的数字为:" << response << endl;


//	int ret = SVM_params->predict(SVMtrainMat);//检测结果  
			sprintf_s(result, "%d\r\n", response);
	cvNamedWindow("dst", 1);
	imshow("dst", test);
	waitKey(30);
	//cout << "识别的数字为:" << result << endl;


	return 0;
}























//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//#include <stdio.h>  
//#include <time.h>  
//#include <math.h>  
//#include <opencv2/opencv.hpp>  
//#include <opencv/cv.h>  
//#include <iostream> 
//#include <opencv2/core/core.hpp>  
//#include <opencv2/highgui/highgui.hpp>  
//#include <opencv2/ml/ml.hpp>  
//#include <io.h>
//
//using namespace std;
//using namespace cv;
//
//void getFiles(string path, vector<string>& files);
//
//int main()
//{
//
//
//	int result = 0; //
//	char * filePath = "E:\\SVM_train_data\\positive\\test";
//	vector<string> files;
//	getFiles(filePath, files);
//	int number = files.size();
//	cout <<"共有测试图片 " <<number <<" 张\n"<< endl;
//
//	Ptr<ml::SVM>svm = ml::SVM::load("svm.xml");
//
//	for (int i = 0; i < number; i++)
//	{
//		clock_t start = clock();
//
//		Mat inMat = imread(files[i].c_str());
//		Mat p = inMat.reshape(1, 1);
//		p.convertTo(p, CV_32FC1);
//		int response = (int)svm->predict(p);
//		cout << "识别的数字为:" << response << endl;
//		
//		if (response > 1)
//		{
//			result++;
//		}
//
//		clock_t ends = clock();
//
//		cout << "Running Time : " << (double)(ends - start) / CLOCKS_PER_SEC << endl;
//
//	}
//	cout << result << endl;
//	
//	getchar();
//	return  0;
//}
//void getFiles(string path, vector<string>& files)
//{
//	intptr_t   hFile = 0;
//	struct _finddata_t fileinfo;
//	string p;
//	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
//	{
//		do
//		{
//			if ((fileinfo.attrib &  _A_SUBDIR))
//			{
//				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
//					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
//			}
//			else
//			{
//				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
//			}
//		} while (_findnext(hFile, &fileinfo) == 0);
//		_findclose(hFile);
//	}
//}
//
//
//
//
//
//
//
//


#测试程序二:多个图片













#include <stdio.h>  
#include <time.h>  
#include <math.h>  
#include <opencv2/opencv.hpp>  
#include <opencv/cv.h>  
#include <iostream> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <io.h>



#include "windows.h"  
#include "fstream"  

using namespace std;
using namespace cv;
using namespace cv::ml;

void getFiles(string path, vector<string>& files);

int main()
{
	Ptr<ml::SVM>svm = ml::SVM::load("svm6_13.xml");//加载训练好的xml文件,
	
	char result[300]; //存放预测结果   
	int aaa_0 = 0; 
	int aaa_1 = 0; 
	int aaa_2 = 0;
	int aaa_3 = 0; 
	int aaa_4 = 0; 
	int aaa_5 = 0; 
	int aaa_6 = 0; 
	int aaa_7 = 0; 
	int aaa_8 = 0;
	int aaa_9 = 0;

	char * filePath = "E:\\OCR_Recognition\\opencv_project\\SVM_test_data";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	cout << "共有测试图片 " << number << " 张\n" << endl;



	//检测样本      
	Mat test_img;
	//char result[300]; //存放预测结果   

	for (int i = 0; i < number; i++)
	{
		clock_t start = clock();
		Mat test_img = imread(files[i].c_str(), 1);
	
	/*	namedWindow("测试图片", 0);
		imshow("测试图片", test_img);
		waitKey(10);*/

		if (test_img.empty())
		{
			cout << " 待预测图像不存在: " << files[i].c_str() << endl;
			continue;
		}

		Mat trainTempImg = Mat::zeros(Size(128, 128), CV_8UC3);

		resize(test_img, trainTempImg, trainTempImg.size());

		HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
		vector<float>descriptors;//结果数组         
		hog->compute(trainTempImg, descriptors, Size(1, 1), Size(0, 0));
		cout << "HOG描述子向量维数    " << descriptors.size() << endl;
		Mat SVMtrainMat = Mat(1, descriptors.size(), CV_32FC1);
		int n = 0;


		//将计算好的HOG描述子复制到样本特征矩阵SVMtrainMat  
		for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++)
		{

			SVMtrainMat.at<float>(0, n) = *iter;//第i个样本的特征向量中的第n个元素  
			n++;
		}
		SVMtrainMat.convertTo(SVMtrainMat, CV_32FC1);
		int response = (int)svm->predict(SVMtrainMat);


         cout << "识别的数字为:" << response << endl;

      sprintf_s(result, "%d\r\n", response);
        cvNamedWindow("dst", 1);
		imshow("dst", test_img);
          waitKey(1);



	//	cout << "识别的数字为:" << response << endl;

		if (response == 0)
		{
			aaa_0++;
	     }
		if (response == 1)
		{
			aaa_1++;
		}
		if (response == 2)
		{
			aaa_2++;
		}
		if (response == 3)
		{
			aaa_3++;
		}
		if (response == 4)
		{
			aaa_4++;
		}
		if (response == 5)
		{
			aaa_5++;
		}
		if (response == 6)
		{
			aaa_6++;
		}
		if (response == 7)
		{
			aaa_7++;
		}
		if (response == 8)
		{
			aaa_8++;
		}
		if (response == 9)
		{
			aaa_9++;
		}




		clock_t ends = clock();

		cout << "Running Time : " << (double)(ends - start) / CLOCKS_PER_SEC << endl;

	}
	cout << "识别出0的个数为:" << aaa_0 << endl;
	cout << "识别出1的个数为:" << aaa_1 << endl;
	cout << "识别出2的个数为:" << aaa_2 << endl;
	cout << "识别出3的个数为:" << aaa_3 << endl;
	cout << "识别出4的个数为:" << aaa_4 << endl;	
	cout << "识别出5的个数为:" << aaa_5 << endl;
	cout << "识别出6的个数为:" << aaa_6 << endl;
	cout << "识别出7的个数为:" << aaa_7 << endl;
	cout << "识别出8的个数为:" << aaa_8 << endl;
	cout << "识别出9的个数为:" << aaa_9 << endl;

	getchar();
   return 0;
}



void getFiles(string path, vector<string>& files)
{
	intptr_t   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

翟羽嚄

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

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

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

打赏作者

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

抵扣说明:

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

余额充值