关于opencv2.4.10-3.3.1左右版本的特征点剔除与显示问题

opencv3.3.1的剔除和显示(适用其他3以上版本)

ORB特征点检测+Latch描述子+汉明码暴力匹配+自己画线显示+左上右下方式

#include <iostream>

#include "opencv2/opencv_modules.hpp"
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/opencv.hpp>
#include <vector>

// If you find this code useful, please add a reference to the following paper in your work:
// Gil Levi and Tal Hassner, "LATCH: Learned Arrangements of Three Patch Codes", arXiv preprint arXiv:1501.03719, 15 Jan. 2015

using namespace std;
using namespace cv;

const float inlier_threshold = 2.5f; // Distance threshold to identify inliers
const float nn_match_ratio = 0.8f;   // Nearest neighbor matching ratio

int main(int argc, char* argv[])
{

	Mat img1 = imread("img1.ppm", IMREAD_GRAYSCALE);
	Mat img2 = imread("img2.ppm", IMREAD_GRAYSCALE);

	vector<KeyPoint> kpts1, kpts2;
	Mat desc1, desc2;

	Ptr<cv::ORB> orb_detector = cv::ORB::create(10000);

	Ptr<xfeatures2d::LATCH> latch = xfeatures2d::LATCH::create();


	orb_detector->detect(img1, kpts1);
	latch->compute(img1, kpts1, desc1);

	orb_detector->detect(img2, kpts2);
	latch->compute(img2, kpts2, desc2);

	//直接找1近邻,SIFT找匹配点
	//FlannBasedMatcher matcher;
	BFMatcher matcher(NORM_HAMMING);
	vector< DMatch > matches1;
	matcher.match(desc1, desc2, matches1);

	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;

	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((kpts1[matches1[i].queryIdx]).pt);
		objectCoord.push_back((kpts2[matches1[i].trainIdx]).pt);
	}

	// 计算homography矩阵
	Mat mask;
	vector<Point2f> queryInliers;
	//queryInliers[0].
	vector<Point2f> sceneInliers;
	Mat H = findFundamentalMat(queryCoord, objectCoord, mask, CV_FM_RANSAC);
	//Mat H = findHomography( queryCoord, objectCoord, CV_RANSAC, 10, mask);
	int inliers_cnt = 0, outliers_cnt = 0;
	for (int j = 0; j < mask.rows; j++){
		if (mask.at<uchar>(j) == 1){
			queryInliers.push_back(queryCoord[j]);
			sceneInliers.push_back(objectCoord[j]);
			inliers_cnt++;
		}
		else {
			outliers_cnt++;
		}
	}

	Mat srcColorImage = imread("img1.ppm");
	Mat dstColorImage = imread("img2.ppm");
	vector<Point2f> srcPoints = queryInliers;
	vector<Point2f>	dstPoints = sceneInliers;
	// Create a image for displaying mathing keypoints
	Size sz = Size(srcColorImage.size().width + dstColorImage.size().width, 
							srcColorImage.size().height + dstColorImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// Draw camera frame
	Mat roi1 = Mat(matchingImage, Rect(0, 0, srcColorImage.size().width, srcColorImage.size().height));
	srcColorImage.copyTo(roi1);
	// Draw original image
	Mat roi2 = Mat(matchingImage, Rect(srcColorImage.size().width, srcColorImage.size().height, 
							dstColorImage.size().width, dstColorImage.size().height));
	dstColorImage.copyTo(roi2);

	// Draw line between nearest neighbor pairs
	for (int i = 0; i < (int)srcPoints.size(); ++i) {
		Point2f pt1 = srcPoints[i];
		Point2f pt2 = dstPoints[i];
		Point2f from = pt1;
		Point2f to = Point(srcColorImage.size().width + pt2.x, srcColorImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	// Display mathing image
	resize(matchingImage, matchingImage, Size(matchingImage.cols / 2, matchingImage.rows / 2));
	namedWindow("111", CV_WINDOW_AUTOSIZE);
	imshow("111", matchingImage);
	waitKey();
	return 0;
}


SIFT特征点检测+Latch描述子+汉明码暴力匹配+Lowe的ratio=0.8剔除+预先载入单应性矩阵H剔除+系统画线+左右展示(好像自带的只能左右展示)

直接放入灰度图和彩色图放入在算法里自己转换灰度图,结果好像不太一样,看来opencv自带的灰度化方式可能有不同,灰度化的质量影响特征点检测的质量

#include <iostream>

#include "opencv2/opencv_modules.hpp"
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <vector>

// If you find this code useful, please add a reference to the following paper in your work:
// Gil Levi and Tal Hassner, "LATCH: Learned Arrangements of Three Patch Codes", arXiv preprint arXiv:1501.03719, 15 Jan. 2015

using namespace std;
using namespace cv;

const float inlier_threshold = 2.5f; // Distance threshold to identify inliers
const float nn_match_ratio = 0.8f;   // Nearest neighbor matching ratio

int main(int argc, char* argv[])
{
	/*
	CommandLineParser parser(argc, argv,
		"{@img1 | ../data/graf1.png | input image 1}"
		"{@img2 | ../data/graf3.png | input image 2}"
		"{@homography | ../data/H1to3p.xml | homography matrix}");
	*/
	Mat img1 = imread("img1.ppm");
	Mat img2 = imread("img2.ppm");
	//Mat img2 = imread("img5.ppm", IMREAD_GRAYSCALE);
	//Mat img2 = imread("img6.ppm", IMREAD_GRAYSCALE);


	/*
	8.7976964e-01   3.1245438e-01  -3.9430589e+01
  -1.8389418e-01   9.3847198e-01   1.5315784e+02
   1.9641425e-04  -1.6015275e-05   1.0000000e+00
	*/
	/*
	6.2544644e-01   5.7759174e-02   2.2201217e+02
		2.2240536e-01   1.1652147e+00 - 2.5605611e+01
		4.9212545e-04 - 3.6542424e-05   1.0000000e+00
		* /
	/*
	4.2714590e-01  -6.7181765e-01   4.5361534e+02
   4.4106579e-01   1.0133230e+00  -4.6534569e+01
   5.1887712e-04  -7.8853731e-05   1.0000000e+00
	*/
	//Mat homography;
	//FileStorage fs(parser.get<String>("@homography"), FileStorage::READ);
	//fs.getFirstTopLevelNode() >> homography;
	Mat homography1to2 = (Mat_<double>(3, 3) << 8.7976964e-01, 3.1245438e-01, -3.9430589e+01, 
		-1.8389418e-01, 9.3847198e-01, 1.5315784e+02,
		1.9641425e-04, -1.6015275e-05, 1.0000000e+00);
	Mat homography1to5 = (Mat_<double>(3, 3) << 6.2544644e-01, 5.7759174e-02, 2.2201217e+02,
		2.2240536e-01, 1.1652147e+00, -2.5605611e+01,
		4.9212545e-04, -3.6542424e-05, 1.0000000e+00);
	Mat homography1to6 = (Mat_<double>(3, 3) << 4.2714590e-01, -6.7181765e-01, 4.5361534e+02,
		4.4106579e-01, 1.0133230e+00, -4.6534569e+01,
		5.1887712e-04, -7.8853731e-05, 1.0000000e+00);

	Mat homography = homography1to2;
	//Mat homography = homography1to5;
	//Mat homography = homography1to6;

	vector<KeyPoint> kpts1, kpts2;
	Mat desc1, desc2;

	//Ptr<cv::ORB> orb_detector = cv::ORB::create(10000);
	Ptr<cv::xfeatures2d::SIFT> orb_detector = cv::xfeatures2d::SIFT::create(10000);

	Ptr<xfeatures2d::LATCH> latch = xfeatures2d::LATCH::create();


	orb_detector->detect(img1, kpts1);
	latch->compute(img1, kpts1, desc1);

	orb_detector->detect(img2, kpts2);
	latch->compute(img2, kpts2, desc2);

	BFMatcher matcher(NORM_HAMMING);
	vector< vector<DMatch> > nn_matches;
	matcher.knnMatch(desc1, desc2, nn_matches, 2);

	vector<KeyPoint> matched1, matched2, inliers1, inliers2;
	vector<DMatch> good_matches;
	for (size_t i = 0; i < nn_matches.size(); i++) {
		DMatch first = nn_matches[i][0];
		float dist1 = nn_matches[i][0].distance;
		float dist2 = nn_matches[i][1].distance;

		if (dist1 < nn_match_ratio * dist2) {
			matched1.push_back(kpts1[first.queryIdx]);
			matched2.push_back(kpts2[first.trainIdx]);
		}
	}

	for (unsigned i = 0; i < matched1.size(); i++) {
		Mat col = Mat::ones(3, 1, CV_64F);
		col.at<double>(0) = matched1[i].pt.x;
		col.at<double>(1) = matched1[i].pt.y;

		col = homography * col;
		col /= col.at<double>(2);
		double dist = sqrt(pow(col.at<double>(0) - matched2[i].pt.x, 2) +
			pow(col.at<double>(1) - matched2[i].pt.y, 2));

		if (dist < inlier_threshold) {
			int new_i = static_cast<int>(inliers1.size());
			inliers1.push_back(matched1[i]);
			inliers2.push_back(matched2[i]);
			good_matches.push_back(DMatch(new_i, new_i, 0));
		}
	}

	Mat res;

	/*drawMatches其中参数如下:
	* img1 – 源图像1
	* keypoints1 –源图像1的特征点.
	* img2 – 源图像2.
	* keypoints2 – 源图像2的特征点
	* matches1to2 – 源图像1的特征点匹配源图像2的特征点[matches[i]] .
	* outImg – 输出图像具体由flags决定.
	* matchColor – 匹配的颜色(特征点和连线),若matchColor==Scalar::all(-1),颜色随机.
	* singlePointColor – 单个点的颜色,即未配对的特征点,若matchColor==Scalar::all(-1),颜色随机.
	  matchesMask – Mask决定哪些点将被画出,若为空,则画出所有匹配点.
	* flags – Fdefined by DrawMatchesFlags.
	*/

	/*flags其中参数如下:
	struct CV_EXPORTS DrawMatchesFlags
	{
	enum{ DEFAULT = 0, //!< Output image matrix will be created (Mat::create),
		//!< i.e. existing memory of output image may be reused.
		//!< Two source image, matches and single keypoints will be drawn.
		//!< For each keypoint only the center point will be drawn (without
		//!< the circle around keypoint with keypoint size and orientation).
		DRAW_OVER_OUTIMG = 1, //!< Output image matrix will not be created (Mat::create).
		//!< Matches will be drawn on existing content of output image.
		NOT_DRAW_SINGLE_POINTS = 2, //!< Single keypoints will not be drawn.
		DRAW_RICH_KEYPOINTS = 4 //!< For each keypoint the circle around keypoint with keypoint size and
		//!< orientation will be drawn.
		};
	};
	*/
	drawMatches(img1, inliers1, img2, inliers2, good_matches, res, 
                    Scalar(0, 255, 255), Scalar(255, 0, 0), vector<char>(), 0);
	imwrite("latch_result.png", res);


	double inlier_ratio = inliers1.size() * 1.0 / matched1.size();
	cout << "LATCH Matching Results" << endl;
	cout << "*******************************" << endl;
	cout << "# Keypoints 1:                        \t" << kpts1.size() << endl;
	cout << "# Keypoints 2:                        \t" << kpts2.size() << endl;
	cout << "# Matches:                            \t" << matched1.size() << endl;
	cout << "# Inliers:                            \t" << inliers1.size() << endl;
	cout << "# Inliers Ratio:                      \t" << inlier_ratio << endl;
	cout << endl;

	namedWindow("result", 2);
	imshow("result", res);
	waitKey();

	return 0;
}


SIFT特征点检测+SIFT描述子+最近邻匹配+不剔除+计算代码运行时间(精确到小数点)

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img6.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryCoord, objectCoord);
	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;

	namedWindow("matchingImage", CV_WINDOW_AUTOSIZE);
	imshow("matchingImage", result);
	waitKey();
	return 0;
}

