OpenMVG(EXIF、畸变、仿射特征、特征匹配)

本人之前也研究过OpenMVS但是对于OpenMVG只是原理层次的了解,因此乘着过年期间对这个库进行详细的学习。

目录

1 OpenMVG编译与简单测试

1.1 sfm_data.json获取

1.2 计算特征

2 OpenMVG整个流程的运行测试

3 OpenMVG实战

3.1 SVG绘制

3.2 解析图片的EXIF信息

3.3 光学畸变

3.4 提取图像中的仿射特征点

3.5 对图像进行特征匹配(K-VLD)


1 OpenMVG编译与简单测试

参考文章

openMVG+openMVS对数据集的详细重建步骤!避坑!!!_lianqi1008的博客-CSDN博客

OpenMVG源码阅读小记 - 知乎 (zhihu.com)

1.1 sfm_data.json获取

-i参数是已经有的图片,-o是输出路径,创建result文件夹。-d是已经存在的txt

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第1张图片

D:\CPlusProject\MVS_program\openMVG\src\build\Windows-AMD64-Release\Release\openMVG_main_SfMInit_ImageListing.exe 
-i D:\CPlusProject\MVS_program\Data\images 
-o D:\CPlusProject\MVS_program\Data\result\matches 
-d D:\CPlusProject\MVS_program\openMVG\src\openMVG\exif\sensor_width_database\sensor_width_camera_database.txt

执行成功后在matches文件夹产生sfm_data.json文件 。11个图片也就是views长度为11

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第2张图片

1.2 计算特征

D:\CPlusProject\MVS_program\openMVG\src\build\Windows-AMD64-Release\Release\openMVG_main_ComputeFeatures.exe -i D:\CPlusProject\MVS_program\Data\result\matches\sfm_data.json  -o D:\CPlusProject\MVS_program\Data\result\matches

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第3张图片

OpenMVG源码阅读小记 - 知乎 (zhihu.com)

2 OpenMVG整个流程的运行测试

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第4张图片

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第5张图片

 openMVG中的k.txt储存的是相机的内参

增量式 SFM: 

py ./SfM_SequentialPipeline.py  images  matches_sequential 

 全局式SFM:

py ./SfM_GlobalPipeline.py  images  matches_global 

可看到在 matches_sequential 中生成了两个文件夹:matches 存储的是特征点和匹配信息;reconstruction_sequential 保存的是重建后的点云 (后缀为 .ply)。

用 Meshlab 打开其中一个稀疏点云 colorized.ply,显示如下:

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第6张图片

3 OpenMVG实战

3.1 SVG绘制

// 包含必要的头文件
#include 
#include 
#define _USE_MATH_DEFINES
#include 
#include 

#include "svgDrawer.hpp" // 引入SVG绘图库的头文件
using namespace svg; // 使用svg命名空间简化代码


//这段代码展示了如何使用一个简单的SVG绘图库来创建SVG(可缩放矢量图形)文件。SVG是一种基于XML的标记语言,用于描述二维矢量图形。
int main(int argc, char* argv[])
{
    // 简单的使用示例:
    {
        svgDrawer svgSurface; // 创建SVG绘图对象

        // 添加一些绘图指令
        double S = 20.; // 设置一个基础尺寸
        for (double i = 0; i < 3.14 * 2; i += .4) { // 循环绘制一系列的线段
          // 计算线段的起点和终点坐标
            const double ax = cos(i) * S + S;
            const double ay = sin(i) * S + S;
            const double bx = cos(i + 3.14 / 4.) * S + S;
            const double by = sin(i + 3.14 / 4.) * S + S;

            // 使用drawLine函数和svgAttributes设置绘制线段的属性(颜色、线宽等)
            svgSurface << drawLine(ax, ay, bx, by, svgAttributes().stroke("blue", 1));
        }
        // 将SVG内容导出到文件
        std::string sFileName = "FirstExample.svg"; // 文件名
        std::ofstream svgFile(sFileName.c_str()); // 创建文件流
        svgFile << svgSurface.closeSvgFile().str(); // 写入SVG内容并关闭文件
        svgFile.close();
    }

    // 其他绘图原语的使用示例:
    {
        svgDrawer svgSurface(20, 20); // 创建一个新的SVG绘图对象,指定尺寸

        // 添加一些绘图指令
        svgSurface << drawCircle(10, 10, 4, svgAttributes().stroke("red", 1).fill("blue").tooltip("Hello"));
        svgSurface << drawSquare(4, 4, 12, svgAttributes().stroke("black"));
        svgSurface << drawText(8, 11, 6.f, "H", "green");

        // 将SVG内容导出到文件
        std::string sFileName = "SecondExample.svg";
        std::ofstream svgFile(sFileName.c_str());
        svgFile << svgSurface.closeSvgFile().str();
        svgFile.close();
    }

    // 绘制心脏形状(Cardioid)使用SVG多边形线(Polyline):
    {
        size_t nbPoints = 120; // 点的数量
        std::vector vec_x(nbPoints, 0.f), vec_y(nbPoints, 0.f); // 存储点坐标的向量
        double S = 20.; // 基础尺寸
        for (size_t i = 0; i < nbPoints; ++i) { // 计算心脏形的每个点的坐标
            const double theta = i * 2 * M_PI / nbPoints; // 角度
            // 心脏形的方程
            vec_x[i] = (3 * S + S * (2. * sin(theta) - (sin(2. * theta))));
            vec_y[i] = (2 * S - S * (2. * cos(theta) - (cos(2. * theta))));
        }
        // 创建SVG绘图对象并添加心脏形多边形线
        svgDrawer svgSurface(6 * S, 6 * S); // 设置尺寸
        svgSurface << drawPolyline(vec_x.cbegin(), vec_x.cend(), vec_y.cbegin(), vec_y.cend(), svgAttributes().stroke("blue", 2));

        // 将SVG内容导出到文件
        std::string sFileName = "ThirdExample.svg";
        std::ofstream svgFile(sFileName.c_str());
        svgFile << svgSurface.closeSvgFile().str();
        svgFile.close();
    }
    return EXIT_SUCCESS;
}

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第7张图片

