OpenCV3学习笔记(5)- feature2d组件

5.1 Harris角点检测

图像特征类型:边缘、角点(感兴趣关键点)、斑点(感兴趣区域)
角点:位于两条边缘的交点,代表两个边缘变化的方向上的点。(1)一阶导数(梯度)的局部最大对应的像素点;(2)两条及两条以上边缘的交点;(3)图像中梯度值和梯度方向的变化速率都很高的点;(4)角点处的一阶导数最大,二阶导数为0,指示了物体边缘变化不连续的反向。
当前角点检测算法
(1)基于灰度图像的角点检测:基于梯度、基于模板(考虑像素领域点的灰度变化即亮度变化,将邻点亮度对比足够大的点定义为角点,包括Kitchen-Rosenfeld角点检测算法、Harris角点检测算法、KLT角点检测算法、SUSAN角点检测算法)、基于模板梯度
(2)基于二值图像的角点检测
(3)基于轮廓曲线的角点检测
harris角点检测:一种直接基于灰度图像的角点提取算法,稳定性高,尤其对于L型角点检测精度高,但由于采用了高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的情况,而且角点有聚簇想想。

实现harris角点检测:cornerHarris()函数
与cornerMinEigenVal()和cornerEigenValsAndVecs()函数类似,对于每一个像素(x,y)在blockSize×blockSize邻域内计算2×2梯度的协方差矩阵M(x,y),再通过计算下式得到输出图局部最大值
在这里插入图片描述

void conerHarris(InputArray src, OutputArray dst, int blockSize, int ksize, double k, int borderType=BORDER_DEFAULT)

参数1 InputArray类型的src,源图像,单通道8位或浮点型图像
参数2 OutputArray类型的dst,存放Harris角点检测输出结果
参数3 int类型的blockSize,领域大小
参数4 int类型的ksize,Sobel()算子的孔径大小
参数5 double类型的k,Harris参数
参数6 int类型的borderType,图像像素的边界模式,有默认值BORDER_DEFAULT

Mat srcImage = imread("1.jpg");
Mat cornerStrength;
cornelHarris(srcImage, cornerStrength, 2, 3, 0.01);
Mat harrisCorner;
threshold(cornerStrength, harrisCorner, 0.00001, 255, THRESH_BINARY);

OpenCV3学习笔记(5)- feature2d组件_第1张图片

5.2 Shi-Tomasi角点检测

基本思想:将矩阵M的行列式值与M的迹相减,再将差值同预先给定的阈值进行比较。若两个特征值(矩阵M的行列式值与M的迹)中较小的一个大于最小阈值,则会得到强角点
确定图像强角点:goodFeaturesToTrack()函数

void goodFeatureToTrack(InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance, InputArray mask=noArray(), int blockSize=3, bool useHarrisDetector=false, double k=0.04)

参数1 InputArray类型的image,输入图像,8位或浮点型32位单通道图像
参数2 OutputArray类型的corners,检测到的角点输出向量
参数3 int类型的maxCorners,角点的最大数量
参数4 double类型的qualityLevel,角点检测可接受的最小特征值,实际用于过滤角点的最小特征值是qualityLevel与图像中最大特征值的乘积,所以qualityLevel通常不会超过1(常用值为0.1或0.01),在检测完所有的角点后,要进一步剔除掉一些距离较近的角点
参数5 double类型的minDistance,角点之间的最小距离,此参数用于保证返回的角点之间的距离不小于minDistance个像素
参数6 InputArray类型的mask,感兴趣区域,有默认值noArray()
参数7 int类型的blockSzie,默认值3,计算导数自相关矩阵时指定的领域范围
参数8 bool类型的useHarrisDetector,默认值false,是否使用Harris角点检测
参数9 double类型的k,默认值0.04,用于设置Hessian自相关矩阵行列式的相对权重的权重系数

5.3 亚像素级角点检测

