opencv基础

目录

一、读取图片、视频、电脑相机 Image

读图:

读视频:

读本地电脑摄像头:

二、边缘检测 Basic Functions

​编辑

三、缩放与剪切 resize and crop

四、绘制形状及文本 draw shapes and text

​编辑

五、透视投影变换矫正 warp

​编辑

六、颜色检测 color detection

七、形状、轮廓检测  Shape/Contour Detection 

​编辑

八、人脸检测 face detection

九、虚拟画笔  Virtual Painter

十、 扫描文件 scan file

十一、车牌检测 plates detection

十二、 图片拼接


一、读取图片、视频、电脑相机 Image

读图:

#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace std;

void main() {
	//Image

	string path = "Resources/test.png";
	Mat img = imread(path); //读取图像路径赋值给Mat
	imshow("Image", img); //创建一个名为Image的窗口显示img
    //不断刷新图像 0:一直显示这一帧;不为0 时,表示显示完上一帧后delay 某 ms后再显示下一帧
	waitKey(0); 
    
}

读视频:

void main() {
	 Video

	string path = "Resources/test_video.mp4";
	VideoCapture cap(path);
	Mat img;
	while (true) {
		cap.read(img);
		imshow("Image", img);
		waitKey(1); //1 表示1ms后显示下一帧,设为20则视频速度变慢


	}
}

读本地电脑摄像头:

void main() {
	 WebCam
	
	VideoCapture cap(0);//获取本地摄像头
	Mat img;
	while (true) {     //循环读图并显示
		cap.read(img);
		imshow("Image", img);
		waitKey(1);


	}
}

二、边缘检测 Basic Functions

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace cv;
using namespace std;
/// <summary>
/// Basic Functions
/// </summary>
void main() {
	string path = "Resources/test.png";
	Mat img = imread(path);
	Mat imgGray,imgBlur, imgCanny, imgDilate, imgErode;
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	
	GaussianBlur(img, imgBlur, Size(3, 3), 3, 0); //在用Canny边缘检测前先模糊图片
	Canny(img, imgCanny, 25, 75);
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	
	dilate(imgCanny, imgDilate, kernel); //膨胀
	erode(imgDilate, imgErode, kernel);  //腐蚀

	imshow("image", img);
	imshow("imageGray", imgGray);
	imshow("imageBlur", imgBlur);
	imshow("imageCanny", imgCanny);
	imshow("imageDilate", imgDilate);
	imshow("imageErode", imgErode);
	waitKey(0);
}

三、缩放与剪切 resize and crop

#include <opencv2/opencv.hpp>
#include <iostream>;

using namespace cv;
using namespace std;
/// <summary>
/// resize and crop
/// </summary>
void main() {
	string path = "Resources/test.png";
	Mat img = imread(path);
	Mat imgResize,imgCrop;
	resize(img, imgResize, Size(), 0.5, 0.5); //0.5, 0.5按比例缩放
	Rect roi(100,100,400,400);//指定区域剪切
	imgCrop = img(roi);


	imshow("img", img);
	imshow("imgResize", imgResize);
	imshow("imgCrop", imgCrop);
	waitKey(0);
}

四、绘制形状及文本 draw shapes and text

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
/// <summary>
/// draw shapes and text
/// </summary>
void main() {
	//string path = "Resources/test.png";
	Mat img(512, 512, CV_8SC3, Scalar(255, 255, 255));  //Blank Image
	circle(img, Point(256, 256), 155, Scalar(0, 69, 255), FILLED);
	//circle(img, Point(256, 256), 155, Scalar(0, 69, 255), 10);
	rectangle(img, Point(130, 226), Point(137, 262), Scalar(255, 255, 255), 0);
	line(img, Point(101, 256), Point(401, 256), Scalar(255, 255, 255), 8);
	putText(img, "myText", Point(101, 252), FONT_HERSHEY_DUPLEX, 0.95, Scalar(255, 255, 255), 2);
	imshow("img", img);
	waitKey(0);

}

五、透视投影变换矫正 warp

关键在于像素点的选取、旋转矩阵的应用及warpPerspective()函数的使用

#include <opencv2/opencv.hpp>
#include <iostream>
 
using namespace cv;
using namespace std;
/// <summary>
/// warp 透视投影变换矫正
/// </summary>
float w = 250, h = 300;

