opencv3学习笔记(十一)---------特征检测与匹配

 

【特征点的检测与匹配】是计算机视觉中非常重要的技术之一。在物体检测、视觉跟踪、三维重建等领域都有很广泛的应用。
opencv提供了10种特征检测方法:
【FAST】
【STAR】
【SIFT】
【SURF】
【ORB】
【MSER】
【GFTT】
【HARRIS】
【Dense】
【SimpleBlob】

1【SURF特征点检测】

SURF---加速版的具有鲁棒性的特征算法(SIFT---尺寸不变特征变换算法的加速版),SURF最大特征在于采用harr特征以及积分图像的概念,
大大加速了程序的运行时间。应用于计算机视觉的物体识别、3D重建中。
缺点:严重依赖局部像素的梯度方向,容易出现方向不准等问题。
算法原理----①:选用二阶标准高斯函数作为滤波器(Hessian矩阵构造高斯金字塔尺度空间)
            ②:利用非极大值抑制初步确定特征点
            ③:精确定位极值点(采用三维线性插值法得到亚像素级的特征点)
            ④:选取特征点的主方向(梯度直方图,选大于等于bin值的那些方向)
            ⑤:构造surf特征点描述算子

绘制关键点:drawKeypoints()函数

void drawKeypoints(
const Mat&image,                       //输入图像
const vector<KeyPoint>&keypoints,      //根据原图像得到的特征点
Mat& outImage,                         //输出图像
const Scalar& color=Scalar::all(-1),  //关键点颜色(默认Scalar::all(-1))
int flags=DrawMatchesFlags::DEFAULT   //绘制关键点的特征标识符(默认DrawMatchesFlags::DEFAULT)
);

KeyPoint类-----一个为特征点检测而生的数据结构,用于表示特征点。
class KeyPoint{
    Point2f pt;//坐标
    float size;//特征点邻域直径
    float angle;//特征点的方向,值为[0,360],负值表示不使用
    float response;
    int octave;//特征点所在的图像金字塔的组
    int class_id;//用于聚类的id
}


综合示例:SURF特征点检测


