C++_2019-04-28_机器视觉——Opencv——基于轮廓匹配

本文深入探讨了使用OpenCV库进行图像轮廓匹配的方法,重点介绍了Hu轮廓匹配技术的应用。通过对比两张图片的形状相似度,实现图像识别和匹配,特别关注了轮廓检测、边缘提取、模板匹配等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*
Hu轮廓匹配:
#include "Opencv_MatchShape.h"
#include "Match_Shape_NCC.h"

int main(int argc, char* argv)
{

Opencv_MatchShape demo;
demo.MatchShape_HU();
system("pause");
return 0;
}

*/
#include <iostream>
#include <time.h>

#include <opencv/cxcore.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;




class Opencv_MatchShape
{
public:
	Opencv_MatchShape();
	~Opencv_MatchShape();

	// 
	void MatchShape_test1();// 轮廓寻找

	// 
	struct ptin
	{
		int DerivativeX;
		int DerivativeY;
		double Magnitude;
		double MagnitudeN;
	}ptin;



	void MatchShape_test2();

	void MatchShape_HU();/* 对比两张图的形状是 否相似越小越相近 */

private:

};

Opencv_MatchShape::Opencv_MatchShape()
{
}

Opencv_MatchShape::~Opencv_MatchShape()
{
}

void Opencv_MatchShape::MatchShape_test1()
{
	//***********************计算时间************************************************
	
	double total_time = 0;
	clock_t start_time = clock();
	clock_t stop_time = clock();
	total_time = double(stop_time - start_time) / CLOCKS_PER_SEC;
	
	//***********************************************************************

	char* param="./Resource/Search1.jpg";
	Mat searchImage = imread(param, 1);
	if (searchImage.empty())
	{
		return;
	}
	imshow("searchImage", searchImage);
	waitKey(0);

	//***********************Opencv自带的轮廓匹配__SearchImage灰度化****************************
	
	Mat graySearchImg = cvCreateMat(searchImage.rows, searchImage.cols, CV_8UC1);
	if (searchImage.channels() == 3)
		cvtColor(searchImage, graySearchImg, CV_RGB2GRAY);
	else
	{
		searchImage.copyTo(graySearchImg);
	}
	imshow("graySearchImg", graySearchImg);
	waitKey(0);

	//***********************Opencv自带的轮廓匹配__SearchImage二值化****************************
	/*
	//! type of the threshold operation
	enum { THRESH_BINARY=CV_THRESH_BINARY, THRESH_BINARY_INV=CV_THRESH_BINARY_INV,
		   THRESH_TRUNC=CV_THRESH_TRUNC, THRESH_TOZERO=CV_THRESH_TOZERO,
		   THRESH_TOZERO_INV=CV_THRESH_TOZERO_INV, THRESH_MASK=CV_THRESH_MASK,
		   THRESH_OTSU=CV_THRESH_OTSU };
	*/
	Mat dst_bw;
	threshold(graySearchImg, dst_bw, 20, 255, CV_THRESH_OTSU);	
	imshow("dst_bw", dst_bw);
	waitKey(0);

	//***********************Opencv自带的轮廓匹配__SearchImage边缘检测****************************
	Mat edge;
	Canny(dst_bw, edge, 125, 350);
	imshow("dst_edge", edge);
	waitKey(0);

	//***********************Opencv自带的轮廓匹配_SearchImage'Contours****************************
	/*
	https://docs.opencv.org/2.4/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.html
	*/
	/*
	## mode:(定义轮廓的检索模式)
	CV_RETR_EXTERNAL	只检测最外围轮廓,包含在外围轮廓内的内围轮廓将被忽略
	CV_RETR_LIST		检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立
	CV_RETR_CCOMP		检测所有的轮廓,但所有的轮廓只建立两个等级关系
	CV_RETR_TREE		检测所有轮廓,所有轮廓建立一个等级树结构

	## method:(定义轮廓的近似方法)
	CV_CHAIN_APPROX_NONE	存储所有的轮廓点
	CV_CHAIN_APPROX_SIMPLE	压缩水平垂直对角等
	CV_CHAIN_APPROX_TC89_L1	近似算法
	*/
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(edge, contours, CV_RETR_TREE, CV_CHAIN_APPROX_NONE); //找轮廓
	/// Draw contours
	RNG rng(12345);
	Mat drawing = Mat::zeros(edge.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());
	}
	/// Show in a window
	namedWindow("Contours", CV_WINDOW_AUTOSIZE);
	imshow("Contours", drawing);
	waitKey(0);

	//***********************Opencv自带的轮廓匹配_模板TemplatImage轮廓****************************
	char* param_template = "./Resource/Template.jpg";
	Mat TemplateImage = imread(param_template, 1);
	if (TemplateImage.empty())
	{
		return;
	}
	imshow("Template", TemplateImage);
	waitKey(0);

	//
	Mat grayTemplateImg = cvCreateMat(TemplateImage.rows, TemplateImage.cols, CV_8UC1);
	if (TemplateImage.channels() == 3)
		cvtColor(TemplateImage, grayTemplateImg, CV_RGB2GRAY);
	else
	{
		TemplateImage.copyTo(grayTemplateImg);
	}
	imshow("grayTemplateImg", grayTemplateImg);
	waitKey(0);

	//***********************Opencv自带的轮廓匹配_模板TemplatImage轮廓****************************
	Mat dst_template_bw;
	threshold(grayTemplateImg, dst_template_bw, 0, 255, CV_THRESH_OTSU);
	imshow("dst_template_bw", dst_template_bw);
	waitKey(0);

	/*
	## mode:(定义轮廓的检索模式)
		CV_RETR_EXTERNAL	只检测最外围轮廓,包含在外围轮廓内的内围轮廓将被忽略
		CV_RETR_LIST		检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立
		CV_RETR_CCOMP		检测所有的轮廓,但所有的轮廓只建立两个等级关系
		CV_RETR_TREE		检测所有轮廓,所有轮廓建立一个等级树结构
	
	## method:(定义轮廓的近似方法)
		CV_CHAIN_APPROX_NONE	存储所有的轮廓点
		CV_CHAIN_APPROX_SIMPLE	压缩水平垂直对角等
		CV_CHAIN_APPROX_TC89_L1	近似算法
	*/
	//***********************Opencv自带的轮廓匹配_模板TemplatImage边缘检测****************************
	Mat template_edge;
	Canny(dst_template_bw, template_edge, 125, 350);
	imshow("template_edge", template_edge);
	waitKey(0);
	//

	vector<vector<Point>> contours_template;
	vector<Vec4i> hierarchy_template;
	findContours(template_edge, contours_template, hierarchy_template, CV_RETR_TREE, CV_CHAIN_APPROX_NONE); //找轮廓

	/// Draw contours
	RNG rng_template(12345);
	Mat drawing_template = Mat::zeros(template_edge.size(), CV_8UC3);
	for (int i = 0; i< contours_template.size(); i++)
	{
		Scalar color = Scalar(rng_template.uniform(0, 255), rng_template.uniform(0, 255), rng_template.uniform(0, 255));
		drawContours(drawing_template, contours_template, i, color, 2, 8, hierarchy_template, 0, Point());

	}
	/// Show in a window
	namedWindow("Contours_Template", CV_WINDOW_AUTOSIZE);
	imshow("Contours_Template", drawing_template);
	waitKey(0);
	system("pause");

}

