【opencv450-samples】flann_search_dataset.cpp在数据集中搜索查询图片 说明 FLANN 使用的简单程序

46 篇文章 4 订阅

 关键点匹配效果

一、数据集

 

二、目标搜索图像

 三、源码:

#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 演示

 

参考: 

opencv feature2D模块(二)_cshilin的博客-CSDN博客icon-default.png?t=M4ADhttps://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博客icon-default.png?t=M4ADhttps://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博客icon-default.png?t=M4ADhttps://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++ multimapicon-default.png?t=M4ADhttps://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函数icon-default.png?t=M4ADhttps://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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值