亚像素级角点检测:进行几何测量而不是提取用于识别的特征点时,需要更高的精度,有时需要实数坐标值而不是整数坐标值。在摄像机标定、跟踪并重建摄像机的轨迹,或重建被跟踪目标的三维结构时,是一个基本的测量值。
OpenCV3学习笔记(5)- feature2d组件_第2张图片
假设起始角点q在实际亚像素级角点附近,检测所有的p-q向量,两种情况下p点处的梯度与q-p向量正交,点积为0。可以在p点周围找到很多组梯度以及相关向量q-p,令其点集为0,然后求解方程组,方程组的解即为角点q的亚像素级精度位置。

寻找亚像素角点:cornerSubPix()函数

void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)

参数1 InputArray类型的image,源图像
参数2 InputOutputArray类型的corners,提供输入角点的初始坐标和精确的输出坐标
参数3 Size类型的winSize,搜索窗口的一半尺寸。若winSize=Size(5,5),那么表示使用11×11大小的搜索窗口
参数4 Size类型的zeroZone,死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域,用来避免自相关矩阵出现的某些可能的奇异性,值为(-1,-1)表示没有死区
参数5 TermCriteria类型的criteria,求角点的迭代过程的终止条件,可以是最大迭代数目,或是设定的精确度,或它们的组合

Size winSize=Size(5,5);
Size zeroZone=Size(-1,-1);
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
cornerSubPix(g_grayImage, corners, winSize, zeroZone, criteria);
//输出角点信息
for (int i = 0; i < corners.size(); i++){
	cout << "\t >> 精确点坐标 ["<<i<<"]("<<corners[i].x<<","<<corners[i].y<<")" << endl;
}

OpenCV3学习笔记(5)- feature2d组件_第3张图片

5.4 SURF特征点检测

OpenCV提供的10种特征检测方法
FAST - FastFeatureDetector
STAR - StarFeatureDetector
SIFT - SIFT
SURF - SURF
ORB - ORB
MSER - MSER
GFTT - GoodFeaturesToTrackDetector
HARRIS - GoodFeaturesToTrackerDetector
Dense - DenseFeatureDetector
SimpleBlob - SimpleBlobDetector
注意:目前OpenCV3中特征检测算子依赖的稳定版源代码已从OpenCV3中移除,而转移到一个xfeature2d的第三方库中
SURF(SpeededUp Robust Features,加速版的具有鲁棒性的特征)算法,是SIFT(尺度不变特征变换算法)的加速版,在多幅图片下具有更好的稳定性,采用了harr特征以及积分图像的概念,加快了程序的运行时间。

