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

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

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源码阅读小记 - 知乎 (zhihu.com)

2 OpenMVG整个流程的运行测试

 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,显示如下:

3 OpenMVG实战

3.1 SVG绘制

// 包含必要的头文件
#include <iostream>
#include <cstdlib>
#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>#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<float> 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;
}

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 <memory> // 引入智能指针相关头文件//这段代码是一个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> 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; // 程序成功执行完毕
}

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 <cstdlib>
#include <iostream>
#include <string>// 使用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<std::string> vec_fileNames =stlplus::folder_wildcard(sPath, "*." + suffix, false, true);std::cout << "\n在 " << sPath<< " 目录下找到 " << vec_fileNames.size() << " 个文件,后缀为 " << suffix;// 为不同图像格式准备图像对象Image<unsigned char> imageGreyIn, imageGreyU;Image<RGBColor> imageRGBIn, imageRGBU;Image<RGBAColor> 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<unsigned char> 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<Image<unsigned char>::Base>(&tmp_vec[0], h, w);UndistortImage(imageGreyIn, &cam, imageGreyU);WriteImage(sOutFileName.c_str(), imageGreyU);break;}case 3: // RGB图{imageRGBIn = Eigen::Map<Image<RGBColor>::Base>((RGBColor*)&tmp_vec[0], h, w);UndistortImage(imageRGBIn, &cam, imageRGBU);WriteImage(sOutFileName.c_str(), imageRGBU);break;}case 4: // RGBA图{imageRGBAIn = Eigen::Map<Image<RGBAColor>::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)

焦距:5

 焦距: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 <unsupported/Eigen/MatrixFunctions> // 引入Eigen库的矩阵功能扩展#include <iostream> // 引入标准输入输出流