3.2 解析图片的EXIF信息

-i  "D:\CPlusProject\MVS_program\ALLTestData\ImageDataset_SceauxCastle-master\images\100_7100.JPG"
// 包含必要的头文件
#include "openMVG/exif/exif_IO_EasyExif.hpp" // 引入OpenMVG库中处理EXIF信息的头文件
using namespace openMVG::exif; // 使用命名空间简化代码

#include "third_party/cmdLine/cmdLine.h" // 引入命令行解析工具
#include  // 引入智能指针相关头文件

//这段代码是一个C++程序,用于读取一张图片的EXIF信息。EXIF(Exchangeable Image File Format)是一种标准格式,
//用于存储数字照片和音频文件中的信息,如拍摄时间、相机设置、缩略图、版权信息等。
int main(int argc, char** argv)
{
    CmdLine cmd; // 创建命令行解析对象

    std::string sInputImage; // 定义变量存储输入的图片文件路径

    // 添加命令行参数,'-i'用于指定图片文件的路径
    cmd.add(make_option('i', sInputImage, "imafile"));

    // 尝试解析命令行参数
    try {
        if (argc == 1) throw std::string("Invalid command line parameter."); // 如果没有提供参数,则抛出异常
        cmd.process(argc, argv); // 处理命令行参数
    }
    catch (const std::string& s) {
        // 如果出现错误,显示用法信息并退出
        std::cerr << "Usage: " << argv[0] << ' '
            << "[-i|--imafile path] "
            << std::endl;

        std::cerr << s << std::endl; // 显示错误信息

        return EXIT_FAILURE; // 返回失败状态码
    }

    // 显示调用信息,包括程序名和输入的图片文件路径
    std::cout << " You called : " << std::endl
        << argv[0] << std::endl
        << "--imafile " << sInputImage << std::endl;

    // 使用智能指针创建Exif_IO_EasyExif对象,用于读取指定图片的EXIF信息
    std::unique_ptr exif_io(new Exif_IO_EasyExif(sInputImage));

    // 读取并显示图片的EXIF信息,包括宽度、高度、焦距、品牌和模型
    std::cout << "width : " << exif_io->getWidth() << std::endl;
    std::cout << "height : " << exif_io->getHeight() << std::endl;
    std::cout << "focal : " << exif_io->getFocal() << std::endl;
    std::cout << "brand : " << exif_io->getBrand() << std::endl;
    std::cout << "model : " << exif_io->getModel() << std::endl;
    return EXIT_SUCCESS; // 程序成功执行完毕
}

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第8张图片

3.3 光学畸变

-i D:\CPlusProject\MVS_program\ALLTestData\ImageDataset_SceauxCastle-master\images  -o  D:\CPlusProject\MVS_program\ALLTestData\ImageDataset_SceauxCastle-master\outputTest  -f 5   -s JPG
// 引入必要的头文件
#include "openMVG/cameras/Camera_Pinhole_Radial.hpp" // 引入径向畸变的针孔相机模型
#include "openMVG/cameras/Camera_undistort_image.hpp" // 引入图像去畸变功能
#include "openMVG/image/image_io.hpp" // 引入图像输入输出功能
#include "openMVG/system/loggerprogress.hpp" // 引入进度条显示

