基于图像的外观匹配后,我们可以得到初始的匹配结果。然而由于图像局部相似性,可能会导致出现部分错误匹配对,这些匹配对会对后续的运动估计产生较大的影响:最直接的是引入外点,导致运动估计的误差增大,同时使三维重建的耗时增加,并且重建结果很不理想。
因此在初始匹配后,我们通常会采用几何验证的方法过滤部分不满足几何约束的匹配对。最最常见的策略是使用RANSAC,通过随机采样后估计运动模型,并用剩余的非采样点验证模型可靠性,若模型满足约束,则保留所有满足误差约束的匹配对,实现几何过滤。
过滤之后的邻接矩阵
过滤之后的邻接图
#include "openMVG/features/akaze/image_describer_akaze.hpp"
#include "openMVG/features/descriptor.hpp"
#include "openMVG/features/feature.hpp"
#include "openMVG/graph/graph.hpp"
#include "openMVG/graph/graph_stats.hpp"
#include "openMVG/matching/indMatch.hpp"
#include "openMVG/matching/indMatch_utils.hpp"
#include "openMVG/matching/pairwiseAdjacencyDisplay.hpp"
#include "openMVG/matching_image_collection/Cascade_Hashing_Matcher_Regions.hpp"
#include "openMVG/matching_image_collection/E_ACRobust.hpp"
#include "openMVG/matching_image_collection/E_ACRobust_Angular.hpp"
#include "openMVG/matching_image_collection/Eo_Robust.hpp"
#include "openMVG/matching_image_collection/F_ACRobust.hpp"
#include "openMVG/matching_image_collection/GeometricFilter.hpp"
#include "openMVG/matching_image_collection/H_ACRobust.hpp"
#include "openMVG/matching_image_collection/Matcher_Regions.hpp"
#include "openMVG/matching_image_collection/Pair_Builder.hpp"
#include "openMVG/sfm/pipelines/sfm_features_provider.hpp"
#include "openMVG/sfm/pipelines/sfm_regions_provider.hpp"
#include "openMVG/sfm/pipelines/sfm_regions_provider_cache.hpp"
#include "openMVG/sfm/sfm_data.hpp"
#include "openMVG/sfm/sfm_data_io.hpp"
#include "openMVG/stl/stl.hpp"
#include "openMVG/system/timer.hpp"
#include "third_party/cmdLine/cmdLine.h"
#include "third_party/stlplus3/filesystemSimplified/file_system.hpp"
#include <cstdlib>
#include <iostream>
#include <locale>
#include <memory>
#include <string>
using namespace openMVG;
using namespace openMVG::matching;
using namespace openMVG::robust;
using namespace openMVG::sfm;
using namespace openMVG::matching_image_collection;
//几何模型选择
enum EGeometricModel
{
FUNDAMENTAL_MATRIX = 0,
ESSENTIAL_MATRIX = 1,
HOMOGRAPHY_MATRIX = 2,
ESSENTIAL_MATRIX_ANGULAR = 3,
ESSENTIAL_MATRIX_ORTHO = 4,
ESSENTIAL_MATRIX_UPRIGHT = 5
};
int main( int argc, char** argv )
{
CmdLine cmd;
// The scene
std::string sSfM_Data_Filename;
// The input matches
std::string sPutativeMatchesFilename;
// The output matches
std::string sFilteredMatchesFilename;
// The input pairs
std::string sInputPairsFilename;
// The output pairs
std::string sOutputPairsFilename;
std::string sGeometricModel = "f";
bool bForce = false;
bool bGuided_matching = false;
int imax_iteration = 2048;
unsigned int ui_max_cache_size = 0;
//required
cmd.add( make_option( 'i', sSfM_Data_Filename, "input_file" ) );
cmd.add( make_option( 'o', sFilteredMatchesFilename, "output_file" ) );
cmd.add( make_option( 'm', sPutativeMatchesFilename, "matches" ) );
// Options
cmd.add( make_option( 'p', sInputPairsFilename, "input_pairs" ) );
cmd.add( make_option( 's', sOutputPairsFilename, "output_pairs" ) );
cmd.add( make_option( 'g', sGeometricModel, "geometric_model" ) );
cmd.add( make_option( 'f', bForce, "force" ) );
cmd.add( make_option( 'r', bGuided_matching, "guided_matching" ) );
cmd.add( make_option( 'I', imax_iteration, "max_iteration" ) );
cmd.add( make_option( 'c', ui_max_cache_size, "cache_size" ) );
try
{
if ( argc == 1 )
throw std::string( "Invalid command line parameter." );
cmd.process( argc, argv );
}
catch ( const std::string& s )
{
OPENMVG_LOG_INFO << "Usage: " << argv[0] << '\n'
<< "[-i|--input_file] A SfM_Data file\n"
<< "[-m|--matches] (Input) matches filename\n"
<< "[-o|--output_file] (Output) filtered matches filename\n"
<< "\n[Optional]\n"
<< "[-p|--input_pairs] (Input) pairs filename\n"
<< "[-s|--output_pairs] (Output) filtered pairs filename\n"
<< "[-f|--force] Force to recompute data\n"
<< "[-g|--geometric_model]\n"
<< " (pairwise correspondences filtering thanks to robust model estimation):\n"
<< " f: (default) fundamental matrix,\n"
<< " e: essential matrix,\n"
<< " h: homography matrix.\n"
<< " a: essential matrix with an angular parametrization,\n"
<< " u: upright essential matrix with an angular parametrization,\n"
<< " o: orthographic essential matrix.\n"
<< "[-r|--guided_matching] Use the found model to improve the pairwise correspondences.\n"
<< "[-c|--cache_size]\n"
<< " Use a regions cache (only cache_size regions will be stored in memory)\n"
<< " If not used, all regions will be load in memory.";
OPENMVG_LOG_INFO << s;
return EXIT_FAILURE;
}
OPENMVG_LOG_INFO << " You called : "
<< "\n"
<< argv[0] << "\n"
<< "--input_file: " << sSfM_Data_Filename << "\n"
<< "--matches: " << sPutativeMatchesFilename << "\n"
<< "--output_file: " << sFilteredMatchesFilename << "\n"
<< "Optional parameters: "
<< "\n"
<< "--input_pairs " << sInputPairsFilename << "\n"
<< "--output_pairs " << sOutputPairsFilename << "\n"
<< "--force " << (bForce ? "true" : "false") << "\n"
<< "--geometric_model " << sGeometricModel << "\n"
<< "--guided_matching " << bGuided_matching << "\n"
<< "--cache_size " << ((ui_max_cache_size == 0) ? "unlimited" : std::to_string(ui_max_cache_size));
if ( sFilteredMatchesFilename.empty() )
{
OPENMVG_LOG_ERROR << "It is an invalid output file";
return EXIT_FAILURE;
}
if ( sSfM_Data_Filename.empty() )
{
OPENMVG_LOG_ERROR << "It is an invalid SfM file";
return EXIT_FAILURE;
}
if ( sPutativeMatchesFilename.empty() )
{
OPENMVG_LOG_ERROR << "It is an invalid putative matche file";
return EXIT_FAILURE;
}
const std::string sMatchesDirectory = stlplus::folder_part( sPutativeMatchesFilename );
EGeometricModel eGeometricModelToCompute = FUNDAMENTAL_MATRIX;
switch ( std::tolower(sGeometricModel[ 0 ], std::locale()) )
{
case 'f':
eGeometricModelToCompute = FUNDAMENTAL_MATRIX;
break;
case 'e':
eGeometricModelToCompute = ESSENTIAL_MATRIX;
break;
case 'h':
eGeometricModelToCompute = HOMOGRAPHY_MATRIX;
break;
case 'a':
eGeometricModelToCompute = ESSENTIAL_MATRIX_ANGULAR;
break;
case 'u':
eGeometricModelToCompute = ESSENTIAL_MATRIX_UPRIGHT;
break;
case 'o':
eGeometricModelToCompute = ESSENTIAL_MATRIX_ORTHO;
break;
default:
OPENMVG_LOG_ERROR << "Unknown geometric model";
return EXIT_FAILURE;
}
// -----------------------------
// - Load SfM_Data Views & intrinsics data
// a. Load putative descriptor matches
// [a.1] Filter matches with input pairs
// b. Geometric filtering of putative matches
// + Export some statistics
// -----------------------------
//SFM场景读取
SfM_Data sfm_data;
if ( !Load( sfm_data, sSfM_Data_Filename, ESfM_Data( VIEWS | INTRINSICS ) ) )
{
OPENMVG_LOG_ERROR << "The input SfM_Data file \"" << sSfM_Data_Filename << "\" cannot be read.";
return EXIT_FAILURE;
}
// 读取特征和描述子
using namespace openMVG::features;
// Consider that the image_describer.json is inside the matches directory (which is bellow the sfm_data.bin)
const std::string sImage_describer = stlplus::create_filespec( sMatchesDirectory, "image_describer.json" );
std::unique_ptr<Regions> regions_type = Init_region_type_from_file( sImage_describer );
if ( !regions_type )
{
OPENMVG_LOG_ERROR << "Invalid: " << sImage_describer << " regions type file.";
return EXIT_FAILURE;
}
//---------------------------------------
// a. Compute putative descriptor matches
// - Descriptor matching (according user method choice)
// - Keep correspondences only if NearestNeighbor ratio is ok
//---------------------------------------
// Load the corresponding view regions
std::shared_ptr<Regions_Provider> regions_provider;
if ( ui_max_cache_size == 0 )
{
// Default regions provider (load & store all regions in memory)
regions_provider = std::make_shared<Regions_Provider>();
}
else
{
// Cached regions provider (load & store regions on demand)
regions_provider = std::make_shared<Regions_Provider_Cache>( ui_max_cache_size );
}
// Show the progress on the command line:
system::LoggerProgress progress;
if ( !regions_provider->load( sfm_data, sMatchesDirectory, regions_type, &progress ) )
{
OPENMVG_LOG_ERROR << "Invalid regions.";
return EXIT_FAILURE;
}
PairWiseMatches map_PutativeMatches;
//---------------------------------------
// A. 加载初始匹配关系
//---------------------------------------
if ( !Load( map_PutativeMatches, sPutativeMatchesFilename ) )
{
OPENMVG_LOG_ERROR << "Failed to load the initial matches file.";
return EXIT_FAILURE;
}
if ( !sInputPairsFilename.empty() )
{
// Load input pairs
OPENMVG_LOG_INFO << "Loading input pairs ...";
Pair_Set input_pairs;
loadPairs( sfm_data.GetViews().size(), sInputPairsFilename, input_pairs );
// Filter matches with the given pairs
OPENMVG_LOG_INFO << "Filtering matches with the given pairs.";
map_PutativeMatches = getPairs( map_PutativeMatches, input_pairs );
}
//---------------------------------------
// b. Geometric filtering of putative matches
// - A Contrario Estimation of the desired geometric model
// - Use an upper bound for the a contrario estimated threshold
//---------------------------------------
std::unique_ptr<ImageCollectionGeometricFilter> filter_ptr(
new ImageCollectionGeometricFilter( &sfm_data, regions_provider ) );
if ( filter_ptr )
{
system::Timer timer;
const double d_distance_ratio = 0.6;
PairWiseMatches map_GeometricMatches;
switch ( eGeometricModelToCompute )
{
case HOMOGRAPHY_MATRIX:
{
const bool bGeometric_only_guided_matching = true;
filter_ptr->Robust_model_estimation(
GeometricFilter_HMatrix_AC( 4.0, imax_iteration ),
map_PutativeMatches,
bGuided_matching,
bGeometric_only_guided_matching ? -1.0 : d_distance_ratio,
&progress );
map_GeometricMatches = filter_ptr->Get_geometric_matches();
}
break;
case FUNDAMENTAL_MATRIX:
{
filter_ptr->Robust_model_estimation(
GeometricFilter_FMatrix_AC( 4.0, imax_iteration ),
map_PutativeMatches,
bGuided_matching,
d_distance_ratio,
&progress );
map_GeometricMatches = filter_ptr->Get_geometric_matches();
}
break;
case ESSENTIAL_MATRIX:
{
filter_ptr->Robust_model_estimation(
GeometricFilter_EMatrix_AC( 4.0, imax_iteration ),
map_PutativeMatches,
bGuided_matching,
d_distance_ratio,
&progress );
map_GeometricMatches = filter_ptr->Get_geometric_matches();
//-- Perform an additional check to remove pairs with poor overlap
std::vector<PairWiseMatches::key_type> vec_toRemove;
for ( const auto& pairwisematches_it : map_GeometricMatches )
{
const size_t putativePhotometricCount = map_PutativeMatches.find( pairwisematches_it.first )->second.size();
const size_t putativeGeometricCount = pairwisematches_it.second.size();
const float ratio = putativeGeometricCount / static_cast<float>( putativePhotometricCount );
if ( putativeGeometricCount < 50 || ratio < .3f )
{
// the pair will be removed
vec_toRemove.push_back( pairwisematches_it.first );
}
}
//-- remove discarded pairs
for ( const auto& pair_to_remove_it : vec_toRemove )
{
map_GeometricMatches.erase( pair_to_remove_it );
}
}
break;
case ESSENTIAL_MATRIX_ANGULAR:
{
filter_ptr->Robust_model_estimation(
GeometricFilter_ESphericalMatrix_AC_Angular<false>(4.0, imax_iteration),
map_PutativeMatches, bGuided_matching, d_distance_ratio, &progress);
map_GeometricMatches = filter_ptr->Get_geometric_matches();
}
break;
case ESSENTIAL_MATRIX_UPRIGHT:
{
filter_ptr->Robust_model_estimation(
GeometricFilter_ESphericalMatrix_AC_Angular<true>(4.0, imax_iteration),
map_PutativeMatches, bGuided_matching, d_distance_ratio, &progress);
map_GeometricMatches = filter_ptr->Get_geometric_matches();
}
break;
case ESSENTIAL_MATRIX_ORTHO:
{
filter_ptr->Robust_model_estimation(
GeometricFilter_EOMatrix_RA( 2.0, imax_iteration ),
map_PutativeMatches,
bGuided_matching,
d_distance_ratio,
&progress );
map_GeometricMatches = filter_ptr->Get_geometric_matches();
}
break;
}
//---------------------------------------
//-- 导出几何过滤之后的匹配关系
//---------------------------------------
if ( !Save( map_GeometricMatches, sFilteredMatchesFilename ) )
{
OPENMVG_LOG_ERROR << "Cannot save filtered matches in: " << sFilteredMatchesFilename;
return EXIT_FAILURE;
}
// -- export Geometric View Graph statistics
graph::getGraphStatistics(sfm_data.GetViews().size(), getPairs(map_GeometricMatches));
OPENMVG_LOG_INFO << "Task done in (s): " << timer.elapsed();
//-- export Adjacency matrix
OPENMVG_LOG_INFO << "\n Export Adjacency Matrix of the pairwise's geometric matches";
PairWiseMatchingToAdjacencyMatrixSVG( sfm_data.GetViews().size(),
map_GeometricMatches,
stlplus::create_filespec( sMatchesDirectory, "GeometricAdjacencyMatrix", "svg" ) );
const Pair_Set outputPairs = getPairs( map_GeometricMatches );
//-- export view pair graph once geometric filter have been done
{
std::set<IndexT> set_ViewIds;
std::transform( sfm_data.GetViews().begin(), sfm_data.GetViews().end(), std::inserter( set_ViewIds, set_ViewIds.begin() ), stl::RetrieveKey() );
graph::indexedGraph putativeGraph( set_ViewIds, outputPairs );
graph::exportToGraphvizData(
stlplus::create_filespec( sMatchesDirectory, "geometric_matches" ),
putativeGraph );
}
// Write pairs
if ( !sOutputPairsFilename.empty() )
{
OPENMVG_LOG_INFO << "Saving pairs to: " << sOutputPairsFilename;
if ( !savePairs( sOutputPairsFilename, outputPairs ) )
{
OPENMVG_LOG_ERROR << "Failed to write pairs file";
return EXIT_FAILURE;
}
}
}
return EXIT_SUCCESS;
}