关键点匹配效果
一、数据集
二、目标搜索图像
三、源码:
#define _CRT_SECURE_NO_WARNINGS
// flann_search_dataset.cpp
// Naive program to search a query picture in a dataset illustrating usage of FLANN
// 在数据集中搜索查询图片 说明 FLANN 使用的简单程序//
#include <iostream>
#include <vector>
#include "opencv2/core.hpp"
#include "opencv2/core/utils/filesystem.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/features2d.hpp"
#include "opencv2/flann.hpp"
using namespace cv;
using std::cout;
using std::endl;
#define _ORB_
const char* keys =
"{ help h | | Print help message. }"
"{ dataset |C:/Users/Zohar/Pictures/Test | Path to the images folder used as dataset. }"
"{ image | C:/Users/Zohar/Pictures/pujing.bmp | Path to the image to search for in the dataset. }"
"{ save | C:/Users/Zohar/Pictures/pujingflann.bmp | Path and filename where to save the flann structure to. }"
"{ load | | Path and filename where to load the flann structure from. }";
struct img_info {
int img_index;//图片索引
unsigned int nbr_of_matches;//匹配数目
img_info(int _img_index, unsigned int _nbr_of_matches)
: img_index(_img_index)
, nbr_of_matches(_nbr_of_matches)
{}
};
int main(int argc, char* argv[])
{
//-- 测试程序选项
CommandLineParser parser(argc, argv, keys);
if (parser.has("help"))
{
parser.printMessage();
return -1;
}
//读取要搜索的图像
const cv::String img_path = parser.get<String>("image");
Mat img = imread(samples::findFile(img_path), IMREAD_GRAYSCALE);
if (img.empty())
{
cout << "Could not open the image " << img_path << endl;
return -1;
}
//数据集目录
const cv::String db_path = parser.get<String>("dataset");
if (!utils::fs::isDirectory(db_path))
{
cout << "Dataset folder " << db_path.c_str() << " doesn't exist!" << endl;
return -1;
}
//加载flann结构
const cv::String load_db_path = parser.get<String>("load");
if ((load_db_path != String()) && (!utils::fs::exists(load_db_path)))
{
cout << "File " << load_db_path.c_str()
<< " where to load the flann structure from doesn't exist!" << endl;
return -1;
}
//保存flann结构
const cv::String save_db_path = parser.get<String>("save");
//-- Step 1: Detect the keypoints using a detector, compute the descriptors
// in the folder containing the images of the dataset
//使用检测器检测关键点,计算包含数据集图像的文件夹中的描述符
//SIFT \ ORB 特征
#ifdef _SIFT_
int minHessian = 400;
Ptr<Feature2D> detector = SIFT::create(minHessian);
#elif defined(_ORB_)
Ptr<Feature2D> detector = ORB::create();
#else
cout << "Missing or unknown defined descriptor. "
"Only SIFT and ORB are currently interfaced here" << endl;
return -1;
#endif
std::vector<KeyPoint> db_keypoints;//关键点
Mat db_descriptors;///数据集描述子 矩阵
std::vector<unsigned int> db_images_indice_range; //存储每个图像的索引范围store the range of indices per image
std::vector<int> db_indice_2_image_lut; //将描述符索引与其图像匹配 match descriptor indice to its image
db_images_indice_range.push_back(0);//数据集图像索引向量
std::vector<cv::String> files;//数据集文件路径 向量
utils::fs::glob(db_path, cv::String(), files);
for (std::vector<cv::String>::iterator itr = files.begin(); itr != files.end(); ++itr)//遍历数据集
{
Mat tmp_img = imread(*itr, IMREAD_GRAYSCALE);//读取灰度图像
if (!tmp_img.empty())
{
std::vector<KeyPoint> kpts;
Mat descriptors;
detector->detectAndCompute(tmp_img, noArray(), kpts, descriptors);//检测每张数据集的关键点和计算其描述子
db_keypoints.insert(db_keypoints.end(), kpts.begin(), kpts.end());//所有图像关键点在一个长向量中
db_descriptors.push_back(descriptors);//添加到数据集描述子矩阵中。一行一个图像的描述子
db_images_indice_range.push_back(db_images_indice_range.back()
+ static_cast<unsigned int>(kpts.size()));//图像最后一个关键点索引
}
}
//-- Set the LUT
db_indice_2_image_lut.resize(db_images_indice_range.back());//
const int nbr_of_imgs = static_cast<int>(db_images_indice_range.size() - 1);//图象数-1
for (int i = 0; i < nbr_of_imgs; ++i)//遍历所有数据集图像
{
const unsigned int first_indice = db_images_indice_range[i];//关键点向量的 起始索引
const unsigned int last_indice = db_images_indice_range[i + 1];//关键点向量的 终止索引
std::fill(db_indice_2_image_lut.begin() + first_indice,
db_indice_2_image_lut.begin() + last_indice,
i);//第i+1张图像的关键点 起、止索引之间 都填充为图像索引i。
}
//-- Step 2: 构建存储描述符的结构build the structure storing the descriptors
#if defined(_SIFT_)
cv::Ptr<flann::GenericIndex<cvflann::L2<float> > > index;
if (load_db_path != String())
index = cv::makePtr<flann::GenericIndex<cvflann::L2<float> > >(db_descriptors,
cvflann::SavedIndexParams(load_db_path));
else
index = cv::makePtr<flann::GenericIndex<cvflann::L2<float> > >(db_descriptors,
cvflann::KDTreeIndexParams(4));
#elif defined(_ORB_)// L1、L2、Hamming 等距离计算方法
cv::Ptr<flann::GenericIndex<cvflann::Hamming<unsigned char> > > index;
if (load_db_path != String())//保存数据集的flann最近邻索引类的文件路径非空
index = cv::makePtr<flann::GenericIndex<cvflann::Hamming<unsigned char> > >
(db_descriptors, cvflann::SavedIndexParams(load_db_path));//FLANN 最近邻索引类。此类使用为其构建索引的元素类型进行模板化。保存索引类。
else
index = cv::makePtr<flann::GenericIndex<cvflann::Hamming<unsigned char> > >
(db_descriptors, cvflann::LshIndexParams());//
#else
cout << "Descriptor not listed. Set the proper FLANN distance for this descriptor" << endl;
return -1;
#endif
if (save_db_path != String())
index->save(save_db_path);//保存数据集的flann最近邻索引类
//如果没有设置查询图像则返回 Return if no query image was set
if (img_path == String())
return 0;
//-- 检测关键点并计算查询图像的描述符 Detect the keypoints and compute the descriptors for the query image
std::vector<KeyPoint> img_keypoints;//453个关键点
Mat img_descriptors;//453x32 每个关键点 32列
detector->detectAndCompute(img, noArray(), img_keypoints, img_descriptors);//检测要搜索图像的关键点和计算描述子
//-- Step 3: retrieve the descriptors in the dataset matching the ones of the query image
// /!\ knnSearch doesn't follow OpenCV standards by not initialising empty Mat properties
//检索数据集中与查询图像匹配的描述符
// knnSearch 通过不初始化空的 Mat 属性来不遵循 OpenCV 标准
const int knn = 2;//从数据集中找两个最接近的图像
Mat indices(img_descriptors.rows, knn, CV_32S);//近邻索引 矩阵
#if defined(_SIFT_)
#define DIST_TYPE float
Mat dists(img_descriptors.rows, knn, CV_32F);
#elif defined(_ORB_)
#define DIST_TYPE int
Mat dists(img_descriptors.rows, knn, CV_32S);//近邻距离 矩阵
#endif
index->knnSearch(img_descriptors, indices, dists, knn, cvflann::SearchParams(32));//计算搜索图像的k近邻索引和距离 453x2 2:两张相似图像。 每个图像中与搜索图像的453个关键点相近的点索引
//--使用劳氏比率检验过滤匹配 Filter matches using the Lowe's ratio test
const float ratio_thresh = 0.7f;//比率阈值
std::vector<DMatch> good_matches; //匹配项 集合 contains
std::vector<unsigned int> matches_per_img_histogram(nbr_of_imgs, 0);//匹配直方图
for (int i = 0; i < dists.rows; ++i)//遍历每个关键点匹配到的k个近邻距离
{
if (dists.at<DIST_TYPE>(i, 0) < ratio_thresh * dists.at<DIST_TYPE>(i, 1))//第i个关键点 与第一近邻的距离 小于0.7*与第二近邻的距离
{
const int indice_in_db = indices.at<int>(i, 0);//获取第一近邻在数据集中的索引
DMatch dmatch(i, indice_in_db, db_indice_2_image_lut[indice_in_db],
static_cast<float>(dists.at<DIST_TYPE>(i, 0)));//创建匹配对象。 第i个关键点,与第i个关键点的第一近邻的匹配关键点 索引,匹配图像索引,与第一近邻距离
good_matches.push_back(dmatch);//添加到匹配项集合中: 关键点,数据集中关键点的匹配对象,匹配图像索引,距离。
matches_per_img_histogram[db_indice_2_image_lut[indice_in_db]]++;//近邻图像中匹配的关键点总数。 k=2.所有只有两个有数值,其它都为0.
}
}
//-- Step 4: 找到匹配比例最高的数据集图像find the dataset image with the highest proportion of matches
std::multimap<float, img_info> images_infos;//每张图像匹配信息 集合:包含总关键点数与匹配点数比例 以及 数据集对应图像信息
for (int i = 0; i < nbr_of_imgs; ++i)//遍历所有数据集图像
{
const unsigned int nbr_of_matches = matches_per_img_histogram[i];//数据集图像i与 搜索图像匹配的关键点数
if (nbr_of_matches < 4) //单应性至少需要 4 个点 we need at leat 4 points for a homography
continue;//匹配点数小于4 不考虑
const unsigned int nbr_of_kpts = db_images_indice_range[i + 1] - db_images_indice_range[i];//第i+1张数据集图像的关键点数
const float inverse_proportion_of_retrieved_kpts =
static_cast<float>(nbr_of_kpts) / static_cast<float>(nbr_of_matches);//图像的关键点与匹配到的点数比例
img_info info(i, nbr_of_matches);//创建图像匹配信息: 第i+1张图像,与搜索图像匹配点数nbr_of_matches
images_infos.insert(std::pair<float, img_info>(inverse_proportion_of_retrieved_kpts,
info));//添加到近邻图像匹配信息集合中
}
if (images_infos.begin() == images_infos.end())//没有匹配项
{
cout << "No good match could be found." << endl;//没找到相似图像
return 0;
}
//-- if there are several images with a similar proportion of matches,
// select the one with the highest number of matches weighted by the
// squared ratio of proportions如果有几张图像的匹配比例相似,则选择匹配比例最高的一张
const float best_matches_proportion = images_infos.begin()->first;//取出第一张近邻的匹配信息的比率信息
float new_matches_proportion = best_matches_proportion;//初始化最佳比率
img_info best_img = images_infos.begin()->second;//第一张近邻的匹配信息的图像信息
std::multimap<float, img_info>::iterator it = images_infos.begin();
++it;
while ((it != images_infos.end()) && (it->first < 1.1 * best_matches_proportion))//遍历匹配信息中所有项
{
const float ratio = new_matches_proportion / it->first;//前面最好近邻的匹配信息比率/后一匹配信息的比率 历史最好匹配比率与当前项匹配比率的比值
if (it->second.nbr_of_matches * (ratio * ratio) > best_img.nbr_of_matches)//后面的图像匹配点数*比例系数平方 超过历史最好匹配关键点数,认为后面的图像匹配更好
{
new_matches_proportion = it->first;//更新最佳匹配比例
best_img = it->second;//更新最佳匹配图像信息
}
++it;
}
//-- Step 5: 过滤属于数据集最佳图像匹配的 goodmatches filter goodmatches that belong to the best image match of the dataset
std::vector<DMatch> filtered_good_matches;
for (std::vector<DMatch>::iterator itr(good_matches.begin()); itr != good_matches.end(); ++itr)//遍历所有匹配的关键点
{
if (itr->imgIdx == best_img.img_index)//匹配的关键点所属图像索引 与 最佳匹配图像的图像索引 一致
filtered_good_matches.push_back(*itr);// 匹配的关键点 添加到集合中
}
//--从数据集中检索最佳图像匹配 Retrieve the best image match from the dataset
Mat db_img = imread(files[best_img.img_index], IMREAD_GRAYSCALE);//最佳匹配图像
//--绘制匹配项 Draw matches
Mat img_matches;//输出图像
drawMatches(img, img_keypoints, db_img, db_keypoints, filtered_good_matches, img_matches, Scalar::all(-1),
Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);//绘制匹配对
//--显示检测到的匹配项 Show detected matches
imshow("Good Matches", img_matches);
waitKey();
return 0;
}
四、gif 演示
参考: