《Opencv3编程入门》学习笔记—第十一章

《Opencv3编程入门》学习笔记

记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。

第十一章 特征检测与匹配

一、SURF特征点检测

太复杂了!全是公式!

(一)SURF算法概览
  • SURF,SpeededUp Rebust Features,加速版的具有鲁棒性的特征算法,是尺度不变特征变换算法(SIFT)的加速版
  • 特点:采用了haar特征以及积分图像的概念,加快了运行时间
  • 应用:计算机视觉的物体识别以及3D重构
(二)SURF算法原理
1、构建Hessian矩阵构造高斯金字塔尺度空间

(1)Hessian matrix:
一个自变量为向量的实值函数的二阶偏导数组成的方框矩阵,假设函数f(z,y),Hessian矩阵H,图像中某个像素点的Hessian矩阵如下:
《Opencv3编程入门》学习笔记—第十一章_第1张图片                  
即每一个像素点都可以求出一个Hessian矩阵
H矩阵判别式为:
《Opencv3编程入门》学习笔记—第十一章_第2张图片
                  
判别式的值是H矩阵的特征值,可以利用判定结果的符号将所有点分类,根据判别式取值正负,来判别该点是或不是极值点。

(2)SURF算法中H矩阵的计算

用图像像素l(x,y)作为函数值f(x,y),选用二阶标准高斯函数作为滤波器,通过特定核间的卷积计算二阶偏导数,计算出H矩阵:
《Opencv3编程入门》学习笔记—第十一章_第3张图片
                 
由于特征点需要具备尺度无关性,所以在进行H矩阵构造前需要进行高斯滤波,滤波后在进行H计算:
在这里插入图片描述
             
L(x,t)是一幅图像在不同解析度下的表示,可以利用高斯核G(t)与图像函数I(x)在点x的卷积实现,其中高斯核G(t)计算公式为:
《Opencv3编程入门》学习笔记—第十一章_第4张图片
                      
其中,g(x)为高斯函数,t为高斯方差。

Herbert Bay提出用近似值替代L(x,t),为平衡准确值与近似值间的误差引入的权值,权值随尺度变化,H矩阵判别式:
在这里插入图片描述

(3)SURF的金字塔
  金字塔图像分很多层,每一层叫做一个octave,每一个octave有几张不同尺度图片。
  Sift算法中,同一个octave层图片尺寸(大小)相同,但尺度(模糊程度)不同,高斯模糊时,sift的高斯模板大小不变,只在不同octave之间改变图片大小
  Surf算法中,图片大小一直不变,同一层octave中不同图片高斯模板尺度不同,不同octave层图片改变高斯模糊尺寸
传统金字塔图片尺寸变化,且反复利用高斯函数对子层进行平滑,而surf算法保持原图像不变只改变滤波器大小,节省了降采样过程,提升了处理速度。

2、利用非极大值抑制初步确定特征点

将经过hessian矩阵处理过的每个像素点与其三维领域的26个点进行大小比较,如果是26个点中的最大/小值,则保留作为初步特征点。
  检测过程中,使用与该尺度层图像解析度对应大小的滤波器进行检测,以3*3滤波器为例,该尺度层图像9个像素点之一的检测特征点与自身尺度层中其余8个点和其上下尺度层中各9个点进行比较。

3、精确定位极值点

采用三维线性插值法得到亚像素级特征点,同时去掉值小于一定阈值的点,筛选出特征较强点。

4、选取特征点的主方向

(1)Sift选取特征点主方向是采用在特征点领域内统计其梯度直方图,取直方图bin值最大的及超过最大bin值80%的方向作为特征点主方向。
(2)Surf中,不统计梯度直方图,而是统计特征点领域内的haar小波特征。即在特征点领域内(如,半径为6s的圆,s为该点所在尺度),统计60度扇形内所有点的水平haar小波特征和垂直haar小波特征总和,haar小波尺寸边长为4s,得到扇形值,然后60度扇形以一定间隔进行旋转,最后将最大值那个扇形的方向作为该特征点主方向。

5、构造surf特征点描述算子

