Skip to content

Commit 52bd09d

Browse files
Merge pull request #2044 from alicevision/dev/temporalBundle
Add tests for the temporal smoothness constraint
2 parents 332e35c + 3aa8f36 commit 52bd09d

File tree

10 files changed

+417
-121
lines changed

10 files changed

+417
-121
lines changed

meshroom/aliceVision/SfmExpanding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "2.3"
1+
__version__ = "2.4"
22

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

meshroom/aliceVision/TracksSimulating.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ class TracksSimulating(desc.AVCommandLineNode):
4343
invalidate=True,
4444
advanced=True,
4545
),
46+
desc.BoolParam(
47+
name="randomNoiseVariancePerView",
48+
label="Random Variance Per View",
49+
description="Use different noise variance per view.",
50+
value=False,
51+
invalidate=True,
52+
advanced=True,
53+
),
4654
desc.ChoiceParam(
4755
name="verboseLevel",
4856
label="Verbose Level",

src/aliceVision/sfm/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ alicevision_add_test(bundle/bundleAdjustment_Enhanced_test.cpp
160160
${LEMON_LIBRARY}
161161
)
162162

163+
alicevision_add_test(bundle/bundleAdjustment_temporalConstraint_test.cpp
164+
NAME "sfm_bundleAdjustment_temporalConstraint"
165+
LINKS aliceVision_sfm
166+
aliceVision_track
167+
)
168+
163169
alicevision_add_test(utils/alignment_test.cpp
164170
NAME "sfm_alignment"
165171
LINKS
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// This file is part of the AliceVision project.
2+
// Copyright (c) 2026 AliceVision contributors.
3+
// This Source Code Form is subject to the terms of the Mozilla Public License,
4+
// v. 2.0. If a copy of the MPL was not distributed with this file,
5+
// You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
#define BOOST_TEST_MODULE bundleAdjustment_temporalConstraint
8+
9+
#include <aliceVision/sfmData/SfMData.hpp>
10+
#include <aliceVision/sfm/bundle/BundleAdjustmentCeres.hpp>
11+
#include <aliceVision/sfmDataIO/sceneSample.hpp>
12+
#include <aliceVision/sfm/utils/poseNoise.hpp>
13+
#include <aliceVision/track/tracksUtils.hpp>
14+
15+
#include <aliceVision/sfm/utils/statistics.hpp>
16+
#include <aliceVision/numeric/numeric.hpp>
17+
#include <boost/test/unit_test.hpp>
18+
#include <boost/test/tools/floating_point_comparison.hpp>
19+
20+
using namespace aliceVision;
21+
22+
void computeTemporalSmoothness(const sfmData::SfMData& sfmData, double& diff0Mean, double& diff1Mean, double& diff2Mean,
23+
double& angleDiff0Mean, double& angleDiff1Mean, double& angleDiff2Mean)
24+
{
25+
using namespace Eigen;
26+
using namespace Eigen::indexing;
27+
28+
const int viewCount = sfmData.getViews().size();
29+
30+
MatrixXd viewCenters(3, viewCount);
31+
32+
int frameIdx = 0;
33+
for (auto& [_, pose] : sfmData.getPoses().valueRange())
34+
viewCenters.col(frameIdx++) = pose.getTransform().center();
35+
36+
Vector3d viewsCenter = viewCenters.rowwise().mean();
37+
38+
double radiusMean = (viewCenters.colwise() - viewsCenter).colwise().norm().mean();
39+
40+
diff0Mean = (viewCenters(all,seq(1, last)) - viewCenters(all,seq(0, last-1))).array().abs().mean();
41+
diff0Mean = diff0Mean / radiusMean;
42+
43+
diff1Mean = (2. * viewCenters(all,seq(1, last-1))
44+
- viewCenters(all,seq(0, last-2))
45+
- viewCenters(all,seq(2, last))).array().abs().mean();
46+
diff1Mean = diff1Mean / radiusMean;
47+
48+
diff2Mean = (3. * viewCenters(all,seq(1, last-2))
49+
- 3. * viewCenters(all,seq(2, last-1))
50+
+ viewCenters(all,seq(3, last))
51+
- viewCenters(all,seq(0, last-3))).array().abs().mean();
52+
diff2Mean = diff2Mean / radiusMean;
53+
54+
MatrixXd angleDiff(3, viewCount-1);
55+
MatrixXd quaterDiff(4, viewCount-1);
56+
57+
frameIdx = -1;
58+
Quaterniond prevRot, currRot;
59+
for (auto& [_, pose] : sfmData.getPoses().valueRange())
60+
{
61+
prevRot = currRot;
62+
currRot = Quaterniond(pose.getTransform().rotation());
63+
if (frameIdx == -1)
64+
{
65+
frameIdx++;
66+
continue;
67+
}
68+
Quaterniond quatDiff = currRot * prevRot.conjugate();
69+
quaterDiff.col(frameIdx) = quatDiff.coeffs();
70+
AngleAxisd aa(quatDiff);
71+
angleDiff.col(frameIdx++) = aa.angle() * aa.axis();
72+
}
73+
74+
angleDiff0Mean = std::sqrt(angleDiff.array().pow(2).mean());
75+
76+
MatrixXd angleDiff1(3, viewCount-2);
77+
MatrixXd quaterDiff1(4, viewCount-2);
78+
for (frameIdx = 0; frameIdx < viewCount-2; frameIdx++)
79+
{
80+
Quaterniond quatDiff = Quaterniond(quaterDiff.col(frameIdx+1).data()) * Quaterniond(quaterDiff.col(frameIdx).data()).conjugate();
81+
quaterDiff1.col(frameIdx) = quatDiff.coeffs();
82+
AngleAxisd aa(quatDiff);
83+
angleDiff1.col(frameIdx) = aa.angle() * aa.axis();
84+
}
85+
86+
angleDiff1Mean = std::sqrt(angleDiff1.array().pow(2).mean());
87+
88+
MatrixXd angleDiff2(3, viewCount-3);
89+
for (frameIdx = 0; frameIdx < viewCount-3; frameIdx++)
90+
{
91+
Quaterniond quatDiff = Quaterniond(quaterDiff1.col(frameIdx+1).data()) * Quaterniond(quaterDiff1.col(frameIdx).data()).conjugate();
92+
AngleAxisd aa(quatDiff);
93+
angleDiff2.col(frameIdx) = aa.angle() * aa.axis();
94+
}
95+
96+
angleDiff2Mean = std::sqrt(angleDiff2.array().pow(2).mean());
97+
}
98+
99+
BOOST_AUTO_TEST_CASE(BA_temporalConstraint)
100+
{
101+
makeRandomOperationsReproducible();
102+
103+
sfmData::SfMData sfmData;
104+
sfmDataIO::generateSphereScene(sfmData, 100, 240);
105+
106+
track::TracksMap mapTracks;
107+
track::simulateTracks(sfmData, 20., 0., 0., false, mapTracks);
108+
for (auto& [landmarkId, landmark] : sfmData.getLandmarks())
109+
for (auto& [viewId, obs] : mapTracks[landmarkId].featPerView)
110+
landmark.getObservations().at(viewId).setCoordinates(obs.coords);
111+
112+
double diff0MeanClean, diff1MeanClean, diff2MeanClean;
113+
double angleDiff0MeanClean, angleDiff1MeanClean, angleDiff2MeanClean;
114+
computeTemporalSmoothness(sfmData, diff0MeanClean, diff1MeanClean, diff2MeanClean,
115+
angleDiff0MeanClean, angleDiff1MeanClean, angleDiff2MeanClean);
116+
117+
sfm::addPoseNoise(sfmData, 0.3, 0.05);
118+
119+
double diff0MeanNoise, diff1MeanNoise, diff2MeanNoise;
120+
double angleDiff0MeanNoise, angleDiff1MeanNoise, angleDiff2MeanNoise;
121+
computeTemporalSmoothness(sfmData, diff0MeanNoise, diff1MeanNoise, diff2MeanNoise,
122+
angleDiff0MeanNoise, angleDiff1MeanNoise, angleDiff2MeanNoise);
123+
124+
BOOST_CHECK_LT(diff0MeanClean, diff0MeanNoise);
125+
BOOST_CHECK_LT(diff1MeanClean, diff1MeanNoise);
126+
BOOST_CHECK_LT(diff2MeanClean, diff2MeanNoise);
127+
128+
BOOST_CHECK_LT(angleDiff0MeanClean, angleDiff0MeanNoise);
129+
BOOST_CHECK_LT(angleDiff1MeanClean, angleDiff1MeanNoise);
130+
BOOST_CHECK_LT(angleDiff2MeanClean, angleDiff2MeanNoise);
131+
132+
sfm::BundleAdjustmentCeres::CeresOptions options;
133+
sfm::BundleAdjustment::ERefineOptions refineOptions;
134+
refineOptions = sfm::BundleAdjustment::REFINE_ROTATION
135+
| sfm::BundleAdjustment::REFINE_STRUCTURE
136+
| sfm::BundleAdjustment::REFINE_TRANSLATION
137+
| sfm::BundleAdjustment::REFINE_INTRINSICS_ALL;
138+
options.summary = false;
139+
140+
// Bundle adjustment without temporal constraint
141+
double rmseBeforeBA = sfm::RMSE(sfmData);
142+
sfm::BundleAdjustmentCeres BA(options);
143+
BA.adjust(sfmData, refineOptions);
144+
double rmseAfterBA_noTC = sfm::RMSE(sfmData);
145+
BOOST_CHECK_LT(rmseAfterBA_noTC, .5 * rmseBeforeBA);
146+
147+
double diff0MeanNoTC, diff1MeanNoTC, diff2MeanNoTC;
148+
double angleDiff0MeanNoTC, angleDiff1MeanNoTC, angleDiff2MeanNoTC;
149+
computeTemporalSmoothness(sfmData, diff0MeanNoTC, diff1MeanNoTC, diff2MeanNoTC,
150+
angleDiff0MeanNoTC, angleDiff1MeanNoTC, angleDiff2MeanNoTC);
151+
152+
BOOST_CHECK_LT(angleDiff0MeanNoTC, angleDiff0MeanNoise);
153+
BOOST_CHECK_LT(angleDiff1MeanNoTC, angleDiff1MeanNoise);
154+
BOOST_CHECK_LT(angleDiff2MeanNoTC, angleDiff2MeanNoise);
155+
156+
sfmData::SfMData sfmData_noTC = sfmData::SfMData(sfmData);
157+
158+
// Add the temporal constraint
159+
refineOptions |= sfm::BundleAdjustment::REFINE_TEMPORAL_SMOOTHNESS_CONSTRAINT;
160+
options.temporalConstraintParams.positionWeight = 10.;
161+
options.temporalConstraintParams.c0positionWeight = 1.0;
162+
options.temporalConstraintParams.orientationWeight = 0.;
163+
BA = sfm::BundleAdjustmentCeres(options);
164+
165+
// Bundle adjustment with temporal constraint on positions
166+
BA.adjust(sfmData, refineOptions);
167+
double rmseAfterBA_TCP = sfm::RMSE(sfmData);
168+
BOOST_CHECK_LT(rmseAfterBA_TCP, 1.2 * rmseAfterBA_noTC);
169+
170+
double diff0MeanTCP, diff1MeanTCP, diff2MeanTCP;
171+
double angleDiff0MeanTCP, angleDiff1MeanTCP, angleDiff2MeanTCP;
172+
computeTemporalSmoothness(sfmData, diff0MeanTCP, diff1MeanTCP, diff2MeanTCP,
173+
angleDiff0MeanTCP, angleDiff1MeanTCP, angleDiff2MeanTCP);
174+
175+
BOOST_CHECK_LT(diff0MeanTCP, 2. * diff0MeanClean);
176+
BOOST_CHECK_LT(angleDiff0MeanTCP, 2. * angleDiff0MeanClean);
177+
178+
BOOST_CHECK_LT(diff1MeanTCP, .1 * diff1MeanNoTC);
179+
BOOST_CHECK_LT(diff2MeanTCP, .1 * diff2MeanNoTC);
180+
181+
BOOST_CHECK_LT(angleDiff1MeanTCP, .9 * angleDiff1MeanNoTC);
182+
BOOST_CHECK_LT(angleDiff2MeanTCP, .9 * angleDiff2MeanNoTC);
183+
184+
options.temporalConstraintParams.positionWeight = 0.;
185+
options.temporalConstraintParams.orientationWeight = 10.;
186+
BA = sfm::BundleAdjustmentCeres(options);
187+
188+
sfmData = sfmData::SfMData(sfmData_noTC);
189+
190+
// Bundle adjustment with temporal constraint on orientations
191+
BA.adjust(sfmData, refineOptions);
192+
double rmseAfterBA_TCO = sfm::RMSE(sfmData);
193+
BOOST_CHECK_LT(rmseAfterBA_TCO, 1.2 * rmseAfterBA_noTC);
194+
195+
double diff0MeanTCO, diff1MeanTCO, diff2MeanTCO;
196+
double angleDiff0MeanTCO, angleDiff1MeanTCO, angleDiff2MeanTCO;
197+
computeTemporalSmoothness(sfmData, diff0MeanTCO, diff1MeanTCO, diff2MeanTCO,
198+
angleDiff0MeanTCO, angleDiff1MeanTCO, angleDiff2MeanTCO);
199+
200+
BOOST_CHECK_LT(diff0MeanTCO, 2. * diff0MeanClean);
201+
BOOST_CHECK_LT(angleDiff0MeanTCO, 1.5 * angleDiff0MeanClean);
202+
203+
BOOST_CHECK_LT(diff1MeanTCO, .75 * diff1MeanNoTC);
204+
BOOST_CHECK_LT(diff2MeanTCO, .75 * diff2MeanNoTC);
205+
206+
BOOST_CHECK_LT(angleDiff1MeanTCO, .9 * angleDiff1MeanNoTC);
207+
BOOST_CHECK_LT(angleDiff2MeanTCO, .9 * angleDiff2MeanNoTC);
208+
209+
BOOST_CHECK_LT(diff1MeanTCP, .5 * diff1MeanTCO);
210+
BOOST_CHECK_LT(diff2MeanTCP, .5 * diff2MeanTCO);
211+
212+
BOOST_CHECK_LT(angleDiff1MeanTCO, .9 * angleDiff1MeanTCP);
213+
BOOST_CHECK_LT(angleDiff2MeanTCO, .9 * angleDiff2MeanTCP);
214+
}

0 commit comments

Comments
 (0)