OpenCV的feature2d module中提供了从局部图像特征(Local image feature)的检测、特征向量(feature vector)的提取,到特征匹配的实现。其中的局部图像特征包括了常用的几种局部图像特征检测与描述算子,如FAST、SURF、SIFT、以及ORB。对于高维特征向量之间的匹配,OpenCV主要有两种方式:1)BruteForce穷举法;2)FLANN近似K近邻算法(包含了多种高维特征向量匹配的算法,例如随机森林等)。
localFeature.h
// 局部图像特征提取与匹配
// Author: www.icvpr.com
// Blog : http://blog.csdn.net/icvpr
#ifndef _FEATURE_H_
#define _FEATURE_H_
#include
#include
#include
#include
using namespace cv;
using namespace std;
class Feature
{
public:
Feature();
~Feature();
Feature(const string& detectType, const string& extractType, const string& matchType);
public:
void detectKeypoints(const Mat& image, vector& keypoints); // 检测特征点
void extractDescriptors(const Mat& image, vector& keypoints, Mat& descriptor); // 提取特征向量
void bestMatch(const Mat& queryDescriptor, Mat& trainDescriptor, vector& matches); // 最近邻匹配
void knnMatch(const Mat& queryDescriptor, Mat& trainDescriptor, vector>& matches, int k); // K近邻匹配
void saveKeypoints(const Mat& image, const vector& keypoints, const string& saveFileName = ""); // 保存特征点
void saveMatches(const Mat& queryImage,
const vector& queryKeypoints,
const Mat& trainImage,
const vector& trainKeypoints,
const vector& matches,
const string& saveFileName = ""); // 保存匹配结果到图片中
private:
Ptr m_detector;
Ptr m_extractor;
Ptr m_matcher;
string m_detectType;
string m_extractType;
string m_matchType;
};
#endif
LocalFeature.cpp
// 局部图像特征提取与匹配
// Author: www.icvpr.com
// Blog : http://blog.csdn.net/icvpr
#include "LocalFeature.h"
Feature::Feature()
{
m_detectType = "SIFT";
m_extractType = "SIFT";
m_matchType = "FruteForce";
initModule_nonfree();
}
Feature::~Feature()
{
}
Feature::Feature(const string& detectType, const string& extractType, const string& matchType)
{
assert(!detectType.empty());
assert(!extractType.empty());
assert(!matchType.empty());
m_detectType = detectType;
m_extractType = extractType;
m_matchType = matchType;
initModule_nonfree();
}
void Feature::detectKeypoints(const Mat& image, std::vector& keypoints)
{
assert(image.type() == CV_8UC1);
assert(!m_detectType.empty());
keypoints.clear();
m_detector = FeatureDetector::create(m_detectType);
m_detector->detect(image, keypoints);
}
void Feature::extractDescriptors(const Mat& image, std::vector& keypoints, Mat& descriptor)
{
assert(image.type() == CV_8UC1);
assert(!m_extractType.empty());
m_extractor = DescriptorExtractor::create(m_extractType);
m_extractor->compute(image, keypoints, descriptor);
}
void Feature::bestMatch(const Mat& queryDescriptor, Mat& trainDescriptor, std::vector& matches)
{
assert(!queryDescriptor.empty());
assert(!trainDescriptor.empty());
assert(!m_matchType.empty());
matches.clear();
m_matcher = DescriptorMatcher::create(m_matchType);
m_matcher->add(std::vector(1, trainDescriptor));
m_matcher->train();
m_matcher->match(queryDescriptor, matches);
}
void Feature::knnMatch(const Mat& queryDescriptor, Mat& trainDescriptor, std::vector>& matches, int k)
{
assert(k > 0);
assert(!queryDescriptor.empty());
assert(!trainDescriptor.empty());
assert(!m_matchType.empty());
matches.clear();
m_matcher = DescriptorMatcher::create(m_matchType);
m_matcher->add(std::vector(1, trainDescriptor));
m_matcher->train();
m_matcher->knnMatch(queryDescriptor, matches, k);
}
void Feature::saveKeypoints(const Mat& image, const vector& keypoints, const string& saveFileName)
{
assert(!saveFileName.empty());
Mat outImage;
cv::drawKeypoints(image, keypoints, outImage, Scalar(255,255,0), DrawMatchesFlags::DRAW_RICH_KEYPOINTS );
//
string saveKeypointsImgName = saveFileName + "_" + m_detectType + ".jpg";
imwrite(saveKeypointsImgName, outImage);
}
void Feature::saveMatches(const Mat& queryImage,
const vector& queryKeypoints,
const Mat& trainImage,
const vector& trainKeypoints,
const vector& matches,
const string& saveFileName)
{
assert(!saveFileName.empty());
Mat outImage;
cv::drawMatches(queryImage, queryKeypoints, trainImage, trainKeypoints, matches, outImage,
Scalar(255, 0, 0), Scalar(0, 255, 255), vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
//
string saveMatchImgName = saveFileName + "_" + m_detectType + "_" + m_extractType + "_" + m_matchType + ".jpg";
imwrite(saveMatchImgName, outImage);
}
基于特征的匹配方法有FAST、SIFT、SURF、ORB等算法,利用Surf算法进行图像匹配其一般流程为:检测物体特征点->计算特征点描述子->使用BurteForceMatcher或FLANN进行特征点匹配->匹配到的特征点进行透视变换findHomography()->透视矩阵变换perspectiveTransform()->绘制匹配物体轮廓。
图像匹配实际步骤:
一种是使用OpenCV的cv::FeatureDetector接口实现SURF特征检测,可以改变检测阈值实现不同的效果。
// 设置两个用于存放特征点的向量
std::vector keypoint1;
std::vector keypoint2;
// 构造SURF特征检测器
cv::SurfFeatureDetector surf(3000); // 阈值
// 对两幅图分别检测SURF特征
surf.detect(image1,keypoint1);
surf.detect(image2,keypoint2);
再构造SURF描述子提取器,输出矩阵的形式。矩阵的行数与特征向量中的元素的个数相同,每一行都是一个N维描述子的向量。在SURF算法中,默认的描述子维度是64。两个特征点越相似,特征向量也就越接近。
cv::SurfDescriptorExtractor surfDesc;
// 对两幅图像提取SURF描述子
cv::Mat descriptor1, descriptor2;
surfDesc.compute(image1,keypoint1,descriptor1);
surfDesc.compute(image2,keypoint2,descriptor2);
提取出特征描述子后需要进行匹配,用cv::BruteForceMatcher构造匹配器。cv::BruteForceMatcher是类cv::DescriptorMatcher的一个子类,定义了不同的匹配策略的共同接口,结果返回一个cv::DMatch向量,它将被用于表示一对匹配的描述子。之后通过排序筛选出理想的匹配结果,可以加上匹配结果的可视化(也没必要)。
完整代码:
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 以下两图比之
// 输入两张要匹配的图
cv::Mat image1= cv::imread("c:/Fig12.18(a1).jpg",0);
cv::Mat image2= cv::imread("c:/Fig12.18(a2).jpg",0);
if (!image1.data || !image2.data)
qDebug() << "Error!";
cv::namedWindow("Right Image");
cv::imshow("Right Image", image1);
cv::namedWindow("Left Image");
cv::imshow("Left Image", image2);
// 存放特征点的向量
std::vector keypoint1;
std::vector keypoint2;
// 构造SURF特征检测器
cv::SurfFeatureDetector surf(3000); // 阈值
// 对两幅图分别检测SURF特征
surf.detect(image1,keypoint1);
surf.detect(image2,keypoint2);
// 输出带有详细特征点信息的两幅图像
cv::Mat imageSURF;
cv::drawKeypoints(image1,keypoint1,
imageSURF,
cv::Scalar(255,255,255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("Right SURF Features");
cv::imshow("Right SURF Features", imageSURF);
cv::drawKeypoints(image2,keypoint2,
imageSURF,
cv::Scalar(255,255,255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("Left SURF Features");
cv::imshow("Left SURF Features", imageSURF);
// 构造SURF描述子提取器
cv::SurfDescriptorExtractor surfDesc;
// 对两幅图像提取SURF描述子
cv::Mat descriptor1, descriptor2;
surfDesc.compute(image1,keypoint1,descriptor1);
surfDesc.compute(image2,keypoint2,descriptor2);
// 构造匹配器
cv::BruteForceMatcher< cv::L2 > matcher;
// 将两张图片的描述子进行匹配,只选择25个最佳匹配
std::vector matches;
matcher.match(descriptor1, descriptor2, matches);
std::nth_element(matches.begin(), // 初始位置
matches.begin()+24, // 排序元素的位置
matches.end()); // 终止位置
// 移除25位后的所有元素
matches.erase(matches.begin()+25, matches.end());
// 以下操作将匹配结果可视化
cv::Mat imageMatches;
cv::drawMatches(image1,keypoint1, // 第一张图片和检测到的特征点
image2,keypoint2, // 第二张图片和检测到的特征点
matches, // 输出的匹配结果
imageMatches, // 生成的图像
cv::Scalar(128,128,128)); // 画直线的颜色
cv::namedWindow("Matches"); //, CV_WINDOW_NORMAL);
cv::imshow("Matches",imageMatches);
return a.exec();
}
另一种匹配方法则是使用 cv::FlannBasedMatcher 接口以及函数 FLANN 实现快速高效匹配(快速最近邻逼近搜索函数库(Fast Approximate Nearest Neighbor Search Library))。
现在的一个疑问是在怎么样将sift,surf,orb,flann等特征检测和匹配的算法加到我们行人跟随的检测代码中,这个需要就是说结合代码思考一下。