#include "third_party/cmdLine/cmdLine.h" // 引入命令行解析工具
#include "third_party/stlplus3/filesystemSimplified/file_system.hpp" // 引入文件系统操作工具

#include 
#include 
#include 

// 使用OpenMVG库的命名空间,简化代码
using namespace openMVG;
using namespace openMVG::cameras;
using namespace openMVG::image;

//这个程序的目的是自动处理一批图像,通过去除光学畸变来改善它们的质量。
int main(int argc, char** argv)
{
    CmdLine cmd; // 创建命令行解析对象

    // 定义变量存储命令行参数
    std::string sPath; // 输入图像目录
    std::string sOutPath; // 输出图像目录
    Vec2 c; // 畸变中心
    Vec3 k; // 畸变系数
    double f; // 焦距
    std::string suffix = "JPG"; // 默认图像文件后缀

    c = Vec2(1000, 1000);
    k = Vec3(0.001, 0, 0);

    // 添加命令行参数
    cmd.add(make_option('i', sPath, "imadir"));
    cmd.add(make_option('o', sOutPath, "outdir"));
    cmd.add(make_option('a', c(0), "cx"));
    cmd.add(make_option('b', c(1), "cy"));
    cmd.add(make_option('c', k(0), "k1"));
    cmd.add(make_option('d', k(1), "k2"));
    cmd.add(make_option('e', k(2), "k3"));
    cmd.add(make_option('f', f, "focal"));
    cmd.add(make_option('s', suffix, "suffix"));

    // 解析命令行参数
    try {
        if (argc == 1) throw std::string("Invalid command line parameter.");
        cmd.process(argc, argv);
    }
    catch (const std::string& s) {
        // 如果参数解析失败,显示用法信息并退出
        std::cerr << "Usage: " << argv[0] << ' '
            << "[-i|--imadir - 输入路径]\n"
            << "[-o|--outdir - 输出JPG文件的路径]\n"
            << "[-f|--focal - 焦距]\n"
            << "[-s|--suffix - 输入文件的后缀. (默认: JPG)]\n"
            << std::endl;

        std::cerr << s << std::endl;
        return EXIT_FAILURE;
    }

    // 检查输入和输出路径是否相同
    if (sOutPath == sPath)
    {
        std::cerr << "输入和输出路径不能相同" << std::endl;
        return EXIT_FAILURE;
    }

    // 如果输出目录不存在,则创建它
    if (!stlplus::folder_exists(sOutPath))
        stlplus::folder_create(sOutPath);

    // 显示使用的畸变模型参数
    std::cout << "使用的Brown畸变模型参数: \n"
        << "  畸变中心: " << c.transpose() << "\n"
        << "  畸变系数 (K1,K2,K3): "
        << k.transpose() << "\n"
        << "  焦距: " << f << std::endl;

    // 获取指定后缀的文件列表
    const std::vector vec_fileNames =
        stlplus::folder_wildcard(sPath, "*." + suffix, false, true);
    std::cout << "\n在 " << sPath
        << " 目录下找到 " << vec_fileNames.size() << " 个文件,后缀为 " << suffix;

    // 为不同图像格式准备图像对象
    Image imageGreyIn, imageGreyU;
    Image imageRGBIn, imageRGBU;
    Image imageRGBAIn, imageRGBAU;

    // 进度条显示
    system::LoggerProgress my_progress_bar(vec_fileNames.size());
    for (size_t j = 0; j < vec_fileNames.size(); ++j, ++my_progress_bar)
    {
        // 读取图像尺寸、深度
        int w, h, depth;
        std::vector tmp_vec;
        const std::string sOutFileName =
            stlplus::create_filespec(sOutPath, stlplus::basename_part(vec_fileNames[j]), "png");
        const std::string sInFileName =
            stlplus::create_filespec(sPath, stlplus::filename_part(vec_fileNames[j]));
        const int res = ReadImage(sInFileName.c_str(), &tmp_vec, &w, &h, &depth);

        // 创建相机模型对象
        const Pinhole_Intrinsic_Radial_K3 cam(w, h, f, c(0), c(1), k(0), k(1), k(2));

        // 根据图像深度选择相应的处理流程
        if (res == 1)
        {
            switch (depth)
            {
            case 1: // 灰度图
            {
                imageGreyIn = Eigen::Map::Base>(&tmp_vec[0], h, w);
                UndistortImage(imageGreyIn, &cam, imageGreyU);
                WriteImage(sOutFileName.c_str(), imageGreyU);
                break;
            }
            case 3: // RGB图
            {
                imageRGBIn = Eigen::Map::Base>((RGBColor*)&tmp_vec[0], h, w);
                UndistortImage(imageRGBIn, &cam, imageRGBU);
                WriteImage(sOutFileName.c_str(), imageRGBU);
                break;
            }
            case 4: // RGBA图
            {
                imageRGBAIn = Eigen::Map::Base>((RGBAColor*)&tmp_vec[0], h, w);
                UndistortImage(imageRGBAIn, &cam, imageRGBAU);
                WriteImage(sOutFileName.c_str(), imageRGBAU);
                break;
            }
            }
        }
        else
        {
            std::cerr << "\n图像包含 " << depth << "层。不支持此深度!\n";
        }
    } // 结束每个文件的循环
    return EXIT_SUCCESS;
}

摄影新手入门:1分钟搞懂焦距是什么?焦距与视角的关系! - 知乎 (zhihu.com)

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第9张图片

焦距:5

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第10张图片

 焦距:100

3.4 提取图像中的仿射特征点

什么是仿射特征点?

仿射特征点指的是图像中能够在视角变化、光照改变或其他影响下保持其特性的点。这些点具有独特的属性,使得它们在图像的不同视图中都能被识别和匹配。

为什么要提取仿射特征点?

  1. 图像匹配和识别:在不同图像之间识别相同的物体或场景时,通过比较它们的仿射特征点可以有效地找到匹配点。这在例如全景图像拼接、物体识别等任务中非常关键。

  2. 三维重建:通过从不同角度拍摄的图像中提取仿射特征点,可以计算出物体的三维结构。这是现代三维扫描技术和虚拟现实内容创建中的一个重要步骤。

  3. 运动跟踪:在视频中跟踪特定物体或特征的运动轨迹时,识别和追踪仿射特征点可以提供准确的运动信息。

  4. 增强现实(AR):在增强现实应用中,将虚拟对象精准地叠加在现实世界的图像上,需要依据图像的特征点来确定正确的位置和姿态。

这段代码展示了如何在OpenMVG库中使用MSER和TBMR特征检测器来提取和可视化图像中的特征。

非极大值抑制(NMS)和最大稳定极值区域(MSER) - 知乎 (zhihu.com)


#include "openMVG/features/feature.hpp" // 引入特征提取相关的定义
#include "openMVG/features/mser/mser.hpp" // 引入MSER特征提取器的定义
#include "openMVG/features/mser/mser_region.hpp" // 引入MSER区域处理的相关定义
#include "openMVG/features/tbmr/tbmr.hpp" // 引入TBMR特征提取器的定义
#include "openMVG/image/image_io.hpp" // 引入图像输入输出功能
#include "openMVG/image/image_drawing.hpp" // 引入图像绘制功能
#include "openMVG/image/image_resampling.hpp" // 引入图像重采样功能
#include "openMVG/image/sample.hpp" // 引入图像采样工具

#include "third_party/stlplus3/filesystemSimplified/file_system.hpp" // 第三方库,用于简化文件系统操作
#include "third_party/cmdLine/cmdLine.h" // 第三方命令行解析工具

#include  // 引入Eigen库的矩阵功能扩展

#include  // 引入标准输入输出流
#include  // 引入字符串操作

using namespace openMVG; // 使用openMVG命名空间简化代码
using namespace openMVG::image; // 使用openMVG的image命名空间
using namespace openMVG::features; // 使用openMVG的features命名空间

// 定义一个模板函数,用于将给定椭圆的一个区域规范化为指定大小的正方形补丁
template 
void NormalizePatch
(
    const Image& src_img, // 源图像
    const AffinePointFeature& feat, // 仿射点特征
    const int patch_size, // 补丁大小
    Image& out_patch // 输出补丁图像
)
{
    // 映射函数
    Eigen::Matrix A;
    A << feat.a(), feat.b(),
        feat.b(), feat.c();

    // 逆平方根
    A = A.pow(-0.5);

    const float sc = 2.f * 3.f / static_cast(patch_size);
    A = A * sc;

    const float half_width = static_cast(patch_size) / 2.f;

    // 计算采样网格
    std::vector> sampling_grid;
    sampling_grid.reserve(patch_size * patch_size);
    for (int i = 0; i < patch_size; ++i)
    {
        for (int j = 0; j < patch_size; ++j)
        {
            // 相对于补丁中心的变换应用(假设原点在0,0,然后映射到(x,y))
            Vec2 pos;
            pos << static_cast(j) - half_width, static_cast(i) - half_width;
            // 映射(即:椭圆变换)
            const Vec2 affineAdapted = A * pos;

            sampling_grid.emplace_back(affineAdapted(1) + feat.y(), affineAdapted(0) + feat.x());
        }
    }

    Sampler2d< SamplerLinear > sampler;

    // 采样输入图像以生成补丁
    GenericRessample(
        src_img, sampling_grid,
        patch_size, patch_size,
        sampler,
        out_patch);
}

