0
点赞
收藏
分享

微信扫一扫

【opencv】示例-flann_search_dataset.cpp 使用FLANN算法在一个图片数据集中搜索一个查询图像...

420d9936fe89eb7529ee885811779c24.png

// 简单程序,用于展示如何使用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; // 使用OpenCV命名空间
using std::cout;   // 使用标准命名空间中的cout
using std::endl;   // 使用标准命名空间中的endl


#define _ORB_ // 定义_ORB_,以便在后面选择特征点检测算法时使用


// 命令行参数定义
const char* keys =
    "{ help h | | 打印帮助信息。 }"
    "{ dataset | | 作为数据集的图片文件夹的路径。 }"
    "{ image |   | 要在数据集中搜索的图片的路径。 }"
    "{ save |    | 保存flann结构的路径和文件名。 }"
    "{ load |    | 加载flann结构的路径和文件名。 }";


// 结构体,用于存储图片信息
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")) // 如果有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");


    //-- 第一步:使用检测器检测特征点,计算数据集中图片的描述符
    // 选择所要使用的特征点检测算法,这里根据宏定义选取了ORB算法
#ifdef _SIFT_ // 如果定义了_SIFT_
    int minHessian = 400;
    Ptr<Feature2D> detector = SIFT::create( minHessian ); // 使用SIFT算法创建检测器
#elif defined(_ORB_) // 如果定义了_ORB_
    Ptr<Feature2D> detector = ORB::create(); // 使用ORB算法创建检测器
#else // 如果既没有定义_SIFT_也没有定义_ORB_
    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; // 存储每张图片特征点的索引范围
    std::vector<int> db_indice_2_image_lut; // 将描述符索引映射到其所在图片


    // 初始化范围索引为0
    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()) );
        }
    }


    //-- 设置索引到图片的查找表(LUT)
    db_indice_2_image_lut.resize( db_images_indice_range.back() ); // 缩放LUT到合适尺寸
    const int nbr_of_imgs = static_cast<int>( db_images_indice_range.size()-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 ); // 为当前图像的所有特征点设置对应的图片序号
    }


    //-- 第二步:构建存储描述符的结构
    // 根据所选择的特征点检测算法创建相应的FLANN索引实例
#if defined(_SIFT_) // 如果使用了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_) // 如果使用了ORB
    cv::Ptr<flann::GenericIndex<cvflann::Hamming<unsigned char> > > index;
    if (load_db_path != String()) // 如果提供了加载路径
        index  = cv::makePtr<flann::GenericIndex<cvflann::Hamming<unsigned char> > >
                (db_descriptors, cvflann::SavedIndexParams(load_db_path));
    else // 如果没有提供加载路径,创建新的索引
        index  = cv::makePtr<flann::GenericIndex<cvflann::Hamming<unsigned char> > >
                (db_descriptors, cvflann::LshIndexParams());
#else // 如果既没有定义_SIFT_也没有定义_ORB_
    cout<< "Descriptor not listed. Set the proper FLANN distance for this descriptor" <<endl; // 打印错误信息
    return -1; // 退出程序
#endif
    if (save_db_path != String()) // 如果提供了保存FLANN结构的路径
        index->save(save_db_path); // 保存索引


    // 如果没有设置查询图片,返回退出程序
    if (img_path == String())
        return 0;


    //-- 为查询图片检测特征点并计算描述符
    std::vector<KeyPoint> img_keypoints; // 存储查询图片的特征点
    Mat img_descriptors; // 存储查询图片的描述符
    detector->detectAndCompute( img, noArray(), img_keypoints, img_descriptors ); // 检测并计算


    //-- 第三步:检索与查询图片描述符匹配的数据集中的描述符
    // 注意,knnSearch并不遵循OpenCV的标准,其不会初始化空矩阵属性
    const int knn = 2; // K-近邻参数
    Mat indices(img_descriptors.rows, knn, CV_32S); // 存储最近邻的索引
#if defined(_SIFT_) // 如果使用了SIFT算法
#define DIST_TYPE float // 定义距离类型为float
    Mat dists(img_descriptors.rows, knn, CV_32F); // 存储距离
#elif defined(_ORB_) // 如果使用了ORB算法
#define DIST_TYPE int // 定义距离类型为int
    Mat dists(img_descriptors.rows, knn, CV_32S); // 存储距离
#endif
    index->knnSearch( img_descriptors, indices, dists, knn, cvflann::SearchParams(32) ); // 进行K近邻搜索


    //-- 使用Lowe's ratio test过滤匹配项
    const float ratio_thresh = 0.7f; // Lowe测试的比例阈值
    std::vector<DMatch> good_matches; // 保存好的匹配项
    std::vector<unsigned int> matches_per_img_histogram( nbr_of_imgs, 0 ); // 每张图片的匹配数量直方图
    for (int i = 0; i < dists.rows; ++i) // 遍历所有匹配项
    {
        // 如果距离满足Lowe比例测试
        if (dists.at<DIST_TYPE>(i,0) < ratio_thresh * dists.at<DIST_TYPE>(i,1))
        {
            // 获取匹配项在数据库中的索引
            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)));
            good_matches.push_back( dmatch );
            // 更新直方图
            matches_per_img_histogram[ db_indice_2_image_lut[indice_in_db] ]++;
        }
    }


    //-- 第四步:找到拥有最高匹配比例的数据集图片
    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];
        if (nbr_of_matches < 4) // 如果匹配点少于4个,跳过(至少需要4个点计算单应性矩阵)
            continue;


        // 获取当前图片的特征点数量
        const unsigned int nbr_of_kpts = db_images_indice_range[i+1] - db_images_indice_range[i];
        // 计算反向比例
        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);
        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;
    }


    //-- 如果有多张图片具有相似的匹配比例,选择匹配数量最多的一张,由匹配比例的平方比值加权
    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;
    }


    //-- 第五步:过滤属于最佳匹配数据集图片的好匹配项
    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); // 添加到过滤后的匹配项列表中
    }


    //-- 从数据集中检索最佳匹配图片
    Mat db_img = imread( files[best_img.img_index], IMREAD_GRAYSCALE ); // 读取最佳匹配图片


    //-- 绘制匹配项
    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 );


    //-- 显示检测到的匹配项
    imshow("Good Matches", img_matches ); // 显示图片
    waitKey(); // 等待按键事件


    return 0; // 程序正常退出
}

该程序的功能是使用FLANN算法在一个图片数据集中搜索一个查询图像,并且找出与查询图像具有最高匹配度的图像。流程分为几个步骤:首先读取并处理数据集中的图片,检测特征点并计算其描述符;然后用FLANN建立索引,利用K近邻搜索来找出查询图像的特征点对应的最近邻描述符,并通过Lowe的比例测试过滤匹配项;接着根据匹配结果,找到匹配项最多的图片;最后展示查询图像和最佳匹配图片之间的匹配情况。如果命令行中设置了相关选项,还支持保存和加载FLANN索引。

db_images_indice_range.push_back( db_images_indice_range.back()
                       + static_cast<unsigned int>(kpts.size()) );

8e38ec0b60dfa9138fe5cb4778b4f179.png

index->knnSearch( img_descriptors, indices, dists, knn, cvflann::SearchParams(32) );

10e5c4bc13c8d2e445b61f5c762515b9.png

举报

相关推荐

0 条评论