SIFT特征点检测+SIFT描述子+最近邻匹配+

劳氏算法剔除(最近邻与次紧邻的比值<ratio=0.6)

+计算代码运行时间(精确到小数点)

+内点比率(正确点比率)

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img6.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	// <5>根据劳氏算法(Lowe's algorithm),得到优秀的匹配点
	vector< vector<DMatch> > nn_matches; // 匹配最近的两个特征点
	// 通过最近邻与次紧邻的比值<nn_match_ratio 视为优秀匹配
	matcher.knnMatch(qDescriptor, objDesriptor, nn_matches, 2);
	const float nn_match_ratio = 0.8f;   // Nearest neighbor matching ratio
	vector<KeyPoint> matched1, matched2;
	vector<DMatch> good_matches;
	for (size_t i = 0; i < nn_matches.size(); i++) {
		DMatch first = nn_matches[i][0];
		float dist1 = nn_matches[i][0].distance;
		float dist2 = nn_matches[i][1].distance;

		if (dist1 < nn_match_ratio * dist2) {
			good_matches.push_back(nn_matches[i][0]);
			matched1.push_back(qKeypoints[first.queryIdx]);
			matched2.push_back(objKeypoints[first.trainIdx]);
		}
	}

	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryCoord, objectCoord);
	cout << "  " << matches1.size() << endl;

	//点转换
	vector<cv::Point2f> queryCoord1;
	vector<cv::Point2f> objectCoord1;
	for (int i = 0; i < good_matches.size(); i++){
		queryCoord1.push_back((qKeypoints[good_matches[i].queryIdx]).pt);
		objectCoord1.push_back((objKeypoints[good_matches[i].trainIdx]).pt);
	}

	Mat result1 = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		good_matches, queryCoord1, objectCoord1);
	cout << "  " << good_matches.size() << endl;

	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;

	double inlier_ratio = (double)good_matches.size() / matches1.size();
	cout << "匹配率  " << inlier_ratio << endl;

	//namedWindow("matchingImage", CV_WINDOW_AUTOSIZE);
	imshow("matchingImage", result);
	imshow("matchingImage1", result1);
	waitKey();
	return 0;
}