void Opencv_MatchShape::MatchShape_test2()
{
	char* param = "./Resource/Template.jpg";
	Mat src = imread(param, 1);

	if (src.empty())
	{
		return;
	}

	Mat gray;
	cvtColor(src, gray, CV_RGB2GRAY);
	if (gray.type() != CV_8UC1)
	{
		return;
	}
	imshow("gray", gray);
	waitKey(0);

	Mat bw;
	threshold(gray, bw, 20, 255, CV_THRESH_OTSU);
	imshow("bw", bw);
	waitKey(0);

	Mat edge;
	Canny(bw, edge, 125, 350);
	imshow("dst_edge", edge);
	waitKey(0);

	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(edge, contours, CV_RETR_TREE, CV_CHAIN_APPROX_NONE); //找轮廓

	Mat gx, gy;
	Sobel(gray, gx, CV_32F, 1, 0);
	Sobel(gray, gy, CV_32F, 0, 1);

	Mat magnitude, direction;
	cartToPolar(gx, gy, magnitude, direction);
	long contoursLength = 0;
	double magnitudeTemp = 0;
	int originx = contours[0][0].x;
	int originy = contours[0][0].y;

	typedef struct my
	{
		int DerivativeX;
		int DerivativeY;
		double Magnitude;
		double MagnitudeN;
	}ptin;
	// 提取dx\dy\mag\log信息	
	vector<vector<ptin>> contoursInfo;
	// 提取相对坐标位置
	vector<vector<Point>> contoursRelative;

	// 开始提取
	for (int i = 0; i < contours.size(); i++) {
		int n = contours[i].size();
		contoursLength += n;
		contoursInfo.push_back(vector<ptin>(n));
		vector<Point> points(n);
		for (int j = 0; j < n; j++) {
			int x = contours[i][j].x;
			int y = contours[i][j].y;
			points[j].x = x - originx;
			points[j].y = y - originy;
			ptin pointInfo;
			pointInfo.DerivativeX = gx.at<float>(y, x);
			pointInfo.DerivativeY = gy.at<float>(y, x);
			magnitudeTemp = magnitude.at<float>(y, x);
			pointInfo.Magnitude = magnitudeTemp;
			if (magnitudeTemp != 0)
				pointInfo.MagnitudeN = 1 / magnitudeTemp;
			contoursInfo[i][j] = pointInfo;
		}
		contoursRelative.push_back(points);
	}


	// 计算目标图像梯度
	char* param1 = "./Resource/Search2.jpg";
	Mat src_search = imread(param1, 1);
	Mat grayImage;
	if (src_search.empty())
	{
		cout << "空" << endl;
		return;
	}	
	if (src_search.channels() == 3)
	{
		cvtColor(src_search, grayImage, CV_RGB2GRAY);
	}
	else
	{
		src_search.copyTo(grayImage);
	}
	if (grayImage.type() != CV_8UC1)
	{
		cout << "!CV_8UC1" << endl;
		return;
	}
	Mat gradx, grady;
	Sobel(grayImage, gradx, CV_32F, 1, 0);
	Sobel(grayImage, grady, CV_32F, 0, 1);
	

	Mat mag, angle;
	cartToPolar(gradx, grady, mag, angle);


	// NCC模板匹配
	double minScore = 0.7;		//deafult value
	double greediness = 0.8;		//deafult value
	double nGreediness = 0.8;		//deafult value
	double nMinScore = 0.7;		//deafult value

	double partialScore = 0;
	double resultScore = 0;
	int resultX = 0;
	int resultY = 0;
	double start = (double)getTickCount();
	for (int row = 0; row < grayImage.rows; row++) 
	{
		for (int col = 0; col < grayImage.cols; col++) {
			double sum = 0;
			long num = 0;
			for (int m = 0; m < contoursRelative.size(); m++) {
				for (int n = 0; n < contoursRelative[m].size(); n++) {
					num += 1;
					int curX = col + contoursRelative[m][n].x;
					int curY = row + contoursRelative[m][n].y;
					if (curX < 0 || curY < 0 || curX > grayImage.cols - 1 || curY > grayImage.rows - 1) {
						continue;
					}

					// 目标边缘梯度
					double sdx = gradx.at<float>(curY, curX);
					double sdy = grady.at<float>(curY, curX);

					// 模板边缘梯度
					double tdx = contoursInfo[m][n].DerivativeX;
					double tdy = contoursInfo[m][n].DerivativeY;

					// 计算匹配
					if ((sdy != 0 || sdx != 0) && (tdx != 0 || tdy != 0))
					{
						double nMagnitude = mag.at<float>(curY, curX);
						if (nMagnitude != 0)
							sum += (sdx * tdx + sdy * tdy) * contoursInfo[m][n].MagnitudeN / nMagnitude;
					}

					// 任意节点score之和必须大于最小阈值
					partialScore = sum / num;
					if (partialScore < min((minScore - 1) + (nGreediness * num), nMinScore * num))
						break;
				}
			}

			// 保存匹配起始点
			if (partialScore > resultScore)
			{
				resultScore = partialScore;
				resultX = col;
				resultY = row;
			} 
		}
	}
	cout << resultY << endl;
	cout << resultX << endl;
	CvPoint point;
	point.x = resultX;
	point.y = resultY;
	circle(src_search, point, 10, Scalar(255, 255, 255), 8);
	imshow("src", src_search);
	waitKey(0);

}