Mat matrix, imgWarp, matrixJ,imgWarpJ;
void main() {
	string path = "Resources/cards.jpg";
	Mat img = imread(path);
	Point2f srcK[4] = { {528,143},{769,192}, {405,397}, {674,458} };
	Point2f srcJ[4] = { {778,108},{1016,84}, {844,361}, {1116,330} };
	Point2f dest[4] = { {0.0f,0.0f},{w,0.0f}, {0.0f,h}, {w,h} };
	//旋转矩阵
	matrix = getPerspectiveTransform(srcK, dest);
	warpPerspective(img, imgWarp, matrix, Point(w, h));
	matrixJ = getPerspectiveTransform(srcJ, dest);
	warpPerspective(img, imgWarpJ, matrixJ, Point(w, h));

	//确定四个点
	for (int i = 0; i < 4; i++) {
		circle(img, srcK[i], 10, Scalar(0, 69, 255), FILLED);
		circle(img, srcJ[i], 10, Scalar(0, 69, 255), FILLED);
	}
	imshow("img", img);
	imshow("imgWarp", imgWarp);
	imshow("imgWarpJ", imgWarpJ);
	waitKey(0);


}

六、颜色检测 color detection

关键在于hsv三类最值的选取以及inRange()函数的使用;

int hmin = 0, smin = 109, vmin = 154;
int hmax = 22, smax = 255, vmax = 255;

通过trackbar来调整阈值,观测目标颜色的选中情况,从而选定最值(注意,目标要以白色为准)。

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
/// <summary>
/// color dection
/// </summary>


int hmin = 0, smin = 109, vmin = 154;
int hmax = 22, smax = 255, vmax = 255;
void main() {
	string path = "Resources/lambo.png";
	Mat img = imread(path);
	Mat imgHSV,mask;
	//HSV 颜色空间  H(色调):0~180  S(饱和度):0~255  V(亮度):0~255
	cvtColor(img, imgHSV, COLOR_BGR2HSV);

	namedWindow("trackBar", (600, 400));
	createTrackbar("hmin", "trackBar", &hmin, 180); 
	createTrackbar("smin", "trackBar", &smin, 255);
	createTrackbar("vmin", "trackBar", &vmin, 255);
	createTrackbar("hmax", "trackBar", &hmax, 180);
	createTrackbar("smax", "trackBar", &smax, 255);
	createTrackbar("vmax", "trackBar", &vmax, 255);//创建轨迹条,
    //4个参数分别是 轨迹条名字,输出的窗口,一个指向整数的指针来表示当前的值,可到达的最大值
	//调整要点:使目标颜色保持白色

	while (true) {
		Scalar lower(hmin, smin, vmin);//HSV最低值
		Scalar upper(hmax, smax, vmax);//HSV最高值
		inRange(imgHSV, lower, upper, mask);//inRange将在阈值区间内的像素设置为白色,不在的设为黑色

		imshow("img", img);
		imshow("imgHSV", imgHSV);
		imshow("mask", mask);
		waitKey(1);

	}

	

}

七、形状、轮廓检测  Shape/Contour Detection 

#include <opencv2/opencv.hpp>
#include <iostream>
// Shape/Contour Detection //
using namespace cv;
using namespace std;
Mat imgGray, imgCanny, imgGauss, imgDilate, imgErode;


void getContours(Mat imgDilate, Mat img) {
	vector<vector<Point>> contours; //{{Point(20,30),Point(40,30)},{},{}}
	vector<Vec4i> hierarchy; //vector里放置了四个int类型的变量


	findContours(imgDilate, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);
	//存放轮廓点
	vector<vector<Point>> conPoly(contours.size());
	vector<Rect> boundRect(contours.size());
	for (int i = 0; i < contours.size(); i++) {
		int area = contourArea(contours[i]);
		cout << "area:" <<area << endl;
		if (area > 1000) {
			float peri = arcLength(contours[i], true); //弧长
            //把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合
			approxPolyDP(contours[i],    conPoly[i],         0.02 * peri,                               true);//把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合。
			//由图像的轮廓点组成的点集   输出的多边形点集    输出的精度:两个轮廓点之间最大距离数       输出的多边形是否封闭
			drawContours(img, conPoly, i, Scalar(255, 0, 255), 2);
			cout << "count points :"<<conPoly[i].size() << endl;

			//画矩形边框
			boundRect[i] = boundingRect(conPoly[i]);
			rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);

			//add text
			string ojbType;
			int objCor = conPoly[i].size();
			if (objCor == 3) {
				ojbType = "Tri";
			}
			else if (objCor == 4) {
				float aspRatio = (float)boundRect[i].width / boundRect[i].height;
				if (aspRatio > 0.95 && aspRatio < 1.05) {
					ojbType = "Square";
				}
				else {
					ojbType = "Rect";
				}
				
			}
			else if (objCor > 4) {
				ojbType = "Circle";
			}
			putText(img, ojbType,Point(boundRect[i].x, boundRect[i].y), FONT_HERSHEY_DUPLEX,0.75,2);

		}

	}

}
void main() {
	string path = "Resources/shapes.png";
	Mat img = imread(path);

	// Preprocessing

	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	GaussianBlur(imgGray, imgGauss,Size(3,3),3,0 );//在做边缘检测前一般要进行模糊处理
	Canny(imgGauss, imgCanny, 25, 75);
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(imgCanny, imgDilate, kernel);
	erode(imgDilate, imgErode, kernel);
	imshow("img", img);
	getContours(imgDilate, img);

	imshow("imgContour", img);
	/*imshow("imgGray", imgGray);
	imshow("imgGauss", imgGauss);
	imshow("imgCanny", imgCanny);
	imshow("imgDilate", imgDilate);*/
	waitKey(0);

}