SURF算法原理
(1)构建Hessian矩阵构造高斯金字塔尺度空间
采用Hessian矩阵行列式近似值图像,是Surf算法的核心。Hessian矩阵是一个自变量为向量的实值函数的二阶偏导数组成的方块矩阵,为了方便运算假设函数f(z,y),Hessian矩阵H是函数、偏导数组成
OpenCV3学习笔记(5)- feature2d组件_第4张图片
判别式的值是H矩阵的特征值,可以利用判定结果的符号将所有点分类,根据判别式取值正负,判别该点是不是极值点。
在SURF算法中,选用二阶标准高斯函数作为滤波器,通过特定核间的卷积计算二阶偏导数,这样就能计算出H矩阵的三个矩阵元素L_xx, L_xy, L_yy进而计算H矩阵。
由于特征点需具备尺度无关性,所以进行Hessian矩阵构造前需要高斯滤波。可以为图像中每个像素计算出其H行列式的决定值,并用这个值判别特征点。
OpenCV3学习笔记(5)- feature2d组件_第5张图片
为方便应用,Herbert Bay提出用近似值代替L(x,t)。为平衡准确值与近似值间的误差引入权值,权值随尺度变化,H矩阵判别式可表示为
在这里插入图片描述
由于求Hessian时先高斯平滑,再求二阶导数,这两种操作合在一起用一个模板代替即可,比如y方向上的模板
OpenCV3学习笔记(5)- feature2d组件_第6张图片
OpenCV3学习笔记(5)- feature2d组件_第7张图片
相关金字塔结构:SIFT算法中,同一个octave层中的图片尺寸(大小)相同,但尺度(模糊程度)不同,不同的octave层中图片尺寸大小不同,再高斯模糊时SIFT的高斯模板大小是固定不变的,只在不同的octave之间改变图片的大小。
SURF中,图片大小一直不变,不同octave层的待检测图片是改变高斯模糊尺寸大小得到的。算法允许尺度空间多层图像同时被处理,不需要对图像进行二次抽样,从而提高算法性能。
OpenCV3学习笔记(5)- feature2d组件_第8张图片
(2)利用非极大值抑制初步确定特征点
将经过Hessian矩阵处理过的每个像素点与其三维领域的26个点进行大小比较,如果它是26个点中的最大值或最小值,则保留下来当作初步特征点。
检测过程使用与尺度层图像解析度相对应大小的滤波器进行检测,以3×3滤波器为例,该尺度层图像中9个像素点之一的检测特征点与自身尺度层中其余8个点以及上下两层各9个点进行比较,共26个点。
OpenCV3学习笔记(5)- feature2d组件_第9张图片
(3)精确定位极值点
采用三维线性插值法得到亚像素级的特征点,同时去掉那些值小于一定阈值的点,增加极值使检测到的特征点数量减少,最终只有几个特征最强点会被检测出来。
(4)选取特征点的主方向
SIFT:采用在特征点领域内统计其梯度直方图,取直方图bin值最大的以及超过最大bin值80%的那些方向作为特征点主方向
SURF:统计特征点领域内的harr小波特征,即在特征点领域(如半径6s圆内,s为该点尺度),统计60度扇形内所有点的水平haar小波特征和垂直haar小波特征总和,haar小波的尺寸边长为4s,这样一个扇形得到了一个值,然后60度扇形以一定间隔进行旋转,最后将最大值那个扇形的方向作为该特征点的主方向。
OpenCV3学习笔记(5)- feature2d组件_第10张图片
(5)构造SURF特征点描述算子
SIFT:特征点周围取16×16邻域,并把该邻域化为4×4个小区域,每个区域统计8个方向梯度,最后得到4×4×8=128维向量,该向量作为该点SIFT描述子
SURF:特征点周围取一个正方形框,框的边长为20s。该框方向为步骤(4)检测出来的主方向,然后把该框分为16个子区域,每个区域统计25个像素的水平/垂直方向(相对主方向而言)的小波特征。该haar小波特征为水平方向值之和、水平方向绝对值之和、垂直方向之和、垂直方向绝对值之和。这样每个小区域内有4个值,每个特征点就是16×4=64维向量,相比SIFT而言少了一半,加快匹配速度。
OpenCV3学习笔记(5)- feature2d组件_第11张图片
SURF缺陷:(1)获取图像局部最值十分稳定,但在求主方向阶段太过依赖局部区域像素梯度方向,导致主方向不准确。后面的特征向量提取以及匹配依赖主方向,即使不大偏差也会造成后面特征匹配的放大误差,使匹配不成功;(2)图像金字塔的层取得不够紧密使尺度有误差,后面特征向量提取同样依赖相应尺度,解决方法是取适量的层然后插值。
SURF类
SURF=SurfFeatureDetector=SurfDescriptorExtractor
继承自FeatureDetector, DescriptorExtractor类
FeatureDetector, DescriptorExtractor类继承自Algorithm基类

绘制关键点:drawKeypoints()函数

void drawKeypoints(const Mat& image, const vector<KeyPoint>& keypoints, Mat& outImage, const Scalar& color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT)

参数1 const Mat&类型的src,源图像
参数2 const vector&类型的keypoints,根据源图像得到的特征点
参数3 Mat&类型的outImage,输出图像,内容取决于第5个标识符flags
参数4 const Scalar&类型的color,关键点颜色
参数5 int类型的flags,绘制关键点的特征标识符,默认值DrawMatchesFlags::DEFAULT
OpenCV3学习笔记(5)- feature2d组件_第12张图片
KeyPoint类