(1)Sift中,在特征点周围取1616领域,并把该领域化为44个小区域,每个小区域统计8个方向梯度,最后得到448=128维向量,该向量作为该点的sift描述子。
(2)Surf中,在特征点周围取一个正方形框,框边长为20s,该框方向即为第四步检测的主方向。然后把框分为16个子区域,每个子区域统计25个像素的水平方向和垂直方向(相对于主方向)的haar小波特征,该小波特征为水平方向值之和,水平方向绝对值之和,垂直方向之和,垂直方向绝对值之和。每个小区域有4个值,所以每个特征点就是16*4=64维向量,相比sift少了一半,会加快匹配速度。
《Opencv3编程入门》学习笔记—第十一章_第5张图片

6、总结

Surf采用hessian矩阵获取图像局部最值稳定,但求主方向阶段太过于依赖局部区域像素的梯度方向,可能主方向不准确,从而导致后面特征点提取及匹配误差。同时金字塔层不够紧密会使尺度有误差影响特征向量提取,所以应取适量层后进行插值。

(三)SURF类相关OpenCV源码剖析

在opencv安装路径下…\opencv\sources\modules\nonfree\include\opencv2\nonfree下的features2d.hpp头文件中,我们可以发现这样两句定义:
(这里不一一解读了,感兴趣的自行查看源码)

typedef SURF SurfFeatureDetector;
typedef SURF SurfDescriptorExtractor;

我们平常使用的SurfFeatureDetector类和SurfDescriptorExtractor类,其实就是SURF类,他们三者等价。

SURF类关系图:
《Opencv3编程入门》学习笔记—第十一章_第6张图片

(四)绘制关键点:drawKeypoints()函数

用于绘制关键点。

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

第一个参数,const Mat&类型的src,输入图像。
第二个参数,const vector&类型的keypoints,根据源图像得到的特征点,它是一个输出参数。
第三个参数,Mat&类型的outImage,输出图像,其内容取决于第五个参数标识符falgs。
第四个参数,const Scalar&类型的color,关键点的颜色,有默认值Scalar::all(-1)。
第五个参数,int类型的flags,绘制关键点的特征标识符,有默认值DrawMatchesFlags::DEFAULT。可以在如下这个结构体中选取值。

struct DrawMatchesFlags
{
    enum
    {
        DEFAULT = 0, // Output image matrix will be created (Mat::create),
                     // i.e. existing memory of output image may be reused.
                     // Two source images, matches, and single keypoints
                     // will be drawn.
                     // For each keypoint, only the center point will be
                     // drawn (without a circle around the keypoint with the
                     // keypoint size and orientation).
        DRAW_OVER_OUTIMG = 1, // Output image matrix will not be
                       // created (using 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.
    };
};
(五)KeyPoint类

KeyPoint类是一个为特征点检测而生的数据结构,用于表示特征点

class KeyPoint
{
	Point2f pt;   //坐标
	float size;    //特征点领域直径
	float angle;  //特征点方向,值为[0,360),负值表示不使用
	float response;
	int octave;   //特征点所在图像金字塔的组
	int class_id;  //用于聚类的id
}
(六)示例程序:SURF特征点检测

步骤
1、使用FeatureDetector接口来发现感兴趣点
2、使用SurfFeatureDetector以及其函数detect来实现检测过程
3、使用函数drawKeypoints绘制检测到的关键点

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//		【SURF特征点检测】

//----------------------------------------------------------------------------------------------

//-----------------------------------【头文件包含部分】---------------------------------------
//		描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include 


//-----------------------------------【命名空间声明部分】--------------------------------------
//          描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;

//-----------------------------------【全局函数声明部分】--------------------------------------
//          描述:全局函数的声明
//-----------------------------------------------------------------------------------------------
static void ShowHelpText( );//输出帮助文字