// 定义一个函数,用于从给定图像中提取MSER特征
void Extract_MSER
(
    const Image& img, // 输入图像
    std::vector& feats_dark, // 暗区域特征
    std::vector& feats_bright // 亮区域特征
)
{
    using namespace openMVG::features::MSER;

    // 提取亮区域MSER
    {
        // 反转图像
        Image image4(255 - img.array());
        std::vector regs;
        MSERExtractor extr4(2, 0.0005, 0.1, 0.5, 0.5, MSERExtractor::MSER_4_CONNECTIVITY);
        extr4.Extract(image4, regs);
        for (size_t i = 0; i < regs.size(); ++i)
        {
            double a, b, c;
            regs[i].FitEllipse(a, b, c);
            double x, y;
            regs[i].FitEllipse(x, y);
            feats_bright.emplace_back(x, y, a, b, c);
        }
    }

    // 提取暗区域MSER
    {
        std::vector regs;
        MSERExtractor extr8(2, 0.0005, 0.1, 0.5, 0.5, MSERExtractor::MSER_8_CONNECTIVITY);
        extr8.Extract(img, regs);
        for (size_t i = 0; i < regs.size(); ++i)
        {
            double a, b, c;
            regs[i].FitEllipse(a, b, c);
            double x, y;
            regs[i].FitEllipse(x, y);
            feats_dark.emplace_back(x, y, a, b, c);
        }
    }
}

// 定义一个函数,用于使用TBMR方法从图像中提取特征
void Extract_TBMR
(
    const Image& img, // 输入图像
    std::vector& feats_dark, // 暗区域特征
    std::vector& feats_bright // 亮区域特征
)
{
    tbmr::Extract_tbmr(img, feats_bright, std::less(), 30);
    tbmr::Extract_tbmr(img, feats_dark, std::greater(), 30);
}

// 程序主入口
int main(int argc, char** argv)
{
    std::string sAffine_Detector_Method = "TBMR"; // 默认使用TBMR方法

    CmdLine cmd; // 命令行解析对象
    cmd.add(make_switch('P', "PATCH")); // 添加命令行选项,用于导出规范化的补丁
    cmd.add(make_option('d', sAffine_Detector_Method, "detector")); // 添加命令行选项,用于选择特征检测器

    // 打印程序使用说明
    std::cout
        << "TBMR Demo:\n"
        << " Show detected Affine regions as ellipses,\n"
        << " -[P] in the command line exports square normalized patches for each ellipses.\n"
        << " -[d|detector] TBMR|MSER Detect TBMR or MSER affine regions."
        << std::endl;

    try {
        cmd.process(argc, argv); // 处理命令行参数
    }
    catch (const std::string& s) {
        std::cerr << s << std::endl; // 捕获并打印处理命令行参数时的错误
        return EXIT_FAILURE;
    }

    // 构建输入图像的路径
    const std::string sInputDir =
        stlplus::folder_up(std::string(THIS_SOURCE_DIR)) + "/imageData/SceauxCastle/";
    const std::string jpg_filename = sInputDir + "100_7101.jpg";

    Image image; // 定义用于存储读入的图像的变量
    ReadImage(jpg_filename.c_str(), &image); // 读取图像

    std::vector feats_dark, feats_bright; // 定义存储特征的向量
    if (sAffine_Detector_Method == "MSER") // 如果选择的是MSER方法
    {
        Extract_MSER(image, feats_dark, feats_bright); // 提取MSER特征
    }
    else if (sAffine_Detector_Method == "TBMR") // 如果选择的是TBMR方法
    {
        Extract_TBMR(image, feats_dark, feats_bright); // 提取TBMR特征
    }
    else // 如果输入了无效的检测器类型
    {
        std::cerr << "Invalid Affine detector type." << std::endl; // 打印错误信息
        return EXIT_FAILURE;
    }

    // 特征检测器演示
    {
        std::cout << "#detected BRIGHT " << sAffine_Detector_Method << ": " << feats_bright.size() << std::endl; // 打印亮区域检测到的特征数量

        // 显示提取的区域椭圆
        Image Icpy(image); // 创建图像的副本
        for (size_t i = 0; i < feats_bright.size(); ++i) // 遍历所有亮区域特征
        {
            const AffinePointFeature& fp = feats_bright[i]; // 获取特征点
            DrawEllipse(fp.x(), fp.y(), fp.l1(), fp.l2(), 255, &Icpy, fp.orientation()); // 在图像上绘制椭圆
            if (cmd.used('P')) // 如果命令行中指定了导出补丁
            {
                // 椭圆到正方形41x41补丁的规范化
                Image patch;
                NormalizePatch(Icpy, fp, 41, patch); // 规范化补丁
                std::stringstream str;
                str << "Patch_" << i << ".png"; // 构建补丁的文件名
                WriteImage(str.str().c_str(), patch); // 写入补丁图像
            }
        }
        std::ostringstream os;
        os << sAffine_Detector_Method << "_BRIGHT_features.jpg"; // 构建亮区域特征图像的文件名
        WriteImage(os.str().c_str(), Icpy); // 写入亮区域特征图像

        std::cout << "#detected DARK " << sAffine_Detector_Method << ": " << feats_dark.size() << std::endl; // 打印暗区域检测到的特征数量

        // 显示提取的区域椭圆
        Icpy = image; // 重置图像副本
        for (size_t i = 0; i < feats_dark.size(); ++i) // 遍历所有暗区域特征
        {
            const AffinePointFeature& fp = feats_dark[i]; // 获取特征点
            DrawEllipse(fp.x(), fp.y(), fp.l1(), fp.l2(), 255, &Icpy, fp.orientation()); // 在图像上绘制椭圆
        }
        os.str("");
        os << sAffine_Detector_Method << "_DARK_features.jpg"; // 构建暗区域特征图像的文件名
        WriteImage(os.str().c_str(), Icpy); // 写入暗区域特征图像
    }

    return EXIT_SUCCESS; // 程序成功结束
}