class KeyPoint{
	Point2f pt; //坐标
	float size; //特征点邻域直径
	float angle; //特征点方向
	float response;
	int octave; //特征点所在金字塔组
	int class_id; //用于聚类的id
}

例:SURF特征点检测
1、使用FeatureDetector接口发现感兴趣点
2、使用SurfFeatureDetector以及函数detect实现检测过程
3、使用函数drawKeypoints绘制检测到的关键点

#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgi.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include 
using namespace cv;
int main(){
	Mat srcImage1 = imread("1.jpg", 1);
	Mat srcImage2 = imread("2.jpg", 1);
	//[1]定义变量和类
	int minHessian = 400; //SURF中的hessian阈值特征点检测算子
	SurfFeatureDetector detector(minHessian); //定义一个SurfFeatureDetector(SURF)特征检测类对象
	std::vector<KeyPoint> keypoints_1, keypoints_2; //vector模板类是否能存放任意类型的动态数组,能够增加和压缩数据
	//[2]调用detect函数检测出SURF特征关键点,保存在vector容器中
	detector.detect(srcImage1, keypoints_1);
	detector.detect(srcImage2, keypoints_2);
	//[3]绘制特征关键点
	Mat img_keypoints_1; Mat img_keypoints_2;
	drawKeypoints(srcImage1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
	drawKeypoints(srcImage2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
}

5.5 SURF特征提取

SURF算法为每个检测到的特征定义了位置和尺度,尺度值用于定义围绕特征点的窗口大小,无论物体的尺度在窗口是什么样的,都将包含相同的视觉信息。特征匹配中,特征描述子通常用于N维向量,在光照不变以及少许透视变形的情况下很理想;可以通过简单的距离测量对优质的描述子进行比较。

绘制匹配点:drawMatches()函数
原型1

void drawMatches(const Mat& img1, const vector<KeyPoint>& keypoints1, const Mat& img2, const vector<KeyPoint>& keypoints2, const vector<DMatch>& matches1to2, Mat& outImg, const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1), const vector<char>& matchesMask=vector<char>(), int flags=DrawMatchesFlags::DEFAULT)

原型2

void drawMatches(const Mat& img1, const vector<KeyPoint>& keypoints1, const Mat& img2, const vector<KeyPoint>& keypoints2, const vector<vector<DMatch>>& matches1to2, Mat& outImg, const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1), const vector<vector<char>>& matchesMask=vector<vector<char>>(), int flags=DrawMatchesFlags::DEFAULT)

参数1 const Mat&类型的img1,源图像1
参数2 const vector&类型的keypoints1,根据第一幅源图像得到的特征点
参数3 const Mat&类型的img2,源图像2
参数4 const vector&类型的keypoints2,根据第二幅源图像得到的特征点
参数5 matches1to2,源图像1到源图像2的匹配点,即每一个图1中的特征点都在图2中有一一对应的点
参数6 Mat&类型的outImg,输出图像,内容取决于参数5标识符flags
参数7 const Scalar&类型的matchColor,匹配的输出颜色,即线和关键点颜色,有默认值Scalar::all(-1),表示颜色是随机生成的
参数8 const Scalar&类型的singlePointColor,单一特征点的颜色,表示随机生成颜色的默认值Scalar::all(-1)
参数9 matchesMask,确定哪些匹配是会绘制出来的掩膜
参数10 int类型的flags,特征绘制的标识符,默认值DrawMatchesFlags::DEFAULT

BruteForceMatcher类:公共继承自BFMatcher类 > 公共继承自DescriptorMatcher类 > 公共继承自Algorithm基类

例:SURF特征提取核心思想
1、使用DescriptorExtractor接口来寻找关键点对应的特征向量
2、使用SurfDescriptorExtractor以及它的函数compute完成特定计算
3、使用BruteForceMatcher来匹配特征向量
4、使用函数drawMatches来绘制检测到的匹配点

