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