本人之前也研究过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 提取图像中的仿射特征点
什么是仿射特征点?
仿射特征点指的是图像中能够在视角变化、光照改变或其他影响下保持其特性的点。这些点具有独特的属性,使得它们在图像的不同视图中都能被识别和匹配。
为什么要提取仿射特征点?
-
图像匹配和识别:在不同图像之间识别相同的物体或场景时,通过比较它们的仿射特征点可以有效地找到匹配点。这在例如全景图像拼接、物体识别等任务中非常关键。
-
三维重建:通过从不同角度拍摄的图像中提取仿射特征点,可以计算出物体的三维结构。这是现代三维扫描技术和虚拟现实内容创建中的一个重要步骤。
-
运动跟踪:在视频中跟踪特定物体或特征的运动轨迹时,识别和追踪仿射特征点可以提供准确的运动信息。
-
增强现实(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筛选的基础上,绘制掩码图