#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 
using namespace cv;
using namespace std;
int main(){
	Mat srcImage1 = imread("1.jpg",1);
	Mat srcImage2 = imread("2.jpg",1);
	//使用SURF算子检测关键点
	int minHessian = 700; //SURF算法中的hessian阈值
	SurfFeatureDetector detector(minHessian); //定义一个SurfFeatureDetector(SURF)特征检测类对象
	std::vector<KeyPoint> keyPoint1, keyPoint2; //vector模板类,存放任意类型动态数组
	//调用detect函数检测出SURF特征关键点,保存在vector容器中
	detector.detect(srcImage1, keyPoint1);
	detector.detect(srcImage2, keyPoint2);
	//计算描述符(特征向量)
	SurfDescriptorExtractor extractor;
	Mat descriptors1, descriptors2;
	extractor.compute(srcImage1, keyPoint1, descriptors1);
	extractor.compute(srcImage2, keyPoint2. descriptors2);
	//使用BruteForce进行匹配
	BruteForceMatcher<L2<float>> matcher;
	std::vector<DMatch> matches;
	matcher.match(descriptors1, descriptors2, matches);
	//绘制从两个图像中匹配出的关键点
	Mat imgMatches;
	drawMatches(srcImage1, keyPoint1, srcImage2, keyPoint2, matches, imgMatches);
}

OpenCV3学习笔记(5)- feature2d组件_第13张图片

5.6 使用FLANN进行特征的匹配

使用FlannBasedMatcher接口以及函数FLANN()实现快速高效匹配(Fast Library for Approximate Nearest Neighbors, FLANN,快速最近邻逼近搜索函数库)
FlannBasedMatcher类:继承自DescriptorMatcher,同样使用来自DescriptorMatcher类的match方法进行匹配
找到最佳匹配:DescriptorMatcher::match()方法
版本1

void DescriptorMatcher::match(const Mat& queryDescriptors, const Mat& trainDescriptors, vector<DMatch>& matches, const Mat& mask=Mat())

版本2

void DescriptorMatcher::match(const Mat& queryDescriptors, vector<DMatch>& matches, const vector<Mat>& masks=vector<Mat>())

参数1 const Mat&类型的queryDescriptors,查询描述符集
(参数2 const Mat&类型的trainDescriptors,训练描述符集)
参数2 vector&类型的matches,得到的匹配。若查询描述符有在掩膜中被标记出来,则没有匹配添加到描述符中,则匹配量可能会比查询描述符数量少
参数3 const vector&类型的masks,一组掩膜

#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 
using namespace cv;
using namespace std;
int main(int argc, char** argv){
	Mat img_1 = imread("1.jpg", 1);
	Mat img_2 = imread("2.jpg", 1);
	//利用SURF检测器检测关键点
	int minHessian = 300;
	SURF detector(minHessian);
	std::vector<KeyPoint> keypoints_1, keypoints_2;
	detector.detect(img_1, keypoints_1);
	detector.detect(img_2, keypoints_2);
	//计算描述符(特征向量)
	SURF extractor;
	Mat descriptors_1, descriptors_2;
	extractor.compute(img_1, keypoints_1, descriptors_1);
	extractor.compute(img_2, keypoints_2, descriptors_2);
	//采用FLANN算法匹配描述符向量
	FlannBasedMatcher matcher;
	std::vector<DMatch> matches;
	matcher.match(descriptors_1, descriptors_2, matches);
	//快速计算关键点之间的最大和最小距离
	double max_dist = 0; double min_dist = 100;
	for(int i = 0; i < descriptors_1.rows; i++){
		double dist = matches[i].distance;
		if (dist < min_dist) min_dist = dist;
		if (dist > max_dist) max_dist = dist;
	}
	//存在符合条件的匹配结果(即距离小于2*min_dist的,使用radiusMatch同样可行
	std::vector<DMatch> good_matches;
	for (int i = 0; i < descriptors_1.rows; i++){
		if(matches[i].distance < 2*min_dist)
			good_matches.push_back(matches[i]);
	}
	//绘制出符合条件的匹配点
	Mat img_matches;
	drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
}