八、人脸检测 face detection

key:CascadeClassifier faceCascade; //级联分类器

          detectMultiScale()函数使用

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

void main() {
	string path = "Resources/multiFaces.png";
	Mat img = imread(path);
	
	imshow("Image", img);
	
	CascadeClassifier faceCascade; //级联分类器
	faceCascade.load("Resources/haarcascade_frontalface_default.xml");//加载训练模型

	if (faceCascade.empty()) {
		cout << "XML file is empty"<<endl;	
	}
	vector<Rect> faces;
	faceCascade.detectMultiScale(img,   faces,   1.1,                 2); //
	                          //input   output   检测框的最小尺寸    检测的阈值,过小会出现误检现象,即把一些其他元素误判成人脸,过大可能会检测不到目标     
	for (int i = 0; i < faces.size(); i++) {
		rectangle(img, faces[i].tl(), faces[i].br(), Scalar(255, 0, 255), 3);
	}

	imshow("face detect", img);
	waitKey(0);
}

九、虚拟画笔  Virtual Painter

先选定颜色的阈值:ColorPicker;

基于第六部分的颜色选择器 colordetection制作该程序;

黑框中持续打印滑动条的选值,选出颜色笔后便可使用打印出的hsv最大最小值;

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
/// <summary>
/// color dection
/// </summary>

void main() {
	
	VideoCapture cap(0);

	Mat img;
	Mat imgHSV, mask;
	//HSV 颜色空间  H(色调):0~180  S(饱和度):0~255  V(亮度):0~255
	int hmin = 0, smin = 0, vmin = 0;
	int hmax = 180, smax = 255, vmax = 255;

	namedWindow("trackBar", (600, 400));
	createTrackbar("hmin", "trackBar", &hmin, 180);
	createTrackbar("smin", "trackBar", &smin, 255);
	createTrackbar("vmin", "trackBar", &vmin, 255);
	createTrackbar("hmax", "trackBar", &hmax, 180);
	createTrackbar("smax", "trackBar", &smax, 255);
	createTrackbar("vmax", "trackBar", &vmax, 255);//创建轨迹条,
	//4个参数分别是 轨迹条名字,输出的窗口,一个指向整数的指针来表示当前的值,可到达的最大值
	//调整要点:使目标颜色保持白色

	while (true) {
		cap.read(img);
		cvtColor(img, imgHSV, COLOR_BGR2HSV);
		Scalar lower(hmin, smin, vmin);//HSV最低值
		Scalar upper(hmax, smax, vmax);//HSV最高值
		inRange(imgHSV, lower, upper, mask);//inRange将在阈值区间内的像素设置为白色,不在的设为黑色
		cout << hmin << "," << smin << ","<<vmin << ","<<hmax << ","<<smax << ","<<vmax << endl;
		imshow("img", img);
		imshow("imgHSV", imgHSV);
		imshow("mask", mask);
		waitKey(1);

	}



}

将上一步的hsv值填入myColors二维vector中;

#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
VideoCapture cap(0);
Mat img;
vector<vector<int>> newPoints;

vector<vector<int>> myColors{ {111,113,75,136,255,255}, //blue
							  {151,56,87,180,255,168} }; //red
vector<Scalar> myColorValues{ {0,0,255} , //blue
	                            {255,0,0} };//red