左边是亮区域,右边是暗区域,在图像上绘制椭圆。

3.5 对图像进行特征匹配(K-VLD)

一个是模板区域,一个是搜索区域。使其两者进行特征匹配


// OpenMVG库的一部分,一个开源多视角几何C++库
#include "openMVG/features/sift/SIFT_Anatomy_Image_Describer.hpp" // SIFT特征描述器
#include "openMVG/features/svg_features.hpp" // 用于SVG特征可视化
#include "openMVG/image/image_io.hpp" // 图像输入输出
#include "openMVG/image/image_concat.hpp" // 图像拼接
#include "openMVG/matching/kvld/kvld.h" // KVLD匹配算法
#include "openMVG/matching/kvld/kvld_draw.h" // KVLD匹配结果绘制
#include "openMVG/matching/regions_matcher.hpp" // 区域匹配
#include "openMVG/matching/svg_matches.hpp" // 匹配结果的SVG可视化
#include "third_party/cmdLine/cmdLine.h" // 命令行解析
#include "third_party/stlplus3/filesystemSimplified/file_system.hpp" // 文件系统操作
#include "openMVG/vector_graphics/svgDrawer.hpp" // SVG绘图
#include 
#include 
#include 



using namespace openMVG;
using namespace openMVG::image;
using namespace openMVG::matching;
using namespace svg;