//-----------------------------------【main( )函数】--------------------------------------------
//   描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
	//【0】改变console字体颜色    
	system("color 2F");    

	//【0】显示帮助文字  
	ShowHelpText( );  

	//【1】载入源图片并显示
	Mat srcImage1 = imread("D://lili/Desktop/jpg/opencv/4.jpg", 1 );
	Mat srcImage2 = imread("D://lili/Desktop/jpg/opencv/5.jpg", 1 );
	if( !srcImage1.data || !srcImage2.data )//检测是否读取成功
	{ printf("读取图片错误,请确定目录下是否有imread函数指定名称的图片存在~! \n"); return false; } 
	imshow("原始图1",srcImage1);
	imshow("原始图2",srcImage2);

	//【2】定义需要用到的变量和类
	int minHessian = 400;//定义SURF中的hessian阈值特征点检测算子
	SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF) 特征检测类对象
	std::vector<KeyPoint> keypoints_1, keypoints_2;//vector模板类是能够存放任意类型的动态数组,能够增加和压缩数据

	//【3】调用detect函数检测出SURF特征关键点,保存在vector容器中
	detector.detect( srcImage1, keypoints_1 );
	detector.detect( srcImage2, keypoints_2 );

	//【4】绘制特征关键点
	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】显示效果图
	imshow("特征点检测效果图1", img_keypoints_1 );
	imshow("特征点检测效果图2", img_keypoints_2 );

	waitKey(0);
	return 0;
}


//-----------------------------------【ShowHelpText( )函数】----------------------------------
//          描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{ 
	//输出一些帮助信息  
	printf("\n\n\n\t欢迎来到【SURF特征点检测】示例程序~\n\n");    
	printf("\t当前使用的OpenCV版本为 OpenCV "CV_VERSION);  
	printf( "\n\n\t按键操作说明: \n\n"     
				"\t\t键盘按键任意键- 退出程序\n\n"
				"\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n");  

}

运行效果


二、SURF特征提取

在SURF进行特征点描述主要是drawMatches方法和BruteForceMatcher类的运用。

(一)绘制匹配点:drawMatches()函数

用于绘制出相匹配的两个图像的关键点

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

第一个参数是第一个源图像,
第二个参数是第一个源图像的关键点,
第三个参数是第二个源图像,
第四个参数是第二个源图像的关键点,
第五个参数是从第一张图像匹配到第二张图像,
第六个参数是输出图像。它的内容取决于定义在输出图像中绘制的内容的标志值,
第七个参数是匹配的颜色(线和连接的关键点),
第八个参数是单个关键点(圆圈)的颜色,表示关键点不匹配,
第九个参数是确定绘制哪些匹配项的掩码。如果掩码为空,则绘制所有匹配项。
第十个参数是标志设置绘图功能。可以在如下DrawMatchesFlags结构体中选取值

struct DrawMatchesFlags
{
    enum
    {
        DEFAULT = 0, // Output image matrix will be created (Mat::create),
                     // i.e. existing memory of output image may be reused.
                     // Two source images, matches, and single keypoints
                     // will be drawn.
                     // For each keypoint, only the center point will be
                     // drawn (without a circle around the keypoint with the
                     // keypoint size and orientation).
        DRAW_OVER_OUTIMG = 1, // Output image matrix will not be
                       // created (using 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.
    };
};
(二)BruteForceMatcher类源码分析

在…\opencv\sources\modules\legacy\include\opencv2\legacy\legacy.hpp路径下(感兴趣的自行查看)

(三)示例程序:SURF特征提取

这个示例程序中,我们利用SurfDescriptorExtractor类进行特征向量的相关计算。

程序利用了SURF特征的特征描述办法,其操作封装在类SurfFeatureDetector中,利用类内的detect函数可以检测出SURF特征的关键点,保存在vector容器中。第二步利用SurfDescriptorExtractor类进行特征向量的相关计算。将之前的vector变量变成向量矩阵形式保存在Mat中。最后强行匹配两幅图像的特征向量,利用了类BruteForceMatcher中的函数match。

程序的核心思想是:

  • 使用 DescriptorExtractor 接口来寻找关键点对应的特征向量。
  • 使用 SurfDescriptorExtractor 以及它的函数 compute 来完成特定的计算。
  • 使用 BruteForceMatcher 来匹配特征向量。
  • 使用函数 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;