综合示例1:FLANN结合SURF进行关键点的描述和匹配

#include 
#include 
#include 
#include 
using namespace cv;
using namespace std;
int main(){
	Mat trainImage = imread("1.jpg"), trainImage_gray;
	cvtColor(trainImage, trainImage_gray, CV_BGR2GRAY);
	//检测Surf关键点,提取训练图像描述符
	vector<KeyPoint> train_keyPoint;
	Mat trainDescriptor;
	SurfFeatureDetector featureDetector(80);
	featureDetector.detect(trainImage_gray, train_keyPonit);
	surfDescriptorExtractor featureExtractor;
	featureExtractor.compute(trainImage_gray, train_keyPoint, trainDescriptor);
	//创建基于FLANN的描述符匹配对象
	FlannBasedMatcher matcher;
	vector<Mat> train_desc_collection(1, trainDescriptor);
	matcher.add(train_desc_collection);
	matcher.train();

	//创建视频对象,定义帧率
	VideoCapture cap(0);
	unsigned int frameCount = 0; //帧数
	//不断循环,直到q被按下
	while(char(waitKey(1)!='q')){
		int64 time0 = getTickCount();
		Mat testImage, testImage_gray;
		cap >> testImage; //采集视频到testImage中
		if(testImage.empty()) continue;
		cvtColor(testImage, testImage_gray, CV_BGR2GRAY);
		//检测关键点,提取测试图像描述符
		vector<KeyPoint> test_keyPoint;
		Mat testDescriptor;
		featureDetector.detect(testImage_gray, test_keyPoint);
		featureExtractor.compute(testImage_gray, test_keyPoint, testDescriptor);
		//匹配训练和测试描述符
		vector<vector<DMatch>> matches;
		matcher.knnMatch(testDescriptor,matches,2);
		//根据劳氏算法,得到优秀的匹配点
		vector<DMatch> goodMatches;
		for (unsigned int i = 0; i < matchers.size(); i++){
			if(matches[i][0].distance < 0.6*matches[i][1].distance)
				goodMatches.push_back(matches[i][0]);
		}
		//绘制匹配点并显示窗口
		Mat dstImage;
		drawMatches(testImage, test_keyPoint, trainImage, train_keyPoint, goodMatches, dstImage);
		imshow("匹配窗口", dstImage);
		
		//输出帧率
		cout << "当前帧率为:" << getTickFrequency() / (getTickCount() - time0) << endl;
	}	
	return 0;
}

OpenCV3学习笔记(5)- feature2d组件_第14张图片

OpenCV3学习笔记(5)- feature2d组件_第15张图片
综合示例2:SIFT配合暴力匹配进行关键点描述和提取(理论上,SURF是SIFT速度的3倍)
OpenCV3学习笔记(5)- feature2d组件_第16张图片