/* 对比两张图的形状是 否相似越小越相近 */
void Opencv_MatchShape::MatchShape_HU()
{
	cv::Mat src_template, hsv_base;
	cv::Mat src_search, hsv_test1;

	src_template = imread("./Resource/a.jpg");//模板
	src_search = imread("./Resource/b.jpg");//待测图
	int thresh = 100;
	double ans = 0, result = 0;

	Mat imageresult1, imageresult2;

	if (src_template.type() == CV_8UC3)
	{
		cvtColor(src_template, imageresult1, CV_RGB2GRAY);
		cvtColor(src_search, imageresult2, CV_RGB2GRAY);
	}
	else
	{
		src_template.copyTo(imageresult1);
		src_search.copyTo(imageresult2);
	}

	blur(imageresult1, imageresult1, Size(3, 3));
	blur(imageresult2, imageresult2, Size(3, 3));

	threshold(imageresult1, hsv_base, 20, 255, CV_THRESH_OTSU);
	threshold(imageresult2, hsv_test1, 20, 255, CV_THRESH_OTSU);	

	Canny(hsv_base, imageresult1, thresh, thresh * 2);
	Canny(hsv_test1, imageresult2, thresh, thresh * 2);	

	std::vector<std::vector<cv::Point>>contours1, contours2;
	std::vector<Vec4i>hierarchy1, hierarchy2;
	CvSize Ssize;
	Ssize.height = src_template.rows;;
	Ssize.width = src_template.cols;
	Mat drawing1(Ssize, CV_8UC3, Scalar(0, 0, 0));
	findContours(imageresult1, contours1, hierarchy1, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
	RNG rng(12345);
	int T_w, T_h;
	for (int i = 0; i<contours1.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing1, contours1, i, color, 1, 8, hierarchy1, 0, cv::Point());

		Rect boundRect = boundingRect(contours1[i]);//检测外轮廓//对外轮廓加加矩形框  ——不带旋转		
		rectangle(drawing1, boundRect, Scalar(0, 255, 0), 3);

		RotatedRect box = minAreaRect(Mat(contours1[i]));
		Point2f rect[4];
		box.points(rect);  //把最小外接矩形四个端点复制给rect数组
		for (int j = 0; j<4; j++)
		{
			line(drawing1, rect[j], rect[(j + 1) % 4], Scalar(0, 255, 0), 2, 8);  //绘制最小外接矩形每条边
		}
		if (box.size.height>box.size.width)
		{
			T_w = box.size.height;
			T_h = box.size.width;
		}
		else
		{
			T_h = box.size.height;
			T_w = box.size.width;
		}
		cout << T_w << endl<< T_h << endl;

	}
	namedWindow("imageresult2", 0);
	imshow("imageresult1", drawing1);
	
	CvSize Ssize2;
	Ssize2.height = src_search.rows;;
	Ssize2.width = src_search.cols;
	Mat drawing2(Ssize2, CV_8UC3, Scalar(0, 0, 0));;
	findContours(imageresult2, contours2, hierarchy2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, cvPoint(0, 0));
	vector<Rect> boundRect(contours2.size());//包围矩形框
	vector<RotatedRect> box(contours2.size());
	for (int i = 0; i<contours2.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing2, contours2, i, color, 1, 8, hierarchy2, 0, cv::Point());
		
		boundRect[i] = boundingRect(contours2[i]);//检测外轮廓//对外轮廓加加矩形框  		
		rectangle(drawing2, boundRect[i], Scalar(0, 0, 255), 3);

		box[i] = minAreaRect(Mat(contours2[i]));
		Point2f rect[4];
		box[i].points(rect);  //把最小外接矩形四个端点复制给rect数组
		for (int j = 0; j<4; j++)
		{
			line(drawing2, rect[j], rect[(j + 1) % 4], Scalar(0, 255, 0), 2, 8);  //绘制最小外接矩形每条边
		}
	}	

	std::cout << "contours1.size()的值"<<contours1.size() << endl;
	int k = 0;
	
	for (int i = 0; i<contours1.size(); i++)
	{
		for (size_t j = 0; j < contours2.size(); j++)
		{			 
			ans = matchShapes(contours1[i], contours2[j], CV_CONTOURS_MATCH_I1, 0);
			k = j;
			std::cout << "第" << i << " 和 " << k << "的匹配值:" << ans << " " << endl;
		
			// string
			/*匹配值*/
			string label;
			stringstream stream;
			stream << (float)ans;
			/*角度*/
			//stream << ",";
			//stream << box[j].angle;			
			stream >> label;
			
			// 
			cout << box[j].size.width<< endl;
			cout << box[j].size.height << endl;
			float temp_size,w,h;
			if (box[j].size.width < box[j].size.height)
			{
				h = box[j].size.width;
				w = box[j].size.height;
			}
			else
			{
				w = box[j].size.width;
				h = box[j].size.height;
			}
			cout << w << endl;
			cout << h << endl;
			//筛选最接近尺寸的形状-长和宽的比较
			/*int T = 15;
			if (sqrt(abs((w - T_w)*(w - T_w) + (h - T_h)*(h - T_h))) < T)
			{
				putText(drawing2, label, Point(box[j].center.x, box[j].center.y), CV_FONT_BLACK, 1, Scalar(0, 255, 0), 2);
			}*/
			putText(drawing2, label, Point(box[j].center.x, box[j].center.y), CV_FONT_BLACK, 1, Scalar(0, 255, 0), 2);
		}			
	}
	namedWindow("imageresult2", 0);
	imshow("imageresult2", drawing2);
	waitKey(0);
}

 

<think>嗯,用户想了解Halcon模板匹配相比OpenCV等库的优势。这个问题很专业,用户应该是在做工业视觉方案选型,需要从实际应用角度比较两者的差异。 从对话历史看,用户已经掌握了Halcon模板匹配的基本原理和方法,现在更关注工程实践层面的对比。用户提供的四份参考文献很有价值,其中[2]和[3]直接对比了OpenCV和HALCON的特性,[4]则展示了实际代码转换案例。 需要重点突出工业场景下的差异点。Halcon的核心优势在于其算法深度优化,比如他们的形状匹配算法用了亚像素级边缘提取和多尺度金字塔搜索,而OpenCV的matchTemplate只是基础的像素比对。这在产线检测中至关重要——当零件有0.1mm的偏移时,Halcon能稳定检出而OpenCV可能漏检。 用户可能还关心开发效率问题。文献[2]提到Halcon的HDevelop交互环境能实时调整参数,比OpenCV需要反复编译调试高效得多。这个点应该纳入回复,特别是用户如果做快速原型开发时会深有体会。 注意到文献[4]的代码示例很有说服力。OpenCV实现旋转匹配需要自己写轮廓旋转和ROI处理,而Halcon的find_shape_model直接支持旋转角度搜索,代码量相差5倍以上。这个具体案例可以放在回复里。 最后要强调适用场景差异。虽然OpenCV在学术研究更灵活,但像汽车零件检测这种需要毫秒级响应的场景,Halcon的专用硬件加速(如GPU优化)才是王道。文献[3]指出Halcon对140种工业相机有原生驱动支持,这也是OpenCV不具备的工业属性。 回复结构应该按算法精度、开发效率、工业适配三个维度展开,每个维度都用对比数据支撑。用户明显是技术决策者,需要给出可量化的比较依据。</think>Halcon 模板匹配相较于 OpenCV 等其他机器视觉库的核心优势主要体现在**工业级精度优化、算法鲁棒性、开发效率及硬件适配性**四个方面,具体对比如下: --- ### 一、算法精度与鲁棒性对比 | **维度** | **Halcon** | **OpenCV** | |------------------|----------------------------------------|----------------------------------------| | **形状匹配** | 亚像素级边缘提取,支持非线性形变补偿[^1] | 基础边缘检测,形变处理需手动实现[^4] | | **光照适应性** | 内置灰度归一化算法,自动补偿光照波动[^3] | 需手动设计预处理流程(如直方图均衡化) | | **遮挡处理** | 基于局部特征点的部分匹配(支持>50%遮挡) | 遮挡场景下匹配率显著下降[^4] | | **旋转/缩放** | 原生支持$[0^\circ, 360^\circ]$全角度搜索 | 需旋转模板多轮匹配,耗时增加$3\times$[^4] | **示例场景**: 检测旋转$30^\circ$且光照不均的零件时,Halcon 的 `find_shape_model` 函数可直接输出亚像素坐标与角度,而 OpenCV 需组合 `rotate` + `matchTemplate` + 后处理才能实现,且精度损失约$0.5$像素[^4]。 --- ### 二、开发效率与工具链 | **能力** | Halcon 优势 | |------------------|----------------------------------------------------------------------------| | **交互式调试** | HDevelop IDE 实时显示特征点/边缘,参数调整即时生效[^1] | | **自动参数优化** | `determine_shape_model_params` 自动计算金字塔层级、角度步长等关键参数[^3] | | **代码集成** | 支持 C++/C#/Python 等语言,提供 .NET 控件直接嵌入工业 HMI[^3] | | **文档支持** | 每个算子提供工业案例(如 PCB 定位、齿轮检测)[^1] | > **典型开发对比**: > 实现多尺度模板匹配时,Halcon 仅需 5 行代码: > ```python > create_shape_model(Template, 'auto', 0, 360, 'auto', 'auto', 'use_polarity', []) > find_shape_model(Image, ModelID, 0, 360, 0.7, 0, 0.5, 'least_squares', 0, 0.9, Row, Column, Angle, Score) > ``` > 而 OpenCV 需自行实现尺度空间金字塔(约 50+ 行代码)[^4]。 --- ### 三、工业场景性能优化 | **特性** | Halcon 实现方案 | 工业价值 | |-------------------|------------------------------------------------|-------------------------------------| | **硬件加速** | 原生支持 GPU/多核并行,匹配速度提升$5\times$[^3] | 满足产线 $<100$ms 实时性要求 | | **相机兼容性** | 内置 140+ 工业相机驱动(如 Basler, Cognex)[^3] | 免去 SDK 二次开发成本 | | **算法稳定性** | 20+ 年工业数据训练的噪声模型 | 在油污/反光环境下误检率 $<0.1\%$ | --- ### 四、适用场景建议 - **优先选 Halcon**: 高精度工业检测(半导体定位、精密零件测量)、严苛环境(低光照、振动场景)、需要亚像素级输出的场景[^1][^3]。 - **可选 OpenCV**: 通用图像处理(如人脸识别)、学术研究、成本敏感型项目(开源免费)[^2][^4]。 > **典型案例**:汽车零件装配线上,Halcon 在 $0.5$秒内完成多角度螺柱定位(精度$±0.02$mm),而 OpenCV 方案因光照波动导致漏检率$>8\%$[^3]。 --- ### 总结 Halcon 在算法鲁棒性、开发效率、工业适配性上具有显著优势,尤其适合**高精度、强实时、环境复杂**的工业场景。OpenCV 更适用于**成本敏感、通用性强**的项目,但需投入更多开发资源实现同等性能[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

智能之心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值