Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions meshroom/aliceVision/SfMTriangulating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
__version__ = "1.0"

from meshroom.core import desc
from meshroom.core.utils import VERBOSE_LEVEL


class SfMTriangulating(desc.AVCommandLineNode):
commandLine = "aliceVision_sfmTriangulating {allParams}"
size = desc.DynamicNodeSize("input")

category = "Sparse Reconstruction"
documentation = """
This node perfoms keypoint triangulation on its input data.
Contrary to the StructureFromMotion node, this node does not infer the camera poses, therefore they must be given in the SfMData input.
"""

inputs = [
desc.File(
name="input",
label="SfMData",
description="SfMData file. Must contain the camera calibration.",
value="",
),
desc.File(
name="tracksFilename",
label="Tracks File",
description="Tracks file.",
value="",
),
desc.IntParam(
name="minNumberOfObservationsForTriangulation",
label="Min Observations For Triangulation",
description="Minimum number of observations to triangulate a point.\n"
"Setting it to 3 (or more) reduces drastically the noise in the point cloud,\n"
"but the number of final poses is a little bit reduced\n"
"(from 1.5% to 11% on the tested datasets).",
value=2,
range=(2, 10, 1),
advanced=True,
),
desc.FloatParam(
name="minAngleForTriangulation",
label="Min Angle For Triangulation",
description="Minimum angle for triangulation.",
value=3.0,
range=(0.1, 10.0, 0.1),
advanced=True,
),
desc.FloatParam(
name="maxTriangulationError",
label="Max Triangulation Error",
description="Maximum reprojection error in the triangulation process.",
value=8.0,
range=(0.1, 10.0, 0.1),
advanced=True,
),
desc.ChoiceParam(
name="verboseLevel",
label="Verbose Level",
description="Verbosity level (fatal, error, warning, info, debug, trace).",
values=VERBOSE_LEVEL,
value="info",
),
]

outputs = [
desc.File(
name="output",
label="SfMData",
description="Path to the output SfMData file.",
value="{nodeCacheFolder}/sfm.abc",
)
]
13 changes: 13 additions & 0 deletions src/software/pipeline/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,19 @@ if (ALICEVISION_BUILD_SFM)
Boost::program_options
)

# SfM Triangulation
alicevision_add_software(aliceVision_sfmTriangulating
SOURCE main_sfmTriangulating.cpp
FOLDER ${FOLDER_SOFTWARE_PIPELINE}
LINKS aliceVision_system
aliceVision_cmdline
aliceVision_track
aliceVision_sfm
aliceVision_sfmData
aliceVision_sfmDataIO
Boost::program_options
)