//------------------------【SURF特征点检测】-----------------------------
//描述:使用FeatureDetector接口来发现感兴趣点
//      使用SurfFeatureDetector以及其函数detect来实现检测过程
//      使用函数drawKeypoints绘制检测到的关键点
//-----------------------------------------------------------------------
#include <opencv2/opencv.hpp>  
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/xfeatures2d/nonfree.hpp>
#include <iostream>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
int main()
{
    Mat srcImage1 = imread("1.jpg", 1);
	Mat srcImage2 = imread("2.jpg", 1);
	if (!srcImage1.data || !srcImage2.data){cout << "读取图片出错" << endl;return false;}
	
	imshow("原始图1", srcImage1);
	imshow("原始图2", srcImage2);
		
	int minHessian = 100;//SURF算子检测关键点

    //定义一个SurfFeatureDetector(SURF)特征检测类对象
	Ptr<SurfFeatureDetector> detector = SurfFeatureDetector::create(minHessian);
	vector<cv::KeyPoint> key_points_1, key_points_2;//vector模板类,存放任意的动态数组
	
	Mat dstImage1, dstImage2;
	
	//调用detect函数检测出SURF特征关键点,保存在vector容器中
    detector->detectAndCompute(srcImage1, Mat(), key_points_1, dstImage1);
	detector->detectAndCompute(srcImage2, Mat(), key_points_2, dstImage2);//可以分成detect和compute
	Mat img_keypoints_1, img_keypoints_2;
	
	//绘制特征关键点
	drawKeypoints(srcImage1, key_points_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
	drawKeypoints(srcImage2, key_points_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT);

	Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("FlannBased");
	vector<DMatch>mach;
	matcher->match(dstImage1, dstImage2, mach);
	double Max_dist = 0;
	double Min_dist = 100;
	for (int i = 0; i < dstImage1.rows; i++)
	{
		 double dist = mach[i].distance;
		 if (dist < Min_dist)Min_dist = dist;
		 if (dist > Max_dist)Max_dist = dist;
	}
	cout << "最短距离" << Min_dist << endl;
	cout << "最长距离" << Max_dist << endl;

	vector<DMatch>goodmaches;
	for (int i = 0; i < dstImage1.rows; i++)
	{
	    if (mach[i].distance < 2 * Min_dist)goodmaches.push_back(mach[i]);
	}
	Mat img_maches;
    drawMatches(srcImage1, key_points_1, srcImage2, key_points_2, goodmaches, img_maches);
	
	for (int i = 0; i < goodmaches.size(); i++)
	{
		cout << "符合条件的匹配:" << goodmaches[i].queryIdx << "--" << goodmaches[i].trainIdx << endl;
	}
    imshow("效果图1", img_keypoints_1);
	imshow("效果图2", img_keypoints_2);
	imshow("匹配效果", img_maches);
	
	waitKey(0);
	return 0;
 }
 

2【FLANN特征检测与匹配】

检测关键点并计算描述符:detectAndCompute()

void detectAndCompute(
    InputArray image, //图像
    InputArray mask, //掩模
    CV_OUT std::vector<KeyPoint>& keypoints,//输出关键点的集合
    OutputArray descriptors,//计算描述符(descriptors[i]是为keypoints[i]的计算描述符)
    bool useProvidedKeypoints=false //使用提供的关键点
);

从查询集中查找每个描述符的最佳匹配:DescriptorMatcher::match()方法。
 

void DescriptorMatcher::match(
    const Mat& queryDescriptors, //查询描述符集
    const Mat&  trainDescriptors, //训练描述符集合
    CV_OUT std::vector<DMatch>& matches, //匹配
    InputArray mask=noArray() //指定输入查询和描述符的列表矩阵之间的允许匹配的掩码
);

综合示例:FLANN特征点检测

#include<opencv2/opencv.hpp>
#include<opencv2/xfeatures2d.hpp>
using namespace cv;
using namespace cv::xfeatures2d;

//FLANN对高维数据较快
int main()
{
	Mat src1, src2;
	src1 = imread("1.jpg");
	src2 = imread("2.jpg");
	if (src1.empty() || src2.empty()){printf("can ont load images....\n");return -1;}
	imshow("image1", src1);
	imshow("image2", src2);

	int minHessian = 400;

	//选择SURF特征
	Ptr<SURF>detector = SURF::create(minHessian);
	std::vector<KeyPoint>keypoints1;
	std::vector<KeyPoint>keypoints2;
	Mat descriptor1, descriptor2;
	//检测关键点并计算描述符
	detector->detectAndCompute(src1, Mat(), keypoints1, descriptor1);
	detector->detectAndCompute(src2, Mat(), keypoints2, descriptor2);

	//基于Flann的描述符匹配器
	FlannBasedMatcher matcher;
	std::vector<DMatch>matches;
	//从查询集中查找每个描述符的最佳匹配
	matcher.match(descriptor1, descriptor2, matches);
	double minDist = 1000;
	double maxDist = 0;
	for (int i = 0; i < descriptor1.rows; i++)
	{
		double dist = matches[i].distance;
		printf("%f \n", dist);
		if (dist > maxDist)
		{
			maxDist = dist;
		}
		if (dist < minDist)
		{
			minDist = dist;
		}

	}
	//DMatch类用于匹配关键点描述符的
	std::vector<DMatch>goodMatches;
	for (int i = 0; i < descriptor1.rows; i++)
	{
		double dist = matches[i].distance;
		if (dist < max(2.5*minDist, 0.02))
		{
			goodMatches.push_back(matches[i]);
		}
	}
	Mat matchesImg;
	drawMatches(src1, keypoints1, src2, keypoints2, goodMatches, matchesImg, Scalar::all(-1), Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
	imshow("output", matchesImg);

	waitKey();
	return 0;
}

【FLANN结合SURF进行关键点的描述和匹配】 :

//------------------【FLANN结合SURF进行关键点的描述和匹配】----------------------

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

int main()
{
	//【1】载入原图
	Mat srcImage = imread("1.jpg");
	imshow("【原图】", srcImage);

	//【2】对BGR空间的图像直接进行计算很费时间,所以,需要转换为灰度图
	Mat srcGrayImage;
	cvtColor(srcImage, srcGrayImage, CV_BGR2GRAY);

	//【3】首先对两幅图像检测SURF关键点、提取测试图像描述符
	vector<KeyPoint> keyPoint1;
	Mat dstImage1, dstImage2;
	Ptr<SURF> surf = SURF::create(80);
	surf->detect(srcGrayImage, keyPoint1);
	Mat descriImage1;
	surf->compute(srcGrayImage, keyPoint1, descriImage1);

	//【4】先对原图的描述子进行保留-------邻近匹配
	//FlannBasedMatcher FLMatcher;
	//因为FlannBasedMatcher类的成员函数add()的参数是一个vector<Mat>的容器,所以先定义一个这样的变量,并将原图的描述子放入容器中
	//vector<Mat> g_vdescriImage1(1, descriImage1);
	//调用FlannBasedMatcher类的成员函数add,将原图的描述子放在FlannBasedMatcher的对象FLMatcher中
	//FLMatcher.add(g_vdescriImage1);
	//FLMatcher.train();

	//【4】进行基于描述符的-------暴力匹配
	BFMatcher matcher;
	//因为FlannBasedMatcher类的成员函数add()的参数是一个vector<Mat>的容器,所以先定义一个这样的变量,并将原图的描述子放入容器中
	vector<Mat> g_vdescriImage1(1, descriImage1);
	//调用FlannBasedMatcher类的成员函数add,将原图的描述子放在FlannBasedMatcher的对象FLMatcher中
	matcher.add(g_vdescriImage1);
	matcher.train();

	VideoCapture capture;
	capture.open(0);

	Mat frameImage, frameGrayImage;
	while (waitKey(1) != 27)
	{
		capture >> frameImage;

		//<1>为了提高计算效率,将图像转换为灰度图像
		cvtColor(frameImage, frameGrayImage, CV_BGR2GRAY);

		//<2>检测S关键点、提取测试图像描述符
		vector<KeyPoint> keyPoints2;
		surf->detect(frameGrayImage, keyPoints2);
		Mat descriImage2;
		surf->compute(frameGrayImage, keyPoints2, descriImage2);

		//<3>将之前得到的原图的描述子和现在得到的描述子进行匹配(匹配训练和测试描述符)
		//成员函数knnMatch的参数是二维的DMatch向量,所以首先定义一个该容器的向量
		vector<vector<DMatch>> knnDMatches;
		
		//<4>用之前已经存放原图描述子的对象来计算------邻近匹配
		//FLMatcher.knnMatch(descriImage2, knnDMatches, 2);

		//<4>用之前已经存放原图描述子的对象来计算------暴力匹配
		matcher.knnMatch(descriImage2, knnDMatches, 2);
		//<5>根据劳氏算法,采集优秀的匹配点
		vector<DMatch> goodMatches;
		for (size_t i = 0; i < knnDMatches.size(); i++)
		{
			if (knnDMatches[i][0].distance < 0.6 * knnDMatches[i][1].distance)
			{
				goodMatches.push_back(knnDMatches[i][0]);
			}
		}
		//<6>绘制匹配点并显示窗口
		Mat dstImage;
		drawMatches(frameImage, keyPoints2, srcImage, keyPoint1, goodMatches, dstImage);

		imshow("【结果图】", dstImage);
	}

	return 0;
}

3、【寻找已知物体】

寻找已知物体:在Flann特征匹配的基础上,还可以进一步利用Homography映射找出已知物体。
集体来说就是利用findHomography函数通过匹配的关键点找出相应的变量,在利用perspectiveTransform函数映射点群。

寻找透视变换:findHomography()函数----找到并返回原图像和目标图像之间的透视变换H

Mat findHomography(
    InputArray srcPoints,   //原平面上的对应点
    InputArray dstPoints,   //目标平面上的对应点
    int method=0,//用于计算单应矩阵的方法(默认0;CV_RANSAC---基于RANSAC的鲁棒性方法;CV_LMEDS---最小中值鲁棒性方法)
    double ransacReprojThreshold=3,//(默认3)处理点对为内围层时,允许重投影误差的最大值
    OutputArray mask=noArray()//可选参数
);

进行透视矩阵变换:perspectiveTransform()函数---进行向量透视矩阵变换

void perspectiveTransform(
    InputArray src,   //输入图像
    OutputArray dst,  //目标图像
    InputArray m     //变换矩阵
);

综合示例:寻找已知物体

//-------------------【寻找已知物体】---------------
#include<opencv2/opencv.hpp>
#include<vector>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/xfeatures2d/nonfree.hpp>
#include <iostream>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

int main()
{
	Mat srcImage1 = imread("1.jpg");
	Mat srcImage2 = imread("2.jpg");
	imshow("【原图1】", srcImage1);
	imshow("【原图2】", srcImage2);

	Mat grayImage1, grayImage2;
	cvtColor(srcImage1, grayImage1, CV_BGR2GRAY);
	cvtColor(srcImage2, grayImage2, CV_BGR2GRAY);

	//首先对两幅图像进行特征点的检测
	//先准备参数
	vector<KeyPoint> g_vKeyPoint1;
	vector<KeyPoint> g_vKeyPoint2;
	Ptr<SURF> surf = SURF::create(400);
	surf->detect(grayImage1, g_vKeyPoint1);
	surf->detect(grayImage2, g_vKeyPoint2);

	//利用得到的特征点计算特征描述子
	//目的:对得到的每个特征点进行特征描述,整合到Mat类型的矩阵中(计算结果是Mat类型的)
	//该得到的结果矩阵的行数就是特征点的个数,因为是对每个点进行描述,所以每行都会有一个描述的字子向量,共同构成Mat矩阵
	Mat descriImage1, descriImage2;
	surf->compute(grayImage1, g_vKeyPoint1, descriImage1);
	surf->compute(grayImage2, g_vKeyPoint2, descriImage2);

	//正式开始在两幅图像中进行匹配
	//先得到一个匹配向量
	FlannBasedMatcher FLMatcher;
	vector<DMatch> g_vMatches;
	//g_vMatches就是得到的匹配向量
	FLMatcher.match(descriImage1, descriImage2, g_vMatches);

	//用找最大最小值的方式找到 两幅图像中匹配的点的距离的最大值和最小值
	//这里的 keyPoint1.size() 和 descriImage1.rows是一样的值,因为descriImage1的行数就是检测到的特征点的个数
	double minDistance = g_vMatches[0].distance, maxDistance = g_vMatches[0].distance;
	for (size_t i = 0; i < g_vKeyPoint1.size(); i++)
	{
		double currDistance = g_vMatches[i].distance;
		if (currDistance < minDistance)
			minDistance = currDistance;
		if (currDistance > maxDistance)
			maxDistance = currDistance;
	}

	//定义一个新的变量,用来存储 通过距离检测后  通过阀值的点
	vector<DMatch> newMatches;
	for (size_t i = 0; i < g_vKeyPoint1.size(); i++)
	{
		if (g_vMatches[i].distance < 2 * minDistance)
			newMatches.push_back(g_vMatches[i]);
	}

	//用绘制函数对匹配向量进行绘制
	Mat dstImage;
	drawMatches(srcImage1, g_vKeyPoint1, srcImage2, g_vKeyPoint2, newMatches, dstImage
		, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255))
		, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), Mat(), 2);

	imshow("【特征提取后的图像】", dstImage);

	//=================================正式开始寻找已知物体============================
	//为了调用 得到H矩阵findHomography函数,所以需要得到 匹配点所对应的特征点   然后作为参数传递给计算H矩阵的函数
	//所以首先是进行 匹配点和对应的特征点的转换步骤
	//将得到的点放入新的容器中,所以需要定义新的容器
	vector<Point2f> g_vSrcPoint2f1;
	vector<Point2f> g_vSrcPoint2f2;
	for (size_t i = 0; i < newMatches.size(); i++)
	{
		g_vSrcPoint2f1.push_back(g_vKeyPoint1[newMatches[i].queryIdx].pt);
		g_vSrcPoint2f2.push_back(g_vKeyPoint2[newMatches[i].trainIdx].pt);
	}

	//将得到的对应的特征点  计算H矩阵
	Mat H = findHomography(g_vSrcPoint2f1, g_vSrcPoint2f2, 0);

	用得到的H矩阵  来进行透视矩阵变换  用到的是perspectiveTransform函数
	//vector<Point2f> g_vCorners1(4);
	//vector<Point2f> g_vCorners2(4);
	//g_vCorners1[0] = Point2f(0, 0);
	//g_vCorners1[1] = Point2f((float)srcImage1.cols, 0);
	//g_vCorners1[2] = Point2f((float)srcImage1.cols, (float)srcImage1.rows);
	//g_vCorners1[3] = Point2f(0, (float)srcImage1.rows);

	//perspectiveTransform(g_vCorners1, g_vCorners2, H);

	在得到的两幅图像的合成图中绘制检测到的物体的直线
	//line(dstImage, (Point)g_vCorners2[0] + Point(srcImage1.cols, 0), (Point)g_vCorners2[1] + Point(srcImage1.cols, 0)
	//	, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
	//line(dstImage, (Point)g_vCorners2[1] + Point(srcImage1.cols, 0), (Point)g_vCorners2[2] + Point(srcImage1.cols, 0)
	//	, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
	//line(dstImage, (Point)g_vCorners2[2] + Point(srcImage1.cols, 0), (Point)g_vCorners2[3] + Point(srcImage1.cols, 0)
	//	, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
	//line(dstImage, (Point)g_vCorners2[3] + Point(srcImage1.cols, 0), (Point)g_vCorners2[0] + Point(srcImage1.cols, 0)
	//	, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);

	//imshow("【检测物体后的图像】", dstImage);

	//进行角点检测
	开始进行强角点检测
	先配置需要的函数参数
	vector<Point2f> dstPoint2f1;
	goodFeaturesToTrack(grayImage1, dstPoint2f1, 200, 0.01, 10, Mat(), 3);
	vector<Point2f> dstPoint2f2(dstPoint2f1.size());
	//进行透视变换
	perspectiveTransform(dstPoint2f1, dstPoint2f2, H);

	//在计算得到的点中寻找最小包围矩形
	//rectPoint变量中得到了矩形的四个顶点坐标
	RotatedRect rectPoint = minAreaRect(dstPoint2f2);
	//定义一个存储以上四个点的坐标的变量
	Point2f fourPoint2f[4];
	//将rectPoint变量中存储的坐标值放到 fourPoint的数组中
	rectPoint.points(fourPoint2f);

	//根据得到的四个点的坐标  绘制矩形
	for (int i = 0; i < 3; i++)
	{
		line(srcImage2, fourPoint2f[i], fourPoint2f[i + 1]
			, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 3);
	}
	line(srcImage2, fourPoint2f[0], fourPoint2f[3]
		, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 3);

	imshow("【检测到的物体】", srcImage2);

	waitKey(0);

	return 0;
}

4、【ORB特征提取】

优于其他两种方法。

//------------------【ORB】--------------------------

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

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage = imread("1.jpg");
	imshow("【原图】", srcImage);

	//对BGR空间的图像直接进行计算很费时间,所以,需要转换为灰度图
	Mat srcGrayImage;
	cvtColor(srcImage, srcGrayImage, CV_BGR2GRAY);

	//首先对两幅图像进行特征点的检测和描述子的计算
	vector<KeyPoint> keyPoint1;
	Ptr<ORB> orb = ORB::create(400);
	//调用detect函数检测出特征关键点,保存在vector中
	orb->detect(srcGrayImage, keyPoint1);
	Mat descriImage1;
	//计算描述符(特征向量)
	orb->compute(srcGrayImage, keyPoint1, descriImage1);

	//基于FLANN的描述符对象匹配
	flann::Index flannIndex(descriImage1, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);
	
	//初始化视屏采集对象
	VideoCapture capture;
	capture.open(0);
	capture.set(CV_CAP_PROP_FRAME_WIDTH, 360);//设置采集视频的宽度
	capture.set(CV_CAP_PROP_FRAME_HEIGHT, 900);//设置采集视频的高度

	Mat frameImage, frameGrayImage;

	while (waitKey(1) != 27)
	{
		capture >> frameImage;

		//为了提高计算效率,将图像转换为灰度图像
		cvtColor(frameImage, frameGrayImage, CV_BGR2GRAY);

		//计算特征点和描述子
		vector<KeyPoint> keyPoints2;
		orb->detect(frameGrayImage, keyPoints2);
		Mat descriImage2;
		orb->compute(frameGrayImage, keyPoints2, descriImage2);

		//匹配和测试描述符,获取两个最临近的描述符
		Mat matchIndex(descriImage2.rows, 2, CV_32SC1);
		Mat matchDistance(descriImage2.rows, 2, CV_32SC1);

		//调用k邻近算法
		flannIndex.knnSearch(descriImage2, matchIndex, matchDistance, 2, flann::SearchParams());


		//采集优秀的匹配点(根据劳氏算法)
		vector<DMatch> goodMatches;
		for (int i = 0; i < matchDistance.rows; i++)
		{
			//........................................................................
			if (matchDistance.at<float>(i, 0) < 0.6 * matchDistance.at<float>(i, 1))
			{
				DMatch midDMatch(i, matchIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));
				goodMatches.push_back(midDMatch);
			}
		}

		Mat dstImage;
		drawMatches(frameImage, keyPoints2, srcImage, keyPoint1, goodMatches, dstImage);

		imshow("【结果图】", dstImage);
	}

	return 0;
}

 

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值