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

《Opencv3编程入门》学习笔记

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

第十一章 特征检测与匹配

一、SURF特征点检测

太复杂了!全是公式!

(一)SURF算法概览

  • SURF,SpeededUp Rebust Features,加速版的具有鲁棒性的特征算法,是尺度不变特征变换算法(SIFT)的加速版
  • 特点:采用了haar特征以及积分图像的概念,加快了运行时间
  • 应用:计算机视觉的物体识别以及3D重构

(二)SURF算法原理

1、构建Hessian矩阵构造高斯金字塔尺度空间

(1)Hessian matrix:
一个自变量为向量的实值函数的二阶偏导数组成的方框矩阵,假设函数f(z,y),Hessian矩阵H,图像中某个像素点的Hessian矩阵如下:
在这里插入图片描述                  
即每一个像素点都可以求出一个Hessian矩阵
H矩阵判别式为:
在这里插入图片描述
                  
判别式的值是H矩阵的特征值,可以利用判定结果的符号将所有点分类,根据判别式取值正负,来判别该点是或不是极值点。

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

用图像像素l(x,y)作为函数值f(x,y),选用二阶标准高斯函数作为滤波器,通过特定核间的卷积计算二阶偏导数,计算出H矩阵:
在这里插入图片描述
                 
由于特征点需要具备尺度无关性,所以在进行H矩阵构造前需要进行高斯滤波,滤波后在进行H计算:
在这里插入图片描述
             
L(x,t)是一幅图像在不同解析度下的表示,可以利用高斯核G(t)与图像函数I(x)在点x的卷积实现,其中高斯核G(t)计算公式为:
在这里插入图片描述
                      
其中,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少了一半,会加快匹配速度。
在这里插入图片描述

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类关系图:
在这里插入图片描述

(四)绘制关键点: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 <iostream>//-----------------------------------【命名空间声明部分】--------------------------------------
//          描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
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 <opencv2\nonfree\nonfree.hpp>
#include <opencv2\legacy\legacy.hpp> 
#include <iostream>
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<SurfFeatureDetector> 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<SurfDescriptorExtractor> 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()函数从每个描述符查询集中找到最佳匹配,有两个版本的源码,下面用注释对其进行讲解。
在这里插入图片描述

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

示例代码

#include<opencv2\features2d\features2d.hpp>
#include<opencv2\core\core.hpp>
#include<highgui\highgui.hpp>
#include<opencv2\nonfree\nonfree.hpp>
#include<opencv2\legacy\legacy.hpp>
#include<iostream>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<opencv2\features2d\features2d.hpp>
#include<opencv2\core\core.hpp>
#include<highgui\highgui.hpp>
#include<opencv2\nonfree\nonfree.hpp>
#include<opencv2\legacy\legacy.hpp>
#include<iostream>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 = 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);c//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;
}

运行效果
在这里插入图片描述
在这里插入图片描述

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

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

#include<opencv2\features2d\features2d.hpp>
#include<opencv2\core\core.hpp>
#include<highgui\highgui.hpp>
#include<opencv2\nonfree\nonfree.hpp>
#include<opencv2\legacy\legacy.hpp>
#include<iostream>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<Mat>的容器,所以先定义一个这样的变量,并将原图的描述子放入容器中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<opencv2\features2d\features2d.hpp>
#include<opencv2\core\core.hpp>
#include<highgui\highgui.hpp>
#include<opencv2\nonfree\nonfree.hpp>
#include<opencv2\legacy\legacy.hpp>
#include<iostream>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<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;
}

运行效果
在这里插入图片描述

在这里插入图片描述

五、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{//......
}

类关系图
在这里插入图片描述

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

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

//------------------【ORB】--------------------------#include<opencv2/opencv.hpp>
#include<iostream>
#include<vector>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编程入门》学习笔记到这里就结束啦!完结撒花
在这里插入图片描述
静候下一个学习系列吧!喜欢的可以关注伍六琪,持续更新包括但不限于以下内容:计算机视觉,图像处理,深度学习,计算机相关小技巧,各种软件安装包插件使用教程破解教程…
大家一起共同进步,共同学习吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/4852.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无广告 齐全 简洁 免费的音乐开源软件(支持 电脑max win linux 手机 )——lxMusic

无广告 齐全 简洁 免费的音乐开源软件&#xff08;支持 电脑max win linux 手机 &#xff09;——lxMusic 话不多说先上效果 &#xff08;真香&#xff09; 下载地址&#xff08;官方&#xff09; https://www.lanzoui.com/b0bf2cfa/ 密码&#xff1a;glqw 软件安装包说明 文…