#include <string> // 引入字符串操作using namespace openMVG; // 使用openMVG命名空间简化代码
using namespace openMVG::image; // 使用openMVG的image命名空间
using namespace openMVG::features; // 使用openMVG的features命名空间// 定义一个模板函数,用于将给定椭圆的一个区域规范化为指定大小的正方形补丁
template <typename Image>
void NormalizePatch
(const Image& src_img, // 源图像const AffinePointFeature& feat, // 仿射点特征const int patch_size, // 补丁大小Image& out_patch // 输出补丁图像
)
{// 映射函数Eigen::Matrix<double, 2, 2> A;A << feat.a(), feat.b(),feat.b(), feat.c();// 逆平方根A = A.pow(-0.5);const float sc = 2.f * 3.f / static_cast<float>(patch_size);A = A * sc;const float half_width = static_cast<float>(patch_size) / 2.f;// 计算采样网格std::vector<std::pair<float, float>> 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<float>(j) - half_width, static_cast<float>(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<unsigned char>& img, // 输入图像std::vector<features::AffinePointFeature>& feats_dark, // 暗区域特征std::vector<features::AffinePointFeature>& feats_bright // 亮区域特征
)
{using namespace openMVG::features::MSER;// 提取亮区域MSER{// 反转图像Image<unsigned char> image4(255 - img.array());std::vector<MSERRegion> 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<MSERRegion> 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<unsigned char>& img, // 输入图像std::vector<features::AffinePointFeature>& feats_dark, // 暗区域特征std::vector<features::AffinePointFeature>& feats_bright // 亮区域特征
)
{tbmr::Extract_tbmr(img, feats_bright, std::less<uint8_t>(), 30);tbmr::Extract_tbmr(img, feats_dark, std::greater<uint8_t>(), 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<unsigned char> image; // 定义用于存储读入的图像的变量ReadImage(jpg_filename.c_str(), &image); // 读取图像std::vector<features::AffinePointFeature> 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<unsigned char> 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<unsigned char> 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 <cstdlib>
#include <iostream>
#include <string>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 : " <<std::endl<< argv[0] << std::endl<< "--img1 " << sImg1 << std::endl<< "--img2 " << sImg2 << std::endl<< "--outdir " << sOutDir << std::endl;if (sOutDir.empty())  {std::cerr << "\nIt is an invalid output directory" << std::endl;return EXIT_FAILURE;}// -----------------------------// a. List images// b. Compute features and descriptor// c. Compute putatives descriptor matches// d. Geometric filtering of putatives matches// e. Export some statistics// -----------------------------// 是否存在文件夹,若无则创建if (!stlplus::folder_exists(sOutDir))stlplus::folder_create( sOutDir );const std::string jpg_filenameL = sImg1;const std::string jpg_filenameR = sImg2;//读取图片Image<unsigned char> 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> image_describer(new SIFT_Anatomy_Image_describer(SIFT_Anatomy_Image_describer::Params(-1)));// 创建一个映射,用于存储每张图像检测到的特征区域。键是图像的索引,值是特征区域的智能指针。std::map<IndexT, std::unique_ptr<features::Regions>> 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<SIFT_Regions*>(regions_perImage.at(0).get());const SIFT_Regions* regionsR = dynamic_cast<SIFT_Regions*>(regions_perImage.at(1).get());// 从每张图像的特征区域中提取特征点的位置。// GetRegionsPositions函数返回一个包含所有特征点位置的容器,这些位置是在图像中的坐标。const PointFeaturesfeatsL = regions_perImage.at(0)->GetRegionsPositions(),featsR = regions_perImage.at(1)->GetRegionsPositions();// Show both images side by side{Image<unsigned char> 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<IndMatch> 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 filterconst 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 filterImage<float> imgA (imageL.GetMat().cast<float>());Image<float> imgB (imageR.GetMat().cast<float>());std::vector<Pair> matchesFiltered;std::vector<Pair> matchesPair;//将每对匹配的索引存入matchesPair中 类似 (137,29)for (const auto & match_it : vec_PutativeMatches){matchesPair.emplace_back(match_it.i_, match_it.j_);}std::vector<double> 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=falsestd::vector<bool> 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 KVLDwhile (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="<<kvldparameters.inlierRate<<std::endl;kvldparameters.K = 2;it_num++;}//将K-VLD过滤后仍然有效的匹配对转换为IndMatch类型并存入vec_FilteredMatches中,这些是最终认为质量较高的匹配结果。std::vector<IndMatch> vec_FilteredMatches;for (std::vector<Pair>::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<matchesPair.size()-1;it1++){for (size_t it2=it1+1; it2<matchesPair.size();it2++){if (valid[it1] && valid[it2] && E(it1,it2)>=0){//(179,56)左图179 右图56const 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 segmentfloat 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-VLDsvgDrawer 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<matchesPair.size();it++){if (valid[it]){const PointFeature & l = featsL[matchesPair[it].first];const PointFeature & r = featsR[matchesPair[it].second];// ".svg"svgStream << svg::drawCircle(l.x(), l.y(), 10, svgAttributes().stroke("yellow", 2.0));svgStream << svg::drawCircle(r.x() + imageL.Width(), r.y(), 10, svgAttributes().stroke("yellow", 2.0));}}const std::string out_filename = stlplus::create_filespec(sOutDir, "04_KVLD_Keypoints.svg");std::ofstream svgFile( out_filename.c_str() );svgFile << svgStream.closeSvgFile().str();svgFile.close();}Image <unsigned char> imageOutL = imageL;Image <unsigned char> 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)寻找两组特征之间的匹配对

 

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

 (5)在K-VLD筛选的基础上,绘制匹配对

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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2780708.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

苹果Mac键盘如何将 F1 到 F12 取消按Fn

苹果电脑安装了Win10操作系统之后&#xff0c;F1到F12用不了怎么办的解决方法。本文将介绍一些解决方法&#xff0c;帮助您解决无法使用F1到F12功能键的问题。 使用 Mac系统的人都知道&#xff0c;Mac系统默认是没有开启 F1-F12 的使用的&#xff0c;平时我们使用的系统都可以使…

【小沐学GIS】基于WebGL绘制三维数字地球Earth(OpenGL)

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第二期3【小沐学GIS】…

计算机科学与导论 第一章绪论和第二章数字系统

文章预览&#xff1a; 1. 计算机软硬件常识(第一章)1. 冯诺依曼2. 计算机历史3. 计算机的诞生 2. 数字系统&#xff08;第二章&#xff09;1. 位置化的数字系统1. 十进制系统2. 二进制系统3. 八进制系统4. 十六进制系统 2. 转换1. 其他进制转换到十进制的转换2. 十进制到其他进…

新年福利:《YOLO目标检测》送书活动

博主简介 AI小怪兽&#xff0c;YOLO骨灰级玩家&#xff0c;1&#xff09;YOLOv5、v7、v8优化创新&#xff0c;轻松涨点和模型轻量化&#xff1b;2&#xff09;目标检测、语义分割、OCR、分类等技术孵化&#xff0c;赋能智能制造&#xff0c;工业项目落地经验丰富&#xff1b; …

最全面的Docker安装部署,配置镜像加速

安装Docker 卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine 配置Docker的yum仓库 首先…

【Git】上传本地文件到Git(以Windows环境为例)

Git 的下载参考&#xff1a;Git 安装及配置 一、Git 上传的整体流程 1、工作区 > 本地仓库 将本地文件上传到Git&#xff0c;需要先上传到本地仓库&#xff0c;然后再上传到远程仓库。要上传文件到本地仓库&#xff0c;不是直接拷贝进去的&#xff0c;而是需要通过命令一步…

【数据结构】哈希表的开散列和闭散列模拟

哈希思想 在顺序和树状结构中&#xff0c;元素的存储与其存储位置之间是没有对应关系&#xff0c;因此在查找一个元素时&#xff0c;必须要经过多次的比较。 顺序查找的时间复杂度为0(N)&#xff0c;树的查找时间复杂度为log(N)。 我们最希望的搜索方式&#xff1a;通过元素…

STM32 新建寄存器版本MDK工程简要步骤

新建工程文件夹 新建一个工程根目录文件夹&#xff0c;并在该文件夹里新建D/M/O/P/U文件夹。 Drivers&#xff1a;存放与硬件相关的驱动层文件Middlewares&#xff1a;存放正点原子提供的中间层组件文件和第三方中间层文件Output&#xff1a;存放工程编译输出文件Projects&am…

linux系统下vscode portable版本的python环境搭建003:venv

这里写自定义目录标题 python安装方案一. 使用源码安装&#xff08;有[构建工具](https://blog.csdn.net/ResumeProject/article/details/136095629)的情况下&#xff09;方案二.使用系统包管理器 虚拟环境安装TESTCG 本文目的&#xff1a;希望在获得一个新的系统之后&#xff…

Vue核心基础4:绑定样式、条件渲染、列表渲染

1 绑定样式 【代码】 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>绑定样式</title><s…

FL Studio版本升级-FL Studio怎么升级-FL Studio升级方案

已经是新年2024年了&#xff0c;但是但是依然有很多朋友还在用FL Studio12又或者FL Studio20&#xff0c;今天这篇文章教大家如何升级FL Studio21 FL Studio 21是Image Line公司开发的音乐编曲软件&#xff0c;除了软件以外&#xff0c;我们还提供了FL Studio的升级服务&#…

C++11中的简化声明

auto 用于自动类型推断&#xff0c;显示定义变量&#xff1a; typeid typeid推导出来的是字符串&#xff0c;只能看不能用&#xff0c;通过打印来查看变量的类型&#xff0c;用法如上。 decltype 同样是用来自动推导类型&#xff0c;与auto的区别是&#xff0c;auto在定义时必…

MySQL篇----第十四篇

系列文章目录 文章目录 系列文章目录前言一、MySQL 数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?二、锁的优化策略三、索引的底层实现原理和优化四、什么情况下设置了索引但无法使用前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽…

PKI - 借助Nginx实现_客户端使用自签证书供服务端验证

文章目录 Pre概述在 Nginx 中实现客户端使用自签名证书供服务器验证1. 生成客户端密钥对2. 生成自签名客户端证书3. 配置 Nginx4. 重启 Nginx 修5. 验证 在浏览器中安装客户端证书以便进行访问 Pre PKI - 借助Nginx 实现Https 服务端单向认证、服务端客户端双向认证 PKI - 数…

线性代数的本质——1 向量

向量是线性代数中最为基础的概念。 何为向量&#xff1f; 从物理上看&#xff0c; 向量就是既有大小又有方向的量&#xff0c;只要这两者一定&#xff0c;就可以在空间中随便移动。 从计算机应用的角度看&#xff0c;向量和列表很接近&#xff0c;可以用来描述某对象的几个不同…

人力资源智能化管理项目(day05:角色管理)

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/humanResourceIntelligentManagementProject 搭建页面结构 分页组件&#xff1a;设置layout&#xff0c;表示需要显示的内容&#xff0c;用逗号分隔&#xff0c;布局元素会依次显示。prev表示上一页&#xff0c;next为…

HP Pavilion Laptop 15-cs3xxx原装出厂Win10.20H1系统

惠普笔记本HP Pavilion - 15-cs3030tx原厂Windows10系统镜像下载 链接&#xff1a;https://pan.baidu.com/s/1LmdJoN7F3BGvt49ovq-eww?pwdzgmt 提取码&#xff1a;zgmt 适用型号&#xff1a; 15-cs3001tx&#xff0c;15-cs3030tx&#xff0c;15-cs3031tx&#xff0c;15-cs…

数据结构:并查集讲解

并查集 1.并查集原理2.并查集实现3.并查集应用4.并查集的路径压缩 1.并查集原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中…

寒假作业2024.2.11

请使用递归实现n! #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <unistd.h> int fun(int n) {if (n0) {return 1;} else {return n*fun(n-1);} } int main(int argc, const char *argv[]) {int n…