Point getContours(Mat img) {
	vector<vector<Point>> contours; //{{Point(20,30),Point(40,30)},{},{}}
	vector<Vec4i> hierarchy; //vector里放置了四个int类型的变量


	findContours(img, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);
	//根据点之间连接直线画出轮廓
	vector<vector<Point>> conPoly(contours.size());
	vector<Rect> boundRect(contours.size());
	Point myPoint(0, 0);
	for (int i = 0; i < contours.size(); i++) {
		int area = contourArea(contours[i]);
		cout << "area:" << area << endl;
		
			float peri = arcLength(contours[i], true); //弧长
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);//把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合。
			//由图像的轮廓点组成的点集   输出的多边形点集    输出的精度:两个轮廓点之间最大距离数       输出的多边形是否封闭
			//drawContours(img, conPoly, i, Scalar(255, 0, 255), 2);
			cout << "count points :" << conPoly[i].size() << endl;

			//画矩形边框
			boundRect[i] = boundingRect(conPoly[i]);
            //找到画笔中间位置
			myPoint.x = boundRect[i].x + boundRect[i].width / 2;
			myPoint.y = boundRect[i].y;
			//rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);


		

	}
	return myPoint;

}

vector<vector<int>>  findColor(Mat img) {
	Mat imgHSV, mask;
	cvtColor(img, imgHSV, COLOR_BGR2HSV);
	for (int i = 0; i < myColorValues.size(); i++) {
		Scalar lower(myColors[i][0], myColors[i][1], myColors[i][2]);
		Scalar upper(myColors[i][3], myColors[i][4], myColors[i][5]);
		inRange(img, lower, upper, mask);
		
		Point myPoint = getContours(mask);
		//imshow(to_string(i), mask);
		if (myPoint.x != 0 && myPoint.y != 0) {
            //存储画笔画圆点的中心、半径及颜色
			newPoints.push_back({ myPoint.x,myPoint.y,i });
		}
	}
	return newPoints;

	
}
void drawOnCanvas(vector<vector<int>> newPoints, vector<Scalar> myColorValues) {
	for (int i = 0; i < newPoints.size(); i++) {
         //画相应的颜色点
		circle(img, Point(newPoints[i][0], newPoints[i][1]), 10, myColorValues[newPoints[i][2]], FILLED);
	}
}

void main() {
	
	
	while (true) {
		cap.read(img);
		newPoints = findColor(img);
		drawOnCanvas(newPoints, myColorValues);
		imshow("img", img);
		waitKey(1);
	}
 
}

十、 扫描文件 scan file

#include <opencv2/opencv.hpp>
#include <iostream>
// scan file //
using namespace cv;
using namespace std;
Mat imgOriginal, imgGray, imgGauss,imgCanny,imgDilate, imgThre, imgInitial,imgWarp, imgCrop;
vector<Point> biggest, initialPoints,docPoints;
float w = 420, h = 596;