int main(int argc, char **argv) {
  CmdLine cmd;

  //输入参数:两个图片,一个模板一个搜索。一个输出文件夹路径
  std::string sImg1 = stlplus::folder_up(std::string(THIS_SOURCE_DIR))
    + "/imageData/StanfordMobileVisualSearch/Ace_0.png";
  std::string sImg2 = stlplus::folder_up(std::string(THIS_SOURCE_DIR))
    + "/imageData/StanfordMobileVisualSearch/Ace_1.png";
  std::string sOutDir = "./kvldOut";
  std::cout << sImg1 << std::endl << sImg2 << std::endl;
  cmd.add( make_option('i', sImg1, "img1") );
  cmd.add( make_option('j', sImg2, "img2") );
  cmd.add( make_option('o', sOutDir, "outdir") );

  if (argc > 1)
  {
    try {
      if (argc == 1) throw std::string("Invalid command line parameter.");
      cmd.process(argc, argv);
    } catch (const std::string& s) {
        std::cerr << "Usage: " << argv[0] << ' '
        << "[-i|--img1 file] "
        << "[-j|--img2 file] "
        << "[-o|--outdir path] "
        << std::endl;

        std::cerr << s << std::endl;
        return EXIT_FAILURE;
    }
  }

  std::cout << " You called : " < imageL, imageR;
  ReadImage(jpg_filenameL.c_str(), &imageL);
  ReadImage(jpg_filenameR.c_str(), &imageR);

  
  //--
  // 检测并描述图像中的特征区域
  //--
// 使用OpenMVG库的特征命名空间,以便访问特征检测和描述的相关功能
  using namespace openMVG::features;

  // 创建一个SIFT特征描述器对象。SIFT_Anatomy_Image_describer是SIFT特征检测和描述的一种实现。
  // SIFT_Anatomy_Image_describer::Params(-1)创建了一个参数对象,-1表示使用默认参数。
  std::unique_ptr image_describer
  (new SIFT_Anatomy_Image_describer(SIFT_Anatomy_Image_describer::Params(-1)));

  // 创建一个映射,用于存储每张图像检测到的特征区域。键是图像的索引,值是特征区域的智能指针。
  std::map> regions_perImage;

  // 使用刚才创建的描述器来描述两张图像中的特征区域。
  // Describe函数执行特征检测,并将检测到的特征存储在regions_perImage映射中。
  image_describer->Describe(imageL, regions_perImage[0]);
  image_describer->Describe(imageR, regions_perImage[1]);

  //左右图像的特征区域
  const SIFT_Regions* regionsL = dynamic_cast(regions_perImage.at(0).get());
  const SIFT_Regions* regionsR = dynamic_cast(regions_perImage.at(1).get());

  // 从每张图像的特征区域中提取特征点的位置。
  // GetRegionsPositions函数返回一个包含所有特征点位置的容器,这些位置是在图像中的坐标。
  const PointFeatures
      featsL = regions_perImage.at(0)->GetRegionsPositions(),
      featsR = regions_perImage.at(1)->GetRegionsPositions();


  // Show both images side by side
  {
    Image concat;
    ConcatH(imageL, imageR, concat);
    std::string out_filename = "00_images.jpg";
    WriteImage(out_filename.c_str(), concat);
  }

  //- Draw features on the two image (side by side)
  {
    Features2SVG
    (
      jpg_filenameL,
      {imageL.Width(), imageL.Height()},
      regionsL->Features(),
      jpg_filenameR,
      {imageR.Width(), imageR.Height()},
      regionsR->Features(),
      "01_features.svg"
    );
  }

   定义一个用于存储初始匹配对的向量
  std::vector vec_PutativeMatches;
  //-- 执行匹配 -> 寻找最近邻居,通过距离比率进行过滤
  {
    // 函数用于寻找两组特征之间的匹配对。
    // 这个函数使用距离比率测试来过滤不可靠的匹配,增加匹配的准确性。
    // 距离比率测试是一种常用的方法,用于剔除那些与最近邻居的距离
    // 与次近邻居距离比值大于某个阈值(这里是0.8)的匹配,因为这样的匹配往往不够可靠。
    matching::DistanceRatioMatch(
      0.8, matching::BRUTE_FORCE_L2,
      *regions_perImage.at(0).get(),
      *regions_perImage.at(1).get(),
      vec_PutativeMatches);

    // Draw correspondences after Nearest Neighbor ratio filter
    const bool bVertical = true;
    Matches2SVG
    (
      jpg_filenameL,
      {imageL.Width(), imageL.Height()},
      regionsL->GetRegionsPositions(),
      jpg_filenameR,
      {imageR.Width(), imageR.Height()},
      regionsR->GetRegionsPositions(),
      vec_PutativeMatches,
      "02_Matches.svg",
      bVertical,
      std::max(std::max(imageL.Width(), imageL.Height()) / float(600), 2.0f)
    );
  }

  //K-VLD filter
  Image imgA (imageL.GetMat().cast());
  Image imgB (imageR.GetMat().cast());

  std::vector matchesFiltered;
  std::vector matchesPair;

  //将每对匹配的索引存入matchesPair中 类似 (137,29)
  for (const auto & match_it : vec_PutativeMatches)
  {
    matchesPair.emplace_back(match_it.i_, match_it.j_);
  }
  std::vector vec_score;

  //E矩阵用于存储每对匹配的一致性评分
  openMVG::Mat E = openMVG::Mat::Ones(vec_PutativeMatches.size(), vec_PutativeMatches.size())*(-1);
  // gvld-consistancy matrix, intitialized to -1,  >0 consistancy value, -1=unknow, -2=false
  std::vector valid(vec_PutativeMatches.size(), true);// indices of match in the initial matches, if true at the end of KVLD, a match is kept.

  //执行K-VLD算法,尝试通过迭代减少inlierRate(内点率)的方法来筛选出高质量的匹配对。KVLD函数评估每对匹配的一致性,
  //并更新matchesFiltered(过滤后的匹配对)、vec_score(匹配对的评分)、E和valid。如果内点率过低,则通过调整参数重新筛选。
  size_t it_num=0;
  KvldParameters kvldparameters; // initial parameters of KVLD
  while (it_num < 5 &&
          kvldparameters.inlierRate > KVLD(imgA, imgB, regionsL->Features(), regionsR->Features(),
          matchesPair, matchesFiltered, vec_score,E,valid,kvldparameters)) {
    kvldparameters.inlierRate /= 2;
    //std::cout<<"low inlier rate, re-select matches with new rate="< vec_FilteredMatches;
  for (std::vector::const_iterator i_matchFilter = matchesFiltered.begin();
      i_matchFilter != matchesFiltered.end(); ++i_matchFilter){
    vec_FilteredMatches.push_back(IndMatch(i_matchFilter->first, i_matchFilter->second));
  }

  /*
打印K-VLD一致的匹配对
首先,通过svgDrawer创建一个SVG画布,其大小足以并排容纳两张输入图像。
将两张输入图像绘制到SVG画布上,一张在左侧,一张在右侧。
遍历所有匹配对,绘制那些通过K-VLD一致性检查的匹配对。具体来说,就是绘制连接一致匹配对的线段,这些线段的宽度根据视觉连通性(VLD)的长度动态调整,并用黄色高亮显示。
将绘制的结果保存为SVG文件,文件名为03_KVLD_Matches.svg,以便于后续查看和分析。
  */
  //Print K-VLD consistent matches
  {
    svgDrawer svgStream(imageL.Width() + imageR.Width(),
                        std::max(imageL.Height(), imageR.Height()));

    // ".svg"
    svgStream << svg::drawImage(jpg_filenameL, imageL.Width(), imageL.Height());
    svgStream << svg::drawImage(jpg_filenameR, imageR.Width(), imageR.Height(), imageL.Width());


    for (size_t it1=0; it1=0){
          //(179,56)左图179 右图56
          const PointFeature & l1 = featsL[matchesPair[it1].first];
          const PointFeature & r1 = featsR[matchesPair[it1].second];
          //(181,66)
          const PointFeature & l2 = featsL[matchesPair[it2].first];
          const PointFeature & r2 = featsR[matchesPair[it2].second];

          // Compute the width of the current VLD segment
          float L = (l1.coords() - l2.coords()).norm();
          float width = 0.1;

          // ".svg"
          svgStream << svg::drawLine(l1.x(), l1.y(), l2.x(), l2.y(), svgAttributes().stroke("yellow", width));
          svgStream << svg::drawLine(r1.x() + imageL.Width(), r1.y(), r2.x() + imageL.Width(), r2.y(), svgAttributes().stroke("yellow", width));

        }
      }
    }
    const std::string out_filename = stlplus::create_filespec(sOutDir, "03_KVLD_Matches.svg");
    std::ofstream svgFile( out_filename.c_str() );
    svgFile << svgStream.closeSvgFile().str();
    svgFile.close();
  }

  {
    //Print keypoints kept by K-VLD
    svgDrawer svgStream(imageL.Width() + imageR.Width(),
                        std::max(imageL.Height(), imageR.Height()));

    // ".svg"
    svgStream << svg::drawImage(jpg_filenameL, imageL.Width(), imageL.Height());
    svgStream << svg::drawImage(jpg_filenameR, imageR.Width(), imageR.Height(), imageL.Width());

    for (size_t it=0; it imageOutL = imageL;
  Image  imageOutR = imageR;

  getKVLDMask(
    &imageOutL, &imageOutR,
    regionsL->Features(), regionsR->Features(),
    matchesPair,
    valid,
    E);

  {
    const std::string out_filename = stlplus::create_filespec(sOutDir, "05_Left-K-VLD-MASK.jpg");
    WriteImage(out_filename.c_str(), imageOutL);
  }
  {
    const std::string out_filename = stlplus::create_filespec(sOutDir, "06_Right-K-VLD-MASK.jpg");
    WriteImage(out_filename.c_str(), imageOutR);
  }

  return EXIT_SUCCESS;
}

 (1)两张图像拼接在一起,灰度化,便于后续特征匹配。

(2)每张图像进行特征检测,并用圆圈进行标注

 

3)寻找两组特征之间的匹配对

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第11张图片

 

(4)执行K-VLD算法筛选高质量匹配对 

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第12张图片 (5)在K-VLD筛选的基础上,绘制匹配对

(6)在K-VLD筛选的基础上,绘制关键点

(7)在K-VLD筛选的基础上,绘制掩码图 

OpenMVG(EXIF、畸变、仿射特征、特征匹配)_第13张图片

 

你可能感兴趣的:(C++(图形,图像),算法)