特征点的检测和匹配是计算机视觉中最用要的技术之一。在物体检测,视觉跟踪,三维重建等领域广泛使用。
OpenCV的10种特征检测方法:
FAST , STAR, SIFT, SURF, ORB, MSER, GFTT, HARRIS, Dense, SimpleBlob
其中最常用的是SIFT,SURF,ORB.
在OpenCV中关于SURF算法的部分,常与SURF,SurfFeatureDector,SurfDescriptorExtractor这三个类有关。
从解压完成后的opencv的源码目录下,可以找到存放不同特征检测算子所依赖的特征检测和匹配相关的俄核心代码 cat ~/Documents/test_code/opencv-3.4.15/modules/features2d/include/opencv2/features2d.hpp
由于我opencv的版本是3.4.15的,查看features2.hpp没有发现SURF相关的类,所以本节就对和SURF相似的SIFT作解释。
features2d.hpp头文件中,有这么两句定义:
这两句表明SIFT类有两个新别名:SiftFeatureDetector和SiftDescriptorExtractor,三者等价。
通过看源码,可以看到这两句的上方是SIFT类的全貌。
由此可以看到SIFT,SiftFeatureDector, SiftDescriptorExtractor(三者typedef)都继承自Feature2D,而Feature2D,FeatureDetector, DescriptorExtractor(三者typdef)类都虚继承自Alorithm类。
绘制关键点函数:
函数原型:void drawKeypoints(const Mat& image, const vector
;
struct DrawMatchesFlags
{
enum
{
DEFAULT = 0, //创建图像矩阵(使用Mat::create()),使用现存的输出图像来绘制关键点和匹配点。对每一个关键点只绘制中间点
DRAW_OVER_OUTIMG = 1, //不创建输出图像矩阵,而是在输出图像上绘制匹配对
NOT_DRAW_SINGLE_POINTS = 2, //单点特征点不被绘制
DRAW_PITCH_KEYPOINTS = 4 //对每一个关键点,绘制带大小和方向的关键点圆圈
};
};
KeyPoint类是一个为特征点检测而生的数据结构,用于表示特征点。
class KeyPoint
{
Point2f pt; //坐标
float angle; //特征点的方向, 值为【0~360】
float response;
int octave; //特征点所在图像金字塔的组
int class_id; //用于聚类的id
这个程序涉及三方面:
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
//[0]改变console字体颜色
// system("color 2F");
//[1]载入原图片并显示
Mat srcImage1 = imread("../1.jpg", 1);
Mat srcImage2 = imread("../2.jpg", 1);
if(!srcImage1.data || !srcImage2.data) {
printf("读取路径有错误!\n");
return false;
}
imshow("原始图1", srcImage1);
imshow("原始图2", srcImage2);
//[2]定义需要用到的变量和类
// int minHessian = 400; //定义SURF中的hessian阈值特征点检测算子
Ptr<FeatureDetector> detector = SIFT::create(); //定义一个特征检测类对象
std::vector<KeyPoint> KeyPoints_1, KeyPoints_2; //vector模板类是能够存放任意类型的动态数组,能够增加和收缩数据
//[3]调用detect函数检测出SIFT特征关键点,保存在vector容器中
detector->detect(srcImage1, KeyPoints_1);
detector->detect(srcImage2, KeyPoints_2);
//[4]绘制特征关键点
Mat image_keypoints_1;
Mat image_keypoints_2;
drawKeypoints(srcImage1, KeyPoints_1, image_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
drawKeypoints(srcImage2, KeyPoints_2, image_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
//[5]显示效果图
imshow("特征检测效果图1", image_keypoints_1);
imshow("特征检测效果图2", image_keypoints_2);
while(char(waitKey(1) != 'q')) {}
// waitKey(0);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.6)
project(vo1)
set(CMAKE_BUILD_TYPE "Release")
find_package(OpenCV 3 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(sift 1.cpp)
target_link_libraries(sift ${OpenCV_LIBS})
SURF算法为每个检测到的特征点定义了位置和尺寸,尺寸值可以用于定义围绕特征点的窗口的大小,不论物体的尺寸窗口是怎样的,都将包含相同的信息。这些信息用于表示特征点以使得他们不同。
在特征匹配中,描述子用N为向量,在光照不变的理想情况下很理想。优质的描述子可以通过简单的距离测量进行比较,比如欧式距离。
进行特征点的描述主要是drawMathes和BruteForceMatche类的运用。
drawMatches()用于绘制出相匹配的两个图像的关键点
原型:
void drawMatches(const Mat& img1, const vector
其中const vector& matches1to2 参数还可以写成:const vector
const vector&matchesMask=vector()可以写成:const vector
struct DrawMatchesFlags
{
enum
{
DEFAULT = 0, //创建图像矩阵(使用Mat::create()),使用现存的输出图像来绘制关键点和匹配点。对每一个关键点只绘制中间点
DRAW_OVER_OUTIMG = 1, //不创建输出图像矩阵,而是在输出图像上绘制匹配对
NOT_DRAW_SINGLE_POINTS = 2, //单点特征点不被绘制
DRAW_PITCH_KEYPOINTS = 4 //对每一个关键点,绘制带大小和方向的关键点圆圈
};
};
接下来,我们通过BruteForceMatcher类的源码分析用于暴力匹配的操作。
在~/Documents/test_code/opencv-3.4.15/modules/cudalegacy/include/opencv2$
路径下有features2d.hpp文件,可以找到匹配类的定义;
可以看到DescriptorMatcher类和我门之前看到的FeatureDetector类和DescriptorExtractor类一样,都继承自Algorithm类。
用多最多的匹配方法就是DescriptorMatcher 中的:
为各种描述子找到最佳的匹配:
可以看到我们用DescriptorExtractor类进行特征向量的相关计算。SIFT特征的描述方法FeatureDetector类中封装了detect函数可以检测SIFT特征的关键点,保存在vector中,第二步,利用DescriptorExtractor类进行特征向量的相关计算,将之前的vector变量变成矩阵保存在Mat中。最后强行匹配两幅图像的特征向量,利用BfMatch类中的match()函数。
图像特征点匹配流程:
OpenCV提供了一个通用的类,用于提取不同特征点的描述子,如下:
//计算描述子
DescriptorExtractor extractor;
Mat descriptors1, descriptors2;
extractor.compute(srcImage1, keypoint1, descriptrors1);
extractor.compute(srcImage2, keypoint2, descriptrors2);
这里的结果是一个Mat 矩阵,它的函数和特征点的个数相同,每一行都是一个N维描述子的向量,比如SURF就是64维的,该向量描述了特征点周围的强度。例如,检测到两幅图像中的特征,然后对他们提取描述子,第一幅图中每个特征描述子向量与第二幅的比较,得到最高的一对描述子,被视为那个特征的最佳。对第一幅所有的特征点进行重复,就是暴力匹配的过程。
//使用BruteForce进行匹配
BFMatcher matcher;
std::vector<DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
BFMatcher是由DescriptorMatcher派生出的一个类,而DescriptorMatcher定义了不同匹配策略的公共接口。调用match()后,第三个参数输出一个cv::DMatch向量。于是我们需要定义一个std::vector类型的matches.
调用match后,可以使用drawMatches方法对匹配到的点进行绘制,并最终显示出来。
//绘制从两个图中匹配出来的关键点
Mat imatgeMatches;
drawMatches(srcImage1, keyPoint1, srcImage2, keyPoint2, matches, imageMatches); //进行绘制
//显示效果图
imshow("匹配图", imageMatches);
示例代码:
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
using namespace std;
using namespace cv;
int main()
{
//[1]载入图像
Mat srcImage1 = imread("../1.jpg", 1);
Mat srcImage2 = imread("../2.jpg", 1);
if(!srcImage1.data || !srcImage2.data) {
printf("读取图片错误!\n");
return false;
}
//[2]使用SIFT算法检测关键点
Ptr<FeatureDetector> detector = ORB::create();//定义一个特征检测对象;
std::vector<KeyPoint> keypoints1, keypoints2;
//[3]调用detect函数来检测SIFT的特征点,保存在vector中
detector->detect(srcImage1, keypoints1);
detector->detect(srcImage2, keypoints2);
//[4]计算描述符(特征向量)
Ptr<DescriptorExtractor> extractor = ORB::create();
Mat descriptors1, descriptors2;
extractor->compute(srcImage1, keypoints1, descriptors1);
extractor->compute(srcImage2,keypoints2, descriptors2);
//[5]使用暴力匹配
//实例化一个匹配器
// Ptr matcher = DescriptorMatcher::create("BruteForce-Hamming");
Ptr<DescriptorMatcher> matcher= DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE_HAMMINGLUT);
std::vector<DMatch> matches;
//匹配两幅图中的描述子
matcher->match(descriptors1, descriptors2, matches);
//[6]绘制从图像中匹配出的关键点
Mat imageMatches;
drawMatches(srcImage1, keypoints1, srcImage2, keypoints2, matches,imageMatches);//进行绘制
//[7]显示效果
imshow("匹配图", imageMatches);
// waitKey(0);
while(char(waitKey(1) != 'q')){}
return 0;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.6)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(open 1.cpp)
target_link_libraries(open ${OpenCV_LIBS})