Mat preProcessing(Mat img) {
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	GaussianBlur(imgGray, imgGauss, Size(3, 3), 3, 0);
	Canny(imgGauss, imgCanny, 25, 75);
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(imgCanny, imgDilate, kernel);
	return imgDilate;
}
vector<Point> getContours(Mat img) {
	vector<vector<Point>> contours;
	findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	vector<vector<Point>> conPoly(contours.size());
	float maxArea=0.0;
	for (int i = 0; i < contours.size(); i++) {
		int area = contourArea(contours[i]);
		if (area > 1000) {
			float peri = arcLength(contours[i], true);
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);
			if (area>maxArea   && conPoly[i].size() == 4) {  //筛选出最大的轮廓,这里是筛选出paper纸张
				maxArea = area;
				biggest = { conPoly[i][0],conPoly[i][1] ,conPoly[i][2] ,conPoly[i][3] };
			}
			drawContours(imgOriginal,conPoly,i,Scalar(255,0,255),2);
		}
	}
	
	return biggest;
}
void drawPoints(vector<Point> points,Scalar color) {
	for (int i = 0; i < points.size(); i++) {
		circle(imgOriginal, points[i], 10, color, FILLED);
		putText(imgOriginal, to_string(i), points[i], FONT_HERSHEY_PLAIN,5, color, 5);
	}
}
vector<Point> reorder(vector<Point > points) {
	vector<Point> newPoints;
	vector<int> sumPoints, subPoints;
	for (int i = 0; i < points.size(); i++) {
		sumPoints.push_back(points[i].x + points[i].y);
		subPoints.push_back(points[i].x - points[i].y);
	}
	cout << "sumPoints,subPoints"<<endl;
	for (int i = 0; i < sumPoints.size(); i++) {

		cout<<sumPoints[i]<<","<< subPoints[i]<<endl;
		
	}

	//	(x+y)min为左上;(x+y)max为右下;(x-y)max为右上;(x-y)min为左下
	newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 0
	cout << min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin() << endl;
	newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); //1
	cout << max_element(subPoints.begin(), subPoints.end()) - subPoints.begin() << endl;
	newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); //2
	cout << min_element(subPoints.begin(), subPoints.end()) - subPoints.begin() << endl;
	newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); //3
	cout << max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin() << endl;
	return newPoints;
}
Mat getWarp(Mat img,vector<Point> points,float w,float h) {
	Point2f src[4] = {points[0],points[1], points[2], points[3]};
	Point2f des[4] = { {0.0f,0.0f},{w,0.0f}, {0.0f,h}, {w,h} };
	Mat matrix = getPerspectiveTransform(src, des);
	warpPerspective(img, imgWarp, matrix, Point(w, h));
	return imgWarp;
}
void main() {
	string path = "Resources/paper.jpg";
	imgOriginal = imread(path);
	resize(imgOriginal, imgOriginal, Size(), 0.5, 0.5);

	//preProcessing
	imgThre = preProcessing(imgOriginal);
	//get contours -Biggest
	initialPoints = getContours(imgThre);
	//drawPoints(initialPoints, Scalar(0,0,255));
	docPoints = reorder(initialPoints);
	//drawPoints(docPoints, Scalar(0, 255, 0));
	//warp
	imgWarp = getWarp(imgOriginal,docPoints,w,h);
	//resize
	int cropVal = 5;
	Rect roi(cropVal, cropVal, w - (2 * cropVal), h - (2 * cropVal));
	imgCrop = imgWarp(roi);
	imshow("imgOriginal", imgOriginal);
	imshow("imgThre", imgThre);
	imshow("imgCrop", imgCrop);
	waitKey(0);

}

 其中:点排序前后如下

最终扫描图:

十一、车牌检测 plates detection

与第八部分人脸识别类似,加载训练模型后,使用detectMultiScale()函数获取检测结果;参数的使用影响检测结果。

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

void main() {
	Mat img = imread("Resources/car.png");
	resize(img, img, Size(), 0.5, 0.5);

	CascadeClassifier carClassifier;
	carClassifier.load("Resources/haarcascade_russian_plate_number.xml");
	if (carClassifier.empty()) cout << "XML file is empty" << endl;
	vector<Rect> plates;
	carClassifier.detectMultiScale(img, plates, 1.1, 9);
	for (int i = 0; i < plates.size(); i++) {
		rectangle(img, plates[i].tl(), plates[i].br(), Scalar(255, 0, 255), 3);
		Mat imgCrop = img(plates[i]);
		imshow("plate", imgCrop);
		
	}
	imshow("car plate detection", img);
	waitKey(0);
}

十二、 图片拼接 

分为水平和竖直两个方向进行拼接。

水平方向:

将一组图片水平排列进行拼接,所有图片的宽度之和作为拼接结果图的长,所有图片中最大的长作为结果图的宽。

void opImgH(const std::vector<cv::Mat> &vecMat, cv::Mat &destImg) {
  int h = 0;
  int w = 0;
  for (size_t i = 0; i < vecMat.size(); ++i) {
    // find max height
    w += vecMat[i].cols;
    if (vecMat[i].rows > h) {
      h = vecMat[i].rows;
    }
  }
  destImg = cv::Mat(h, w, CV_8UC3, cv::Scalar(90, 93, 82));
  w = 0;
  for (size_t i = 0; i < vecMat.size(); ++i) {
    vecMat[i].copyTo(destImg(cv::Rect(w, 0, vecMat[i].cols, vecMat[i].rows)));
    w += vecMat[i].cols;
  }
}

结果图如下:

竖直方向拼接:

void opImgV(const std::vector<cv::Mat> &vecMat, cv::Mat &destImg) {
  int h = 0;
  int w = 0;
  for (size_t i = 0; i < vecMat.size(); ++i) {
    h += vecMat[i].rows;
    if (vecMat[i].cols > w) {
      w = vecMat[i].cols;
    }
  }
  destImg = cv::Mat(h, w, CV_8UC3, cv::Scalar(90, 93, 82));
  h = 0;
  for (size_t i = 0; i < vecMat.size(); ++i) {
    vecMat[i].copyTo(destImg(cv::Rect(0, h, vecMat[i].cols, vecMat[i].rows)));
    h += vecMat[i].rows;
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值