#include 
#include 
#include 
#include 
using namespace cv;
using namespace std;
int main(){
	Mat trainImage = imread("1.jpg"), trainImage_gray;
	cvtColor(trainImage, trainImage_gray, CV_BGR2GRAY);
	//检测SIFT关键点,提取训练图像描述符
	vector<KeyPoint> train_keyPoint;
	Mat trainDesciption;
	SiftFeatureDetector featureDetector;
	featureExtractor.compute(trainImage_gray, train_keyPoint, trainDescription);
	//进行基于描述符的暴力匹配
	BFMatcher matcher;
	vector<Mat> train_desc_collection(1, trainDescription);
	matcher.add(train_desc_collection);
	matcher.train();
	//创建视频对象、定义帧率
	VideoCapture cap(0);
	unsigned int frameCount = 0; //帧数
	while(char(waitKey(1))!='q'){
		double time0= getTickCount();
		Mat captureImage, captureImage_gray;
		cap >> captureImage; //采集视频到testImage中
		if(captureImage.empty()) continue;
		cvtColor(captureImage, captureImage_gray, CV_BGR2GRAY);
		//检测SURF关键点,提取测试图像描述符
		vector<KeyPoint> test_keyPoint;
		Mat testDescriptor;
		featureDetector.detect(captureImage_gray, test_keyPonit);
		featureExtractor.compute(captureImage_gray, test_keyPoint, testDescriptor);
		//匹配训练和测试描述符
		vector<vector<DMatch>> matches;
		matcher.knnMatch(testDescriptor, matches, 2);
		//根据劳氏算法,得到优秀的匹配点
		vector<DMatch> goodMatches;
		for (unsigned int i = 0; i < matches.size(); i++){
			if (matches[i][0].distance < 0.6*matches[i][1].distance)
				goodMatches.push_back(matches[i][0]);
		}
		Mat dstImage;
		drawMatches(captureImage, test_keyPoint, trainImage, train_keyPoint, goodMatches, dstImage);
		imshow("匹配窗口",dstImage);
		
		//输出帧率
		cout << "当前帧率为:" << getTickFrequency() / (getTickCount() - time0) << endl;
	}
}

可见,代码写法基本相同情况下,此方法进行匹配时的帧率低于SURF算法平均4.0的帧率,算法效率有待加强。
OpenCV3学习笔记(5)- feature2d组件_第17张图片
OpenCV3学习笔记(5)- feature2d组件_第18张图片

5.7 寻找已知物体

在FLANN特征匹配的基础上,可以进一步利用Homography映射找出已知物体。具体说就是利用findHomography函数通过匹配的关键点找出相应的变换,再利用perspectiveTransform函数映射点群。
寻找透视变换:findHomograhpy()函数

Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method = 0, double ransacReprojThreshold = 3, OutputArray mask = noArray())

参数1 InputArray类型的srcPoints,源图像上的对应点,可以是CV_32FC2的矩阵类型或者vector
参数2 InputArray类型的dstPoints,目标平面上的对应点,可以是CV_32FC2的矩阵类型或者vector
参数3 int类型的method,用于计算变换矩阵的方法
OpenCV3学习笔记(5)- feature2d组件_第19张图片
参数4 double类型的ransacReprojThreshold,默认值3,处理点对为内围层时,允许重投影误差的最大值。就是说,当||dstPoints[i] - convertPointsHomogeneous(H*srcPoints[i])|| > ransacReprojThreshold时,这里的点i被看作内围层,此参数范围一般在1~10之间
参数5 OutputArray类型的mask

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

void perspectiveTransform(InputArray src, OutputArray dst, InputArray m)

参数1 InputArray类型的src,源图像,为双通道或三通道浮点型图像,其中每个元素是二维或三维可被转换的矢量
参数2 OutputArray类型的dst,函数调用后的输出结果,需和源图像有一样的尺寸和类型
参数3 InputArray类型的m,变换矩阵,为3×3或4×4浮点型矩阵

//从匹配成功的匹配对中获取关键点
for (unsigned int i=0; i < good_matches.size(); i++){
	obj.push_back(keypoints_object[good_matches[i].queryIdx].pt);
	scene.push_back(keypoints_scene[good_matches[i].trainIdx].pt);
	Mat H = findHomography(obj, scene, CV_RANSAC); //计算透视变换
}
//从待测图片中获取角点