SIFT特征点检测+SIFT描述子+最近邻匹配+单应映射关系剔除(H矩阵)

这里没有写的是opencv带的,没有按论文走,但是性质一样

两种实现方式:

1。使用mask剔除

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img6.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	// 计算homography矩阵
	Mat mask;
	Mat H = findHomography(queryCoord, objectCoord, CV_RANSAC, 10, mask);

// double minVal,maxVal; cv::minMaxIdx(ipts,&minVal,&maxVal);
// cv::Mat H = cv::findHomography(queryCoord,objectCoord,mask,CV_RANSAC, 0.004 * maxVal);
// threshold from Snavely07
// N. Snavely, S. Seitz, R. Szeliski, “Modeling the World from Internet Photo Collections, ”Int’l J.of Computer Vision, Vol. 80, No. 2, pp.189–210, 2008.

	vector<Point2f> queryInliers;
	vector<Point2f> sceneInliers;
	int inliers_cnt = 0, outliers_cnt = 0;
	for (int j = 0; j < mask.rows; j++){
		if (mask.at<uchar>(j) == 1){
			queryInliers.push_back(queryCoord[j]);
			sceneInliers.push_back(objectCoord[j]);
			inliers_cnt++;
		}
		else {
			outliers_cnt++;
		}
	}


	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryInliers, sceneInliers);
	cout << "最近邻匹配特征点数  " << matches1.size() << endl;
	cout << "内点数目  " << inliers_cnt << endl;
	cout << "外点数目  " << outliers_cnt << endl;

	

	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;


	//double inlier_ratio = (double)good_matches.size() / matches1.size();
	//cout << "匹配率  " << inlier_ratio << endl;
	imshow("matchingImage", result);
	imwrite("B1.jpg", result);
	waitKey();
	return 0;
}

2。使用[x y 1] * H 的方式剔除

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img6.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	// 计算homography矩阵
	vector<cv::Point2f> queryInliers;
	vector<cv::Point2f> sceneInliers;
	const float inlier_threshold = 2.8f; // Distance threshold to identify inliers
	Mat mask;
	Mat H = findHomography(queryCoord, objectCoord, CV_RANSAC, 10, mask);
	Mat H_inv = H.inv();
	int inliers_cnt = 0, outliers_cnt = 0;

	for (unsigned i = 0; i < queryCoord.size(); i++) {
		Mat col = Mat::ones(3, 1, CV_64F);
		col.at<double>(0) = queryCoord[i].x;
		col.at<double>(1) = queryCoord[i].y;

		col = H * col;
		col /= col.at<double>(2); // 将[x*, y*, #] 转换为 [x*/#, y*/#, 1]
		double dist = sqrt(pow(col.at<double>(0) - objectCoord[i].x, 2) +
			pow(col.at<double>(1) - objectCoord[i].y, 2)); // 计算误差-欧式距离

		if (dist < inlier_threshold) { // 误差小于阈值
			queryInliers.push_back(queryCoord[i]);
			sceneInliers.push_back(objectCoord[i]);
			inliers_cnt++;
		}
	}

	outliers_cnt = matches1.size() - inliers_cnt;
	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryInliers, sceneInliers);
	cout << "最近邻匹配特征点数  " << matches1.size() << endl;
	cout << "内点数目  " << inliers_cnt << endl;
	cout << "外点数目  " << outliers_cnt << endl;

	

	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;


	//double inlier_ratio = (double)good_matches.size() / matches1.size();
	//cout << "匹配率  " << inlier_ratio << endl;
	imshow("matchingImage", result);
	imwrite("B1.jpg", result);
	waitKey();
	return 0;
}