# Global SfM
alicevision_add_software(aliceVision_globalSfM
SOURCE main_globalSfM.cpp
Expand Down
128 changes: 128 additions & 0 deletions src/software/pipeline/main_sfmTriangulating.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// This file is part of the AliceVision project.
// Copyright (c) 2026 AliceVision contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#include <aliceVision/cmdline/cmdline.hpp>
#include <aliceVision/types.hpp>
#include <aliceVision/system/main.hpp>
#include <aliceVision/alicevision_omp.hpp>
#include <aliceVision/sfmDataIO/sfmDataIO.hpp>
#include <aliceVision/track/trackIO.hpp>
#include <aliceVision/track/TracksHandler.hpp>
#include <aliceVision/sfm/pipeline/expanding/SfmTriangulation.hpp>

#include <boost/program_options.hpp>


// These constants define the current software version.
// They must be updated when the command line is changed.
#define ALICEVISION_SOFTWARE_VERSION_MAJOR 1

Check failure on line 21 in src/software/pipeline/main_sfmTriangulating.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this macro by "const", "constexpr" or an "enum".

See more on https://sonarcloud.io/project/issues?id=alicevision_AliceVision&issues=AZ2vCLEi_Xj6EicZc3VN&open=AZ2vCLEi_Xj6EicZc3VN&pullRequest=2113
#define ALICEVISION_SOFTWARE_VERSION_MINOR 0

Check failure on line 22 in src/software/pipeline/main_sfmTriangulating.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this macro by "const", "constexpr" or an "enum".

See more on https://sonarcloud.io/project/issues?id=alicevision_AliceVision&issues=AZ2vCLEi_Xj6EicZc3VO&open=AZ2vCLEi_Xj6EicZc3VO&pullRequest=2113

using namespace aliceVision;

namespace po = boost::program_options;

int aliceVision_main(int argc, char** argv)
{
std::string sfmDataFilename;
std::string sfmDataOutputFilename;
std::string tracksFilename;

int minNbMatches = 0;
double minAngleForTriangulation = 1.0;
size_t minNbObservationsForTriangulation = 0;
double maxTriangulationError = 8.0;
int randomSeed = std::mt19937::default_seed;

Check warning on line 38 in src/software/pipeline/main_sfmTriangulating.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

unused variable 'randomSeed'

See more on https://sonarcloud.io/project/issues?id=alicevision_AliceVision&issues=AZ2vCLEi_Xj6EicZc3VP&open=AZ2vCLEi_Xj6EicZc3VP&pullRequest=2113

// clang-format off
po::options_description requiredParams("Required parameters");
requiredParams.add_options()
("input,i", po::value<std::string>(&sfmDataFilename)->required(), "SfMData file, must contain the camera calibration.")
("output,o", po::value<std::string>(&sfmDataOutputFilename)->required(), "Path to the output SfMData file.")
("tracksFilename,t", po::value<std::string>(&tracksFilename)->required(), "Tracks file.");

po::options_description optionalParams("Optional parameters");
optionalParams.add_options()
("minNumberOfMatches", po::value<int>(&minNbMatches)->default_value(minNbMatches),
"Minimum number of matches per image pair (and per feature type). "
"This can be useful to have a meaningful reconstruction with accurate keypoints. 0 means no limit.")
("maxTriangulationError", po::value<double>(&maxTriangulationError)->default_value(maxTriangulationError), "Maximum reprojection error in the triangulation process.")
("minAngleForTriangulation", po::value<double>(&minAngleForTriangulation)->default_value(minAngleForTriangulation), "Minimum angle for triangulation in degrees.")
("minNumberOfObservationsForTriangulation", po::value<std::size_t>(&minNbObservationsForTriangulation)->default_value(minNbObservationsForTriangulation), "Minimum number of observations to triangulate a point.");
// clang-format on

CmdLine cmdline("AliceVision SfM Triangulation");
cmdline.add(requiredParams);
cmdline.add(optionalParams);
if (!cmdline.execute(argc, argv))
{
return EXIT_FAILURE;
}

// set maxThreads
HardwareContext hwc = cmdline.getHardwareContext();
omp_set_num_threads(hwc.getMaxThreads());

// load input SfMData scene
sfmData::SfMData sfmData;
if(!sfmDataIO::load(sfmData, sfmDataFilename, sfmDataIO::ESfMData::ALL))
{
ALICEVISION_LOG_ERROR("The input SfMData file '" + sfmDataFilename + "' cannot be read.");
return EXIT_FAILURE;
}

// Load tracks
ALICEVISION_LOG_INFO("Load tracks");
track::TracksHandler tracksHandler;
if (!tracksHandler.load(tracksFilename, sfmData.getValidViews()))
{
ALICEVISION_LOG_ERROR("The input tracks file '" + tracksFilename + "' cannot be read.");
return EXIT_FAILURE;
}

std::set<IndexT> evaluatedTracks;
std::map<IndexT, sfmData::Landmark> outputLandmarks;
std::mt19937 randomNumberGenerator;


// Effectively triangulate tracks
sfm::SfmTriangulation sfmTriangulation(minNbObservationsForTriangulation, maxTriangulationError);
if (!sfmTriangulation.process(sfmData, tracksHandler.getAllTracks(), tracksHandler.getTracksPerView(),
randomNumberGenerator, sfmData.getValidViews(),
evaluatedTracks, outputLandmarks, false))
{
return false;
}

auto & landmarks = sfmData.getLandmarks();
landmarks.clear();

for (const auto & [landmarkId, outputLandmark] : outputLandmarks)
{
if (outputLandmark.getObservations().size() < minNbObservationsForTriangulation)
{
continue;
}

if (!sfm::SfmTriangulation::checkChierality(sfmData, outputLandmark))
{
continue;
}

double maxAngle = sfm::SfmTriangulation::getMaximalAngle(sfmData, outputLandmark);
if (maxAngle < minAngleForTriangulation)
{
continue;
}

landmarks[landmarkId] = outputLandmark;
}

// Save output
sfmDataIO::save(sfmData, sfmDataOutputFilename, sfmDataIO::ESfMData::ALL);

return EXIT_SUCCESS;
}
15 changes: 13 additions & 2 deletions src/software/utils/main_sfmMerge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ bool simpleMerge(sfmData::SfMData & sfmData1, const sfmData::SfMData & sfmData2,
}
}

{
auto& poses1 = sfmData1.getPoses();
auto& poses2 = sfmData2.getPoses();
const size_t totalSize = poses1.size() + poses2.size();

poses1.insert(poses2.begin(), poses2.end());
if (poses1.size() < totalSize && !ignoreDuplicates)
{
ALICEVISION_LOG_ERROR("Unhandled error: common Poses ID between both SfMData");
return false;
}
}

{
auto& intrinsics1 = sfmData1.getIntrinsics();
auto& intrinsics2 = sfmData2.getIntrinsics();
Expand Down Expand Up @@ -401,13 +414,11 @@ bool fromLandmarksMerge(sfmData::SfMData & sfmData1, const sfmData::SfMData & sf
// Simple merge of intrinsics
auto& intrinsics1 = sfmData1.getIntrinsics();
auto& intrinsics2 = sfmData2.getIntrinsics();
totalSize = intrinsics1.size() + intrinsics2.size();
intrinsics1.insert(intrinsics2.begin(), intrinsics2.end());

// Simple merge of poses
auto& poses1 = sfmData1.getPoses();
auto& poses2 = sfmData2.getPoses();
totalSize = poses1.size() + poses2.size();
poses1.insert(poses2.begin(), poses2.end());

sfmData1.addFeaturesFolders(sfmData2.getRelativeFeaturesFolders());
Expand Down
Loading