//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main(  )
{
    //【1】载入素材图
    Mat srcImage1 = imread("D://lili/Desktop/jpg/opencv/2.jpg",1);
    Mat srcImage2 = imread("D://lili/Desktop/jpg/opencv/16.jpg",1);
    if( !srcImage1.data || !srcImage2.data )
    { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }
    imshow("原始图1", srcImage1);
    imshow("原始图2", srcImage2);

    //【2】使用SURF算子检测关键点
    int minHessian = 100;//SURF算法中的hessian阈值
    SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF) 特征检测类对象
    //Ptr detector=SurfFeatureDetector::create( minHessian );//定义一个SurfFeatureDetector(SURF) 特征检测类对象
    std::vector<KeyPoint> keyPoint1, keyPoints2;//vector模板类,存放任意类型的动态数组

    //【3】调用detect函数检测出SURF特征关键点,保存在vector容器中
    detector.detect( srcImage1, keyPoint1 );
    detector.detect( srcImage2, keyPoints2 );

    //【4】计算描述符(特征向量)
	SurfDescriptorExtractor extractor;
    //Ptr extractor=SurfDescriptorExtractor::create();
    Mat descriptors1, descriptors2;
    extractor.compute( srcImage1, keyPoint1, descriptors1 );
    extractor.compute( srcImage2, keyPoints2, descriptors2 );

    //【5】使用BruteForce进行匹配
    // 实例化一个匹配器
    BFMatcher matcher(NORM_L1);
    std::vector< DMatch > matches;
    //匹配两幅图中的描述子(descriptors)
    matcher.match( descriptors1, descriptors2, matches );

    //【6】绘制从两个图像中匹配出的关键点
    Mat imgMatches;
    drawMatches( srcImage1, keyPoint1, srcImage2, keyPoints2, matches, imgMatches );//进行绘制

    //【7】显示效果图
    imshow("匹配图", imgMatches );

    waitKey(0);
    return 0;
}

运行效果

三、使用FLANN进行特征点匹配

使用FlannBasedMatcher接口以及函数FLANN(),实现快速高效匹配(FLANN)

(一)FlannBasedMatcher类的简单分析
class CV_EXPORTS_W FlannBasedMatcher : public DescriptorMatcher{
	//......
}

发现:FlannBasedMatcher也是继承自DescriptorMatcher,并且同样主要使用来自DescriptorMatcher类的match方法进行匹配。

(二)找到最佳匹配:DescriptorMatcher::match方法

DescriptorMatcher:.match()函数从每个描述符查询集中找到最佳匹配,有两个版本的源码,下面用注释对其进行讲解。
《Opencv3编程入门》学习笔记—第十一章_第7张图片

(三)示例程序:使用FLANN进行特征点匹配

示例代码

#include
#include
#include
#include
#include
#include

using namespace cv;
using namespace std;
 
//FLANN对高维数据较快
int main(int argc,char** argv)
{
	//【1】载入源图片
	Mat img_1, img_2;
	img_1 = imread("D://lili/Desktop/jpg/opencv/2.jpg");
	img_2 = imread("D://lili/Desktop/jpg/opencv/16.jpg");
	if (img_1.empty() || img_2.empty()){printf("加载图片失败\n");return -1;}
 
	//【2】利用SURF检测器检测的关键点
	int minHessian = 400;

	SURF detector(minHessian);
	std::vector<KeyPoint>keypoints_1,keypoints_2;
	Mat descriptor1, descriptor2;
	//检测关键点并计算描述符
	detector.detect(img_1,keypoints_1);
	detector.detect(img_2,keypoints_2);
 
	//【3】计算描述符(特征向量)
	SURF extractor;
	Mat descriptors_1,descriptors_2;
	extractor.compute(img_1,keypoints_1,descriptors_1);
	extractor.compute(img_2,keypoints_2,descriptors_2);

	//【4】采用FLANN算法匹配描述符向量
	FlannBasedMatcher matcher;
	std::vector<DMatch> matches;
	matcher.match(descriptors_1,descriptors_2,matches);
	double max_dist = 0;double min_dist = 100;

	//【5】快速计算关键点之间的最大和最小距离
	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;
	}
	//输出距离信息
	printf(">最大距离(Max dist): %f \n",max_dist);
	printf(">最小距离(Min dist): %f \n",min_dist);

	//【6】存下符合条件的匹配结果(即其距离小于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]);
		}
	}

	//【7】绘制出符合条件的匹配点
	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);

	//【8】输出相关匹配点信息
	for(int i = 0;i < good_matches.size();i++){
		printf(">符合条件的匹配点 [%d] 特征点1:%d -- 特征点2: %d \n",i,good_matches[i].queryIdx,good_matches[i].trainIdx);
	}

	//【9】显示效果图
	imshow("匹配效果图",img_matches);

	waitKey(0);
	return 0;
}