SIFT特征点检测+SIFT描述子+最近邻匹配+线约束关系剔除(F矩阵)

F也有两种

1。mask

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img6.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	// 计算homography矩阵
	vector<cv::Point2f> queryInliers;
	vector<cv::Point2f> sceneInliers;
	const float inlier_threshold = 2.8f; // Distance threshold to identify inliers

	Mat mask;
	double minVal, maxVal;
	cv::minMaxIdx(queryCoord, &minVal, &maxVal);
	Mat F = findFundamentalMat(queryCoord, objectCoord, FM_RANSAC, 0.006 * maxVal, 0.99, mask); //threshold from [Snavely07 4.1]
	// N. Snavely, S. Seitz, R. Szeliski, “Modeling the World from Internet Photo Collections, ”Int’l J.of Computer Vision, Vol. 80, No. 2, pp.189–210, 2008.

	Mat F_inv = F.inv();
	int inliers_cnt = 0, outliers_cnt = 0;

	for (int j = 0; j < mask.rows; j++){
		if (mask.at<uchar>(j) == 1){
			queryInliers.push_back(queryCoord[j]);
			sceneInliers.push_back(objectCoord[j]);
			inliers_cnt++;
		}
		else {
			outliers_cnt++;
		}
	}

	outliers_cnt = matches1.size() - inliers_cnt;
	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryInliers, sceneInliers);
	cout << "最近邻匹配特征点数  " << matches1.size() << endl;
	cout << "内点数目  " << inliers_cnt << endl;
	cout << "外点数目  " << outliers_cnt << endl;

	

	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;


	//double inlier_ratio = (double)good_matches.size() / matches1.size();
	//cout << "匹配率  " << inlier_ratio << endl;
	imshow("matchingImage", result);
	imwrite("B1.jpg", result);
	waitKey();
	return 0;
}

参数的选择

论文:

Modeling the World from Internet Photo Collections

https://download.csdn.net/download/baidu_40840693/10732806

2。PFP*=0  1*3 * 3*3 3*1 = 0

这里暂时不知道怎么写,只能最后的值dist<0.01

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img6.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	// 计算homography矩阵
	vector<cv::Point2f> queryInliers;
	vector<cv::Point2f> sceneInliers;

	Mat mask;
	double minVal, maxVal;
	cv::minMaxIdx(queryCoord, &minVal, &maxVal);
	Mat F = findFundamentalMat(queryCoord, objectCoord, FM_RANSAC, 0.006 * maxVal, 0.99, mask); //threshold from [Snavely07 4.1]
	// N. Snavely, S. Seitz, R. Szeliski, “Modeling the World from Internet Photo Collections, ”Int’l J.of Computer Vision, Vol. 80, No. 2, pp.189–210, 2008.

	Mat F_inv = F.inv();
	int inliers_cnt = 0, outliers_cnt = 0;



	for (unsigned i = 0; i < queryCoord.size(); i++) {
		Mat row = Mat::ones(1, 3, CV_64F);
		row.at<double>(0) = queryCoord[i].x;
		row.at<double>(1) = queryCoord[i].y;

		row = row*F;

		Mat col = Mat::ones(3, 1, CV_64F);
		col.at<double>(0) = objectCoord[i].x;
		col.at<double>(1) = objectCoord[i].y;

		Mat dist = row*col;
		double distF = dist.at<double>(0);
		distF = sqrt(pow(distF, 2));

		if (distF < 0.01) { // 误差小于阈值
			queryInliers.push_back(queryCoord[i]);
			sceneInliers.push_back(objectCoord[i]);
			inliers_cnt++;
		}
	}


	outliers_cnt = matches1.size() - inliers_cnt;
	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryInliers, sceneInliers);
	cout << "最近邻匹配特征点数  " << matches1.size() << endl;
	cout << "内点数目  " << inliers_cnt << endl;
	cout << "外点数目  " << outliers_cnt << endl;

	

	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;


	//double inlier_ratio = (double)good_matches.size() / matches1.size();
	//cout << "匹配率  " << inlier_ratio << endl;
	imshow("matchingImage", result);
	imwrite("B1.jpg", result);
	waitKey();
	return 0;
}

