1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 // its contributors may be used to endorse or promote products derived
16 // from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31
32 #include "base/similarity_transform.h"
33
34 #include "base/pose.h"
35 #include "base/projection.h"
36 #include "base/reconstruction.h"
37 #include "estimators/similarity_transform.h"
38 #include "optim/loransac.h"
39
40 namespace colmap {
41 namespace {
42
43 struct ReconstructionAlignmentEstimator {
44 static const int kMinNumSamples = 3;
45
46 typedef const Image* X_t;
47 typedef const Image* Y_t;
48 typedef Eigen::Matrix3x4d M_t;
49
SetMaxReprojErrorcolmap::__anon8127749b0111::ReconstructionAlignmentEstimator50 void SetMaxReprojError(const double max_reproj_error) {
51 max_squared_reproj_error_ = max_reproj_error * max_reproj_error;
52 }
53
SetReconstructionscolmap::__anon8127749b0111::ReconstructionAlignmentEstimator54 void SetReconstructions(const Reconstruction* reconstruction1,
55 const Reconstruction* reconstruction2) {
56 CHECK_NOTNULL(reconstruction1);
57 CHECK_NOTNULL(reconstruction2);
58 reconstruction1_ = reconstruction1;
59 reconstruction2_ = reconstruction2;
60 }
61
62 // Estimate 3D similarity transform from corresponding projection centers.
Estimatecolmap::__anon8127749b0111::ReconstructionAlignmentEstimator63 std::vector<M_t> Estimate(const std::vector<X_t>& images1,
64 const std::vector<Y_t>& images2) const {
65 CHECK_GE(images1.size(), 3);
66 CHECK_GE(images2.size(), 3);
67 CHECK_EQ(images1.size(), images2.size());
68
69 std::vector<Eigen::Vector3d> proj_centers1(images1.size());
70 std::vector<Eigen::Vector3d> proj_centers2(images2.size());
71 for (size_t i = 0; i < images1.size(); ++i) {
72 CHECK_EQ(images1[i]->ImageId(), images2[i]->ImageId());
73 proj_centers1[i] = images1[i]->ProjectionCenter();
74 proj_centers2[i] = images2[i]->ProjectionCenter();
75 }
76
77 SimilarityTransform3 tform12;
78 tform12.Estimate(proj_centers1, proj_centers2);
79
80 return {tform12.Matrix().topRows<3>()};
81 }
82
83 // For each image, determine the ratio of 3D points that correctly project
84 // from one image to the other image and vice versa for the given alignment.
85 // The residual is then defined as 1 minus this ratio, i.e., an error
86 // threshold of 0.3 means that 70% of the points for that image must reproject
87 // within the given maximum reprojection error threshold.
Residualscolmap::__anon8127749b0111::ReconstructionAlignmentEstimator88 void Residuals(const std::vector<X_t>& images1,
89 const std::vector<Y_t>& images2, const M_t& alignment12,
90 std::vector<double>* residuals) const {
91 CHECK_EQ(images1.size(), images2.size());
92 CHECK_NOTNULL(reconstruction1_);
93 CHECK_NOTNULL(reconstruction2_);
94
95 const Eigen::Matrix3x4d alignment21 =
96 SimilarityTransform3(alignment12).Inverse().Matrix().topRows<3>();
97
98 residuals->resize(images1.size());
99
100 for (size_t i = 0; i < images1.size(); ++i) {
101 const auto& image1 = *images1[i];
102 const auto& image2 = *images2[i];
103
104 CHECK_EQ(image1.ImageId(), image2.ImageId());
105 CHECK_EQ(image1.CameraId(), image2.CameraId());
106
107 const auto& camera1 = reconstruction1_->Camera(image1.CameraId());
108 const auto& camera2 = reconstruction2_->Camera(image2.CameraId());
109
110 const Eigen::Matrix3x4d proj_matrix1 = image1.ProjectionMatrix();
111 const Eigen::Matrix3x4d proj_matrix2 = image2.ProjectionMatrix();
112
113 CHECK_EQ(image1.NumPoints2D(), image2.NumPoints2D());
114
115 size_t num_inliers = 0;
116 size_t num_common_points = 0;
117
118 for (point2D_t point2D_idx = 0; point2D_idx < image1.NumPoints2D();
119 ++point2D_idx) {
120 // Check if both images have a 3D point.
121
122 const auto& point2D1 = image1.Point2D(point2D_idx);
123 if (!point2D1.HasPoint3D()) {
124 continue;
125 }
126
127 const auto& point2D2 = image2.Point2D(point2D_idx);
128 if (!point2D2.HasPoint3D()) {
129 continue;
130 }
131
132 num_common_points += 1;
133
134 // Reproject 3D point in image 1 to image 2.
135 const Eigen::Vector3d xyz12 =
136 alignment12 *
137 reconstruction1_->Point3D(point2D1.Point3DId()).XYZ().homogeneous();
138 if (CalculateSquaredReprojectionError(point2D2.XY(), xyz12,
139 proj_matrix2, camera2) >
140 max_squared_reproj_error_) {
141 continue;
142 }
143
144 // Reproject 3D point in image 2 to image 1.
145 const Eigen::Vector3d xyz21 =
146 alignment21 *
147 reconstruction2_->Point3D(point2D2.Point3DId()).XYZ().homogeneous();
148 if (CalculateSquaredReprojectionError(point2D1.XY(), xyz21,
149 proj_matrix1, camera1) >
150 max_squared_reproj_error_) {
151 continue;
152 }
153
154 num_inliers += 1;
155 }
156
157 if (num_common_points == 0) {
158 (*residuals)[i] = 1.0;
159 } else {
160 const double negative_inlier_ratio =
161 1.0 - static_cast<double>(num_inliers) /
162 static_cast<double>(num_common_points);
163 (*residuals)[i] = negative_inlier_ratio * negative_inlier_ratio;
164 }
165 }
166 }
167
168 private:
169 double max_squared_reproj_error_ = 0.0;
170 const Reconstruction* reconstruction1_ = nullptr;
171 const Reconstruction* reconstruction2_ = nullptr;
172 };
173
174 } // namespace
175
SimilarityTransform3()176 SimilarityTransform3::SimilarityTransform3() {
177 SimilarityTransform3(1, ComposeIdentityQuaternion(),
178 Eigen::Vector3d(0, 0, 0));
179 }
180
SimilarityTransform3(const Eigen::Matrix3x4d & matrix)181 SimilarityTransform3::SimilarityTransform3(const Eigen::Matrix3x4d& matrix) {
182 transform_.matrix().topLeftCorner<3, 4>() = matrix;
183 }
184
SimilarityTransform3(const Eigen::Transform<double,3,Eigen::Affine> & transform)185 SimilarityTransform3::SimilarityTransform3(
186 const Eigen::Transform<double, 3, Eigen::Affine>& transform)
187 : transform_(transform) {}
188
SimilarityTransform3(const double scale,const Eigen::Vector4d & qvec,const Eigen::Vector3d & tvec)189 SimilarityTransform3::SimilarityTransform3(const double scale,
190 const Eigen::Vector4d& qvec,
191 const Eigen::Vector3d& tvec) {
192 Eigen::Matrix4d matrix = Eigen::MatrixXd::Identity(4, 4);
193 matrix.topLeftCorner<3, 4>() = ComposeProjectionMatrix(qvec, tvec);
194 matrix.block<3, 3>(0, 0) *= scale;
195 transform_.matrix() = matrix;
196 }
197
Estimate(const std::vector<Eigen::Vector3d> & src,const std::vector<Eigen::Vector3d> & dst)198 bool SimilarityTransform3::Estimate(const std::vector<Eigen::Vector3d>& src,
199 const std::vector<Eigen::Vector3d>& dst) {
200 const auto results = SimilarityTransformEstimator<3>().Estimate(src, dst);
201 if (results.empty()) {
202 return false;
203 }
204
205 CHECK_EQ(results.size(), 1);
206 transform_.matrix().topLeftCorner<3, 4>() = results[0];
207
208 return true;
209 }
210
Inverse() const211 SimilarityTransform3 SimilarityTransform3::Inverse() const {
212 return SimilarityTransform3(transform_.inverse());
213 }
214
TransformPoint(Eigen::Vector3d * xyz) const215 void SimilarityTransform3::TransformPoint(Eigen::Vector3d* xyz) const {
216 *xyz = transform_ * *xyz;
217 }
218
TransformPose(Eigen::Vector4d * qvec,Eigen::Vector3d * tvec) const219 void SimilarityTransform3::TransformPose(Eigen::Vector4d* qvec,
220 Eigen::Vector3d* tvec) const {
221 // Projection matrix P1 projects 3D object points to image plane and thus to
222 // 2D image points in the source coordinate system:
223 // x' = P1 * X1
224 // 3D object points can be transformed to the destination system by applying
225 // the similarity transformation S:
226 // X2 = S * X1
227 // To obtain the projection matrix P2 that transforms the object point in the
228 // destination system to the 2D image points, which do not change:
229 // x' = P2 * X2 = P2 * S * X1 = P1 * S^-1 * S * X1 = P1 * I * X1
230 // and thus:
231 // P2' = P1 * S^-1
232 // Finally, undo the inverse scaling of the rotation matrix:
233 // P2 = s * P2'
234
235 Eigen::Matrix4d src_matrix = Eigen::MatrixXd::Identity(4, 4);
236 src_matrix.topLeftCorner<3, 4>() = ComposeProjectionMatrix(*qvec, *tvec);
237 Eigen::Matrix4d dst_matrix =
238 src_matrix.matrix() * transform_.inverse().matrix();
239 dst_matrix *= Scale();
240
241 *qvec = RotationMatrixToQuaternion(dst_matrix.block<3, 3>(0, 0));
242 *tvec = dst_matrix.block<3, 1>(0, 3);
243 }
244
Matrix() const245 Eigen::Matrix4d SimilarityTransform3::Matrix() const {
246 return transform_.matrix();
247 }
248
Scale() const249 double SimilarityTransform3::Scale() const {
250 return Matrix().block<1, 3>(0, 0).norm();
251 }
252
Rotation() const253 Eigen::Vector4d SimilarityTransform3::Rotation() const {
254 return RotationMatrixToQuaternion(Matrix().block<3, 3>(0, 0) / Scale());
255 }
256
Translation() const257 Eigen::Vector3d SimilarityTransform3::Translation() const {
258 return Matrix().block<3, 1>(0, 3);
259 }
260
ComputeAlignmentBetweenReconstructions(const Reconstruction & src_reconstruction,const Reconstruction & ref_reconstruction,const double min_inlier_observations,const double max_reproj_error,Eigen::Matrix3x4d * alignment)261 bool ComputeAlignmentBetweenReconstructions(
262 const Reconstruction& src_reconstruction,
263 const Reconstruction& ref_reconstruction,
264 const double min_inlier_observations, const double max_reproj_error,
265 Eigen::Matrix3x4d* alignment) {
266 CHECK_GE(min_inlier_observations, 0.0);
267 CHECK_LE(min_inlier_observations, 1.0);
268
269 RANSACOptions ransac_options;
270 ransac_options.max_error = 1.0 - min_inlier_observations;
271 ransac_options.min_inlier_ratio = 0.2;
272
273 LORANSAC<ReconstructionAlignmentEstimator, ReconstructionAlignmentEstimator>
274 ransac(ransac_options);
275 ransac.estimator.SetMaxReprojError(max_reproj_error);
276 ransac.estimator.SetReconstructions(&src_reconstruction, &ref_reconstruction);
277 ransac.local_estimator.SetMaxReprojError(max_reproj_error);
278 ransac.local_estimator.SetReconstructions(&src_reconstruction,
279 &ref_reconstruction);
280
281 const auto& common_image_ids =
282 src_reconstruction.FindCommonRegImageIds(ref_reconstruction);
283
284 if (common_image_ids.size() < 3) {
285 return false;
286 }
287
288 std::vector<const Image*> src_images(common_image_ids.size());
289 std::vector<const Image*> ref_images(common_image_ids.size());
290 for (size_t i = 0; i < common_image_ids.size(); ++i) {
291 src_images[i] = &src_reconstruction.Image(common_image_ids[i]);
292 ref_images[i] = &ref_reconstruction.Image(common_image_ids[i]);
293 }
294
295 const auto report = ransac.Estimate(src_images, ref_images);
296
297 if (report.success) {
298 *alignment = report.model;
299 }
300
301 return report.success;
302 }
303
304 } // namespace colmap
305