运行效果

(四)综合示例程序:FLANN结合SURF进行关键点的描述和匹配

【程序运行出现错误,代码不太理解,有待进一步学习】
【需要调用摄像头,暂时无法实现】
示例代码

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

using namespace cv;
using namespace std;
 
int main()
{
	//【1】载入原图
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/2.jpg");
	imshow("【原图】", srcImage);     
 
	//【2】对BGR空间的图像直接进行计算很费时间,所以,需要转换为灰度图
	Mat srcGrayImage;
	cvtColor(srcImage, srcGrayImage, CV_BGR2GRAY);
 
	//【3】首先对两幅图像检测SURF关键点、提取测试图像描述符
	vector<KeyPoint> keyPoint1;
	Mat dstImage1, dstImage2;
	int minHessian = 80;
	SURF surf(minHessian);
	//Ptr surf = SURF::create(80);
	surf.detect(srcGrayImage, keyPoint1);
	Mat descriImage1;
	surf.compute(srcGrayImage, keyPoint1, descriImage1);
 
	//【4】先对原图的描述子进行保留-------邻近匹配
	//FlannBasedMatcher FLMatcher;
	//因为FlannBasedMatcher类的成员函数add()的参数是一个vector的容器,所以先定义一个这样的变量,并将原图的描述子放入容器中
	//vector g_vdescriImage1(1, descriImage1);
	//调用FlannBasedMatcher类的成员函数add,将原图的描述子放在FlannBasedMatcher的对象FLMatcher中
	//FLMatcher.add(g_vdescriImage1);c
	//FLMatcher.train();
 
	//【4】进行基于描述符的-------暴力匹配
	BFMatcher matcher;
	//因为FlannBasedMatcher类的成员函数add()的参数是一个vector的容器,所以先定义一个这样的变量,并将原图的描述子放入容器中
	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;
}

运行效果

(五)综合示例程序:SIFT配合暴力匹配进行关键点描述和提取

【需要调用摄像头,暂时无法实现】
示例代码

#include
#include
#include
#include
#include
#include

using namespace cv;
using namespace std;
 
int main()
{
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/2.jpg");
	imshow("【原图】", srcImage);
 
	//对BGR空间的图像直接进行计算很费时间,所以,需要转换为灰度图
	Mat srcGrayImage;
	cvtColor(srcImage, srcGrayImage, CV_BGR2GRAY);
 
	//首先对两幅图像进行特征点的检测和描述子的计算
	vector<KeyPoint> keyPoint1;
	//这里用SURF会更加快
	SIFT surf(2000);
	surf.detect(srcGrayImage, keyPoint1);
	Mat descriImage1;
	surf.compute(srcGrayImage, keyPoint1, descriImage1);
 
	//先对原图的描述子进行保留
	BFMatcher FLMatcher;
	//因为FlannBasedMatcher类的成员函数add()的参数是一个vector的容器,所以先定义一个这样的变量,并将原图的描述子放入容器中
	vector<Mat> g_vdescriImage1(1, descriImage1);
	/*g_vdescriImage1.push_back(descriImage1);*/
	//调用FlannBasedMatcher类的成员函数add,将原图的描述子放在FlannBasedMatcher的对象FLMatcher中
	FLMatcher.add(g_vdescriImage1);
	//...........................................................
	FLMatcher.train();
 
	VideoCapture capture;
	capture.open(0);
 
	Mat frameImage, frameGrayImage;
	while (waitKey(1) != 27)
	{
		capture >> frameImage;
 
		//为了提高计算效率,将图像转换为灰度图像
		cvtColor(frameImage, frameGrayImage, CV_BGR2GRAY);
 
		//计算特征点和描述子
		vector<KeyPoint> keyPoints2;
		surf.detect(frameGrayImage, keyPoints2);
		Mat descriImage2;
		surf.compute(frameGrayImage, keyPoints2, descriImage2);
 
		//将之前得到的原图的描述子和现在得到的描述子进行匹配
		//成员函数knnMatch的参数是二维的DMatch向量,所以首先定义一个该容器的向量
		vector<vector<DMatch>> knnDMatches;
		//用之前已经存放原图描述子的对象来计算
		FLMatcher.knnMatch(descriImage2, knnDMatches, 2);
 
		//采集优秀的匹配点
		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]);
			}
		}
 
		Mat dstImage;
		drawMatches(frameImage, keyPoints2, srcImage, keyPoint1, goodMatches, dstImage);
 
		imshow("【结果图】", dstImage);
	}
 
	return 0;
}