```cpp
vector<Point2f> obj_corners(4);
obj_corners[0] = cvPoint(0,0);
obj_corners[1] = cvPoint(srcImge1.cols, 0);
obj_corners[2] = cvPoint(srcImage1.cols, srcImage1.rows);
obj_corners[3] = cvPoint(0, srcImage1.rows);
vector<Point2f> scene_corners(4);
//进行透视变换
perspectiveTransform(obj_corners, scene_corners, H);
//绘制出角点之间的直线
...

OpenCV3学习笔记(5)- feature2d组件_第20张图片

5.8 ORB特征提取

ORB: ORiented Brief的简称,综合性能在各种测评里相较于其他特征提取算法是最好的
Brief描述子:Binary Robust Independent Elementary Features,思路是在特征点附近随机选取若干点对,将这些点对的灰度值大小组合成一个二进制串,并将这个二进制串作为该特征点的特征描述子。优点:速度;缺点:不具备旋转不变性、对噪声敏感、不具备尺度不变性
尺度不变性:ORB没有试图解决,但这样只求速度的特征描述子,一般应用在实时视频处理中,这样可以通过跟踪还有一些启发式的策略解决尺度不变性问题
计算速度:ORB执行是SIFT的100倍,SURF的10倍
ORB类:等价于OrbFeatureDetector, OrbDescriptorExtractor类
OpenCV3学习笔记(5)- feature2d组件_第21张图片
例:采用摄像头获取待检测图像,使用FLANN-LSH进行匹配

#include 
#include 
#include 
#include 
int main(){
	Mat srcImage = imread("1.jpg");
	Mat grayImage;
	cvtColor(srcImage, grayImage, CV_BGR2GRAY);
	//检测SIFT特征点并在图像中提取物体的描述符
	OrbFeatureDetector featureDetector;
	vector<KeyPoint> keyPoints;
	Mat descriptors;
	//调用detect函数检测出特征关键点,保存在vector容器中
	featureDetector.detect(grayImage, keyPoints);
	//计算描述符(特征向量)
	OrbDescriptorExtractor featureExtractor;
	featureExtractor.compute(grayImage, keyPoints, descriptors);
	//基于FLANN的描述符对象匹配
	flann::Index flannIndex(descriptors, flann::LshIndexParams(12,20,2), cvflann::FLANN_DIST_HAMMING);
	//初始化视频采集对象
	VideoCapture cpa(0);
	cap.set(CV_CAP_PROP_FRAME_WIDTH, 360); //设置采集视频的宽度
	cap.set(CV_CAP_PROP_FRAME_HEIGHT, 900); //设置采集hi品的高度
	unsigned int frameCount = 0; //帧数
	//轮询,直到按下ESC键退出循环
	while(1){
		double time0 = static_cast<double>(getTickCount()); //记录起始时间
		Mat captureImage, captureImage_gray; //用于视频采集
		cap >> captureImage; //采集视频帧
		if(captureImage.empty()) continue;  //采集为空的处理
		cvtColor(captureImage, captureImage_gray, CV_BGR2GRAY); //采集的视频帧转化为灰度图
		//检测SIFT关键点并提取测试图像的描述符
		vector<KeyPoint> captureKeyPoints;
		Mat captureDescription;
		//调用detect函数检测特征关键点,保存在vector容器中
		featureDetector.detect(captureImage_gray, captureKeyPoints);
		//计算描述符
		featureExtractor.compute(captureImage_gray, captureKeyPoints, captureDescription);
		//匹配和测试描述符,获取两个最邻近的描述符
		Mat matchIndex(captureDescription.rows, 2, CV_32SC1), matchDistance(captureDescription.rows, 2, CV_32FC1);
		flannIndex.knnSearch(captureDescription, matchIndex, matchDistance, 2, flann::SearchParams()); //调用K近邻算法
		//根据劳氏算法选出优秀的匹配
		vector<DMatch> goodMatches;
		for (int i = 0; i < matchDistance.rows; i++){
			if(matchDistance.at<float>(i,0) < 0.6 * matchDistance.at<float>(i,0)){
				DMatch dmatches(i, matchIndex.at<int>(i,0), matchDistance.at<float>(i,0));
				goodMatches.push_back(dmatches);
			}
		}
		//绘制并显示匹配窗口
		Mat resultImage;
		drawMatches(captureImage, captureKeyPoints, srcImage, keyPoins, goodMatches, resultImage);
	}
}

OpenCV3学习笔记(5)- feature2d组件_第22张图片

你可能感兴趣的:(C++,笔记,学习,计算机视觉,算法)