对 tcp out-of-window 的安全建议

TCP 收到一个 out of window 报文后会立即回复一个 ack&#xff0c;这是 RFC793 中 SEGMENT ARRIVES 段的要求。但这是为什么&#xff1f;难道不是默默丢弃才对吗&#xff1f; 对 oow 报文回复 ack&#xff0c;岂不是把正确的 ack 号回过去了吗&#xff0c;这样攻击者盲打一番…

Qt在Ubuntu下如何进行桌面软件开发?

文章目录 0.引言1.新建项目2.编写第一个程序3.在Qt外部启动程序 0.引言 笔者研究的方向涉及在ubuntu中运行代码&#xff0c;早先是直接利用控制台运行代码文件&#xff0c;在控制台中虽然设法将代码精简到一个三个文件中&#xff0c;只需要在控制台运行这三个文件即可&#xff…

MySQL数据库增删改查及聚合查询SQL语句学习汇总

目录 数据库增删改查SQL语句 MySQL数据库指令 1.查询数据库 2.创建数据库 3.删除数据库 4.选择数据库 创建表table 查看所有表 创建表 查看指定表的结构 删除表 数据库命令进行注释 增删改查&#xff08;CRUD&#xff09;详细说明 增加 SQL库提供了关于时间的…

搞芯片怎么能不懂perl?

各位ICer在工作的过程中&#xff0c;无论是前端还是后端&#xff0c;都会使用各种常见的脚本语言&#xff0c;比如&#xff1a;shell&#xff0c;python&#xff0c;perl&#xff0c;tcl等等用于文件的处理&#xff0c;case测试&#xff0c;工具环境的调用和搭建&#xff0c;虽…

【压缩空气储能】非补燃压缩空气储能系统集成的零碳排放综合能源优化调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MySQL环境搭建(Windows电脑)

MySQL环境搭建-Windows电脑篇 软件获取&#xff1a; 搜索gzh【李桥桉】&#xff0c;需要win电脑安装包&#xff0c;回复【win-MS】。 搜索gzh【李桥桉】&#xff0c;需要mac电脑安装包&#xff0c;回复【mac-MS】。 注意&#xff1a;确保电脑为64位系统&#xff08;不是的话需要…

SpringSecurity(五):前后端分离认证总结案例。

前后端分离认证总结案例 前言难点分析Controller层eneity层RoleUser dao层service层config层LoginFilterSecurityConfig resourcesmapper propertiespom.xml结尾 前言 和上一篇一样&#xff0c;从上倒下复制粘贴&#xff0c;所有代码贴完再运行&#xff0c;代码没有问题&#…

Qt QLineEdit篇

QLineEdit篇 【1】QLineEdit简介【2】QLineEdit常用方法【3】QLineEdit使用举例UI设计界面效果头文件源文件 PC饱和了&#xff0c;跟我学Qt比较实在&#xff0c;哈哈哈 【1】QLineEdit简介 QLineEdit是Qt框架中的一个类&#xff0c;用于创建一个文本输入框&#xff0c;允许用…

Google 将为高端 Chromebook 推出独立品牌

说起 Chromebook&#xff0c;一般大家的第一印象就是价格便宜、配置不高、做工普通&#xff0c;所选的材料也都是以塑料为主&#xff0c;产品主打的市场也是学生和教育群体。在不少人看来&#xff0c;Chromebook 就是一个配备了功能齐全的浏览器&#xff0c;外加一定的文件管理…

react antd 样式修改

最近在做一个大数据的大屏ui更改&#xff0c;使用的是antd&#xff0c;需要根据ui稿调很多的antd组件样式 特做一个样式修改记录&#xff0c;也给需要的人一些帮助 我们修改的有以下样式&#xff1a; 如何改呢&#xff1a; /*修改 antd 组件样式 */// 仅 drop 下的下拉框改变样…

苹果手机ios设备管理软件iMazing 2.17.6官方版下载及常见问题解决

苹果手机ios设备管理软件iMazing 2.17.6官方版下载(ios设备管理软件)是一款管理苹果设备的软件&#xff0c; Windows / macos 系统上的一款帮助用户管理 IOS 手机的应用程序&#xff0c;软件功能非常强大&#xff0c;界面简洁明晰、操作方便快捷&#xff0c;设计得非常人性化。…