关键点匹配效果
一、数据集
二、目标搜索图像
三、源码:
#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
#include
#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("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("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("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("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 detector = SIFT::create(minHessian);
#elif defined(_ORB_)
Ptr detector = ORB::create();
#else
cout << "Missing or unknown defined descriptor. "
"Only SIFT and ORB are currently interfaced here" << endl;
return -1;
#endif
std::vector db_keypoints;//关键点
Mat db_descriptors;///数据集描述子 矩阵
std::vector db_images_indice_range; //存储每个图像的索引范围store the range of indices per image
std::vector db_indice_2_image_lut; //将描述符索引与其图像匹配 match descriptor indice to its image
db_images_indice_range.push_back(0);//数据集图像索引向量
std::vector files;//数据集文件路径 向量
utils::fs::glob(db_path, cv::String(), files);
for (std::vector::iterator itr = files.begin(); itr != files.end(); ++itr)//遍历数据集
{
Mat tmp_img = imread(*itr, IMREAD_GRAYSCALE);//读取灰度图像
if (!tmp_img.empty())
{
std::vector 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(kpts.size()));//图像最后一个关键点索引
}
}
//-- Set the LUT
db_indice_2_image_lut.resize(db_images_indice_range.back());//
const int nbr_of_imgs = static_cast(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 > > index;
if (load_db_path != String())
index = cv::makePtr > >(db_descriptors,
cvflann::SavedIndexParams(load_db_path));
else
index = cv::makePtr > >(db_descriptors,
cvflann::KDTreeIndexParams(4));
#elif defined(_ORB_)// L1、L2、Hamming 等距离计算方法
cv::Ptr > > index;
if (load_db_path != String())//保存数据集的flann最近邻索引类的文件路径非空
index = cv::makePtr > >
(db_descriptors, cvflann::SavedIndexParams(load_db_path));//FLANN 最近邻索引类。此类使用为其构建索引的元素类型进行模板化。保存索引类。
else
index = cv::makePtr > >
(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 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 good_matches; //匹配项 集合 contains
std::vector matches_per_img_histogram(nbr_of_imgs, 0);//匹配直方图
for (int i = 0; i < dists.rows; ++i)//遍历每个关键点匹配到的k个近邻距离
{
if (dists.at(i, 0) < ratio_thresh * dists.at(i, 1))//第i个关键点 与第一近邻的距离 小于0.7*与第二近邻的距离
{
const int indice_in_db = indices.at(i, 0);//获取第一近邻在数据集中的索引
DMatch dmatch(i, indice_in_db, db_indice_2_image_lut[indice_in_db],
static_cast(dists.at(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 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(nbr_of_kpts) / static_cast(nbr_of_matches);//图像的关键点与匹配到的点数比例
img_info info(i, nbr_of_matches);//创建图像匹配信息: 第i+1张图像,与搜索图像匹配点数nbr_of_matches
images_infos.insert(std::pair(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::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 filtered_good_matches;
for (std::vector::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(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);//绘制匹配对
//--显示检测到的匹配项 Show detected matches
imshow("Good Matches", img_matches);
waitKey();
return 0;
}
四、gif 演示
参考:
opencv feature2D模块(二)_cshilin的博客-CSDN博客https://blog.csdn.net/cshilin/article/details/52107813?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165347471716780357230974%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165347471716780357230974&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-52107813-null-null.142^v10^pc_search_result_control_group,157^v12^new_style1&utm_term=Feature2D&spm=1018.2226.3001.4187
OpenCV使用 GenericIndex 进行 KNN 搜索_mightbxg的博客-CSDN博客https://blog.csdn.net/mightbxg/article/details/118338302?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165347465116782248516618%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165347465116782248516618&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-118338302-null-null.142^v10^pc_search_result_control_group,157^v12^new_style1&utm_term=GenericIndex&spm=1018.2226.3001.4187
opencv+flann库+GenericIndex类_大王叫我来巡山228的博客-CSDN博客https://blog.csdn.net/weixin_40710375/article/details/80594960?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165347465116782248586689%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165347465116782248586689&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-2-80594960-null-null.142^v10^pc_search_result_control_group,157^v12^new_style1&utm_term=GenericIndex&spm=1018.2226.3001.4187
C++ map容器和multimap容器(STL map容器)_MagnumLu的博客-CSDN博客_c++ multimaphttps://blog.csdn.net/qq_28584889/article/details/83855734?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165347460916782184657270%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165347460916782184657270&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-83855734-null-null.142^v10^pc_search_result_control_group,157^v12^new_style1&utm_term=multimap&spm=1018.2226.3001.4187
OpenCV学习笔记:drawmatches函数的参数详解_视觉闫小亘的博客-CSDN博客_drawmatches函数https://blog.csdn.net/two_ye/article/details/100576029?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165347450816782425119139%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165347450816782425119139&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-100576029-null-null.142^v10^pc_search_result_control_group,157^v12^new_style1&utm_term=drawMatches&spm=1018.2226.3001.4187