根据F矩阵画出极线

#include<ctime>

#include "ASiftDetector.h"
#include "utils.h"

#include "gms_Header.h"
#include "gms_matcher.h"



cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage, 
	const vector<cv::KeyPoint> &qKeypoints, const vector<cv::KeyPoint> &objKeypoints,
	const vector<cv::DMatch> &matches1, 
	const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
	Size sz = Size(queryImage.size().width, 
		queryImage.size().height + objectImage.size().height);
	Mat matchingImage = Mat::zeros(sz, CV_8UC3);

	// 设置matchingImage的感兴趣区域大小并赋予原图
	Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
	queryImage.copyTo(roi1);
	Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
	objectImage.copyTo(roi2);

	//画出点
	for (int i = 0; i < (int)queryCoord.size(); ++i) {
		Point2f pt1 = queryCoord[i];
		Point2f pt2 = objectCoord[i];
		Point2f from = pt1;
		Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
		line(matchingImage, from, to, Scalar(0, 255, 255));
	}
	return matchingImage;
}

int main(int argc, const char * argv[]) {

	string imgFileName = "img1.JPG";
	Mat queryImage;
	queryImage = imread(imgFileName);

	string objFileName = "img3.JPG";
	Mat objectImage;
	objectImage = imread(objFileName);


	resize(queryImage, queryImage, Size(640, 480));
	resize(objectImage, objectImage, Size(640, 480));
	//imresize(queryImage, 480);
	//imresize(objectImage, 480);


	clock_t start, finish;
	start = clock();
	//检测SIFT特征点,方便后续做对比
	Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(2000);
	vector<KeyPoint> qKeypoints;
	feature->detect(queryImage, qKeypoints);
	Mat qDescriptor;
	feature->compute(queryImage, qKeypoints, qDescriptor);

	vector<KeyPoint> objKeypoints;
	feature->detect(objectImage, objKeypoints);
	Mat objDesriptor;
	feature->compute(objectImage, objKeypoints, objDesriptor);

	FlannBasedMatcher matcher;
	vector< DMatch > matches1;
	matcher.match(qDescriptor, objDesriptor, matches1);

	//点转换
	vector<cv::Point2f> queryCoord;
	vector<cv::Point2f> objectCoord;
	for (int i = 0; i < matches1.size(); i++){
		queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
		objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
	}

	// 计算homography矩阵
	vector<cv::Point2f> queryInliers;
	vector<cv::Point2f> sceneInliers;

	Mat mask;
	double minVal, maxVal;
	cv::minMaxIdx(queryCoord, &minVal, &maxVal);
	Mat F = findFundamentalMat(queryCoord, objectCoord, FM_RANSAC, 0.006 * maxVal, 0.99, mask); //threshold from [Snavely07 4.1]
	// N. Snavely, S. Seitz, R. Szeliski, “Modeling the World from Internet Photo Collections, ”Int’l J.of Computer Vision, Vol. 80, No. 2, pp.189–210, 2008.



	//Mat F_inv = F.inv(); //求逆
	Mat F_transpose = F.t(); //转置
	int inliers_cnt = 0, outliers_cnt = 0;



	for (unsigned i = 0; i < queryCoord.size(); i++) {
		Mat row = Mat::ones(1, 3, CV_64F);
		row.at<double>(0) = queryCoord[i].x;
		row.at<double>(1) = queryCoord[i].y;

		row = row*F;

		Mat col = Mat::ones(3, 1, CV_64F);
		col.at<double>(0) = objectCoord[i].x;
		col.at<double>(1) = objectCoord[i].y;

		Mat dist = row*col;
		double distF = dist.at<double>(0);
		distF = sqrt(pow(distF, 2));

		if (distF < 0.01) { // 误差小于阈值
			queryInliers.push_back(queryCoord[i]);
			sceneInliers.push_back(objectCoord[i]);
			inliers_cnt++;
		}
	}

	outliers_cnt = matches1.size() - inliers_cnt;
	Mat result = UpDownDrawInlier(queryImage, objectImage, qKeypoints, objKeypoints,
		matches1, queryInliers, sceneInliers);
	cout << "最近邻匹配特征点数  " << matches1.size() << endl;
	cout << "内点数目  " << inliers_cnt << endl;
	cout << "外点数目  " << outliers_cnt << endl;


	cv::Mat imagekeyPt1; // 左图
	cv::Mat imagekeyPt2; // 右图
	vector<KeyPoint> NewKeyP1;
	vector<KeyPoint> NewKeyP2;
	for (int i = 0; i < queryInliers.size(); i++){
		KeyPoint key;
		key.pt.x = queryInliers[i].x;
		key.pt.y = queryInliers[i].y;
		NewKeyP1.push_back(key);
		key.pt.x = sceneInliers[i].x;
		key.pt.y = sceneInliers[i].y;
		NewKeyP2.push_back(key);
	}
	cv::drawKeypoints(queryImage, NewKeyP1, imagekeyPt1, cv::Scalar(255, 0, 0), cv::DrawMatchesFlags::DEFAULT);
	cv::drawKeypoints(objectImage, NewKeyP2, imagekeyPt2, cv::Scalar(255, 0, 0), cv::DrawMatchesFlags::DEFAULT);

	//使用基础矩阵 在对应图像上绘制外极线
	std::vector<cv::Vec3f> lines1; //存储外极线
	cv::computeCorrespondEpilines(cv::Mat(queryInliers), 1, F, lines1);//获取图像1中的二维特征点 在图像2中对应的外极线
	for (std::vector<cv::Vec3f>::const_iterator it1 = lines1.begin();
		it1 != lines1.end(); ++it1)
	{
		cv::line(imagekeyPt2,
			cv::Point(0, -(*it1)[2] / (*it1)[1]),
			cv::Point(imagekeyPt2.cols, -((*it1)[2] + (*it1)[0] * imagekeyPt2.cols) / (*it1)[1]),
			cv::Scalar(0, 0, 255));
	}
	cv::namedWindow("Image2 Epilines");
	cv::imshow("Image2 Epilines", imagekeyPt2);
	

	//使用基础矩阵 在对应图像上绘制外极线
	std::vector<cv::Vec3f> lines2; //存储外极线
	cv::computeCorrespondEpilines(cv::Mat(sceneInliers), 2, F, lines2);//获取图像2中的二维特征点 在图像1中对应的外极线
	// 这里存有疑问,computeCorrespondEpilines第二个参数为2,就自动是2对1,那么F是否还需要转置,这里需要看一下源代码才知道
	for (std::vector<cv::Vec3f>::const_iterator it2 = lines2.begin();
		it2 != lines2.end(); ++it2)
	{
		cv::line(imagekeyPt1,
			cv::Point(0, -(*it2)[2] / (*it2)[1]),
			cv::Point(imagekeyPt1.cols, -((*it2)[2] + (*it2)[0] * imagekeyPt1.cols) / (*it2)[1]),
			cv::Scalar(0, 0, 255));
	}
	cv::namedWindow("Image1 Epilines");
	cv::imshow("Image1 Epilines", imagekeyPt1);



	finish = clock();
	double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << setprecision(10) << fixed << "sift_time   " << sift_time << endl;


	//double inlier_ratio = (double)good_matches.size() / matches1.size();
	//cout << "匹配率  " << inlier_ratio << endl;
	imshow("matchingImage", result);
	imwrite("B1.jpg", result);
	waitKey();
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值