运行效果

四、寻找已知物体

寻找已知物体:在Flann特征匹配的基础上,还可以进一步利用Homography映射找出已知物体。具体分为两个步骤
(1)使用函数findHomography寻找匹配上的关键点的变换
(2)使用函数perspectiveTransform来映射点

(一)寻找透视变换:findHomography()函数

寻找透视变换: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()函数

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

void perspectiveTransform(
    InputArray src,   //输入图像
    OutputArray dst,  //目标图像
    InputArray m     //变换矩阵
);
(三)示例程序:寻找已知物体

示例代码

//-------------------【寻找已知物体】---------------
#include
#include
#include
#include
#include
#include

using namespace cv;
using namespace std;
 
int main()
{
	Mat srcImage1 = imread("D://lili/Desktop/jpg/opencv/2.jpg");
	Mat srcImage2 = imread("D://lili/Desktop/jpg/opencv/16.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;
	int minHessian = 400;
	SurfFeatureDetector surf(minHessian);
	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 g_vCorners1(4);
	//vector 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;
}

运行效果

五、ORB特征提取

(一)ORB算法概述

ORB是brief算法的改进版。ORB算法综合性能在各种测评里相较于其他特征提取算法是最好的。

(二)相关概念认知
1、关于Brief描述子

主要思路:在特征点附近随机选取若干点对,将这些点对的灰度值的大小,组合成一个二进制串,并将这个二进制串作为该特征点的特征描述子
Brief优点在于速度,缺点在于:不具备旋转不变性;对噪声敏感;不具备尺寸不变性。
为了解决上述缺点中的1和2提出了一种新概念:ORB算法。

2、关于尺寸不变性

ORB没有试图解决尺寸不变性,一般应用在实时视频处理中,可以通过跟踪还有一些启发式的策略来解决尺寸不变性的问题。

3、关于计算速度

经统计,ORB算法的执行速度是SIFT的100倍,是SURF的10倍

(三)ORB类相关源码简单分析

源码路径:…\opencv\buld\include\opencv2\features2d\features2d.hpp
(感兴趣的自行查看)
可以发现ORB,OrbFeatureDetector,OrbDescriptorExtractor这三个类是完全等价的。而且ORB类同样继承自Feature2D类

class CV_EXPORTS_W ORB : public Feature2D{
	//......
}

类关系图
《Opencv3编程入门》学习笔记—第十一章_第8张图片

(四)示例程序:ORB算法描述与匹配

ORB的关键点和描述符的提取,采用摄像头获取待检测图像,使用FLANN-LSH进行匹配。
【需要调用摄像头,暂时无法实现】
示例代码

//------------------【ORB】--------------------------
 
#include
#include
#include
 
using namespace cv;
using namespace std;
 
int main()
{
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/2.jpg");
	imshow("【原图】", srcImage);
 
	//对BGR空间的图像直接进行计算很费时间,所以,需要转换为灰度图
	Mat srcGrayImage;
	cvtColor(srcImage, srcGrayImage, CV_BGR2GRAY);
 
	//首先对两幅图像进行特征点的检测和描述子的计算
	vector<KeyPoint> keyPoint1;
	int minHessian = 400;
	OrbFeatureDetector orb(minHessian);

	//调用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;
}

运行效果
【没有摄像头看不了效果】
书上运行效果

《Opencv3编程入门》学习笔记到这里就结束啦!完结撒花
《Opencv3编程入门》学习笔记—第十一章_第9张图片
静候下一个学习系列吧!喜欢的可以关注伍六琪,持续更新包括但不限于以下内容:计算机视觉,图像处理,深度学习,计算机相关小技巧,各种软件安装包插件使用教程破解教程…
大家一起共同进步,共同学习吧!

你可能感兴趣的:(计算机视觉,计算机视觉,opencv,c++,图像处理)