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