ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
similarity_transform.cc
Go to the documentation of this file.
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 
33 
34 #include "base/pose.h"
35 #include "base/projection.h"
36 #include "base/reconstruction.h"
38 #include "optim/loransac.h"
39 
40 #include <fstream>
41 
42 namespace colmap {
43 namespace {
44 
45 struct ReconstructionAlignmentEstimator {
46  static const int kMinNumSamples = 3;
47 
48  typedef const Image* X_t;
49  typedef const Image* Y_t;
50  typedef Eigen::Matrix3x4d M_t;
51 
52  void SetMaxReprojError(const double max_reproj_error) {
53  max_squared_reproj_error_ = max_reproj_error * max_reproj_error;
54  }
55 
56  void SetReconstructions(const Reconstruction* reconstruction1,
57  const Reconstruction* reconstruction2) {
58  CHECK_NOTNULL(reconstruction1);
59  CHECK_NOTNULL(reconstruction2);
60  reconstruction1_ = reconstruction1;
61  reconstruction2_ = reconstruction2;
62  }
63 
64  // Estimate 3D similarity transform from corresponding projection centers.
65  std::vector<M_t> Estimate(const std::vector<X_t>& images1,
66  const std::vector<Y_t>& images2) const {
67  CHECK_GE(images1.size(), 3);
68  CHECK_GE(images2.size(), 3);
69  CHECK_EQ(images1.size(), images2.size());
70 
71  std::vector<Eigen::Vector3d> proj_centers1(images1.size());
72  std::vector<Eigen::Vector3d> proj_centers2(images2.size());
73  for (size_t i = 0; i < images1.size(); ++i) {
74  CHECK_EQ(images1[i]->ImageId(), images2[i]->ImageId());
75  proj_centers1[i] = images1[i]->ProjectionCenter();
76  proj_centers2[i] = images2[i]->ProjectionCenter();
77  }
78 
79  SimilarityTransform3 tform12;
80  tform12.Estimate(proj_centers1, proj_centers2);
81 
82  return {tform12.Matrix().topRows<3>()};
83  }
84 
85  // For each image, determine the ratio of 3D points that correctly project
86  // from one image to the other image and vice versa for the given alignment.
87  // The residual is then defined as 1 minus this ratio, i.e., an error
88  // threshold of 0.3 means that 70% of the points for that image must reproject
89  // within the given maximum reprojection error threshold.
90  void Residuals(const std::vector<X_t>& images1,
91  const std::vector<Y_t>& images2, const M_t& alignment12,
92  std::vector<double>* residuals) const {
93  CHECK_EQ(images1.size(), images2.size());
94  CHECK_NOTNULL(reconstruction1_);
95  CHECK_NOTNULL(reconstruction2_);
96 
97  const Eigen::Matrix3x4d alignment21 =
98  SimilarityTransform3(alignment12).Inverse().Matrix().topRows<3>();
99 
100  residuals->resize(images1.size());
101 
102  for (size_t i = 0; i < images1.size(); ++i) {
103  const auto& image1 = *images1[i];
104  const auto& image2 = *images2[i];
105 
106  CHECK_EQ(image1.ImageId(), image2.ImageId());
107  CHECK_EQ(image1.CameraId(), image2.CameraId());
108 
109  const auto& camera1 = reconstruction1_->Camera(image1.CameraId());
110  const auto& camera2 = reconstruction2_->Camera(image2.CameraId());
111 
112  const Eigen::Matrix3x4d proj_matrix1 = image1.ProjectionMatrix();
113  const Eigen::Matrix3x4d proj_matrix2 = image2.ProjectionMatrix();
114 
115  CHECK_EQ(image1.NumPoints2D(), image2.NumPoints2D());
116 
117  size_t num_inliers = 0;
118  size_t num_common_points = 0;
119 
120  for (point2D_t point2D_idx = 0; point2D_idx < image1.NumPoints2D();
121  ++point2D_idx) {
122  // Check if both images have a 3D point.
123 
124  const auto& point2D1 = image1.Point2D(point2D_idx);
125  if (!point2D1.HasPoint3D()) {
126  continue;
127  }
128 
129  const auto& point2D2 = image2.Point2D(point2D_idx);
130  if (!point2D2.HasPoint3D()) {
131  continue;
132  }
133 
134  num_common_points += 1;
135 
136  // Reproject 3D point in image 1 to image 2.
137  const Eigen::Vector3d xyz12 =
138  alignment12 *
139  reconstruction1_->Point3D(point2D1.Point3DId()).XYZ().homogeneous();
140  if (CalculateSquaredReprojectionError(point2D2.XY(), xyz12,
141  proj_matrix2, camera2) >
142  max_squared_reproj_error_) {
143  continue;
144  }
145 
146  // Reproject 3D point in image 2 to image 1.
147  const Eigen::Vector3d xyz21 =
148  alignment21 *
149  reconstruction2_->Point3D(point2D2.Point3DId()).XYZ().homogeneous();
150  if (CalculateSquaredReprojectionError(point2D1.XY(), xyz21,
151  proj_matrix1, camera1) >
152  max_squared_reproj_error_) {
153  continue;
154  }
155 
156  num_inliers += 1;
157  }
158 
159  if (num_common_points == 0) {
160  (*residuals)[i] = 1.0;
161  } else {
162  const double negative_inlier_ratio =
163  1.0 - static_cast<double>(num_inliers) /
164  static_cast<double>(num_common_points);
165  (*residuals)[i] = negative_inlier_ratio * negative_inlier_ratio;
166  }
167  }
168  }
169 
170  private:
171  double max_squared_reproj_error_ = 0.0;
172  const Reconstruction* reconstruction1_ = nullptr;
173  const Reconstruction* reconstruction2_ = nullptr;
174 };
175 
176 } // namespace
177 
180  Eigen::Vector3d(0, 0, 0)) {}
181 
183  transform_.matrix().topLeftCorner<3, 4>() = matrix;
184 }
185 
187  const Eigen::Transform<double, 3, Eigen::Affine>& transform)
188  : transform_(transform) {}
189 
191  const Eigen::Vector4d& qvec,
192  const Eigen::Vector3d& tvec) {
193  Eigen::Matrix4d matrix = Eigen::MatrixXd::Identity(4, 4);
194  matrix.topLeftCorner<3, 4>() = ComposeProjectionMatrix(qvec, tvec);
195  matrix.block<3, 3>(0, 0) *= scale;
196  transform_.matrix() = matrix;
197 }
198 
199 void SimilarityTransform3::Write(const std::string& path) {
200  std::ofstream file(path, std::ios::trunc);
201  CHECK(file.is_open()) << path;
202  // Ensure that we don't loose any precision by storing in text.
203  file.precision(17);
204  file << transform_.matrix() << std::endl;
205 }
206 
208  return SimilarityTransform3(transform_.inverse());
209 }
210 
211 void SimilarityTransform3::TransformPoint(Eigen::Vector3d* xyz) const {
212  *xyz = transform_ * *xyz;
213 }
214 
215 void SimilarityTransform3::TransformPose(Eigen::Vector4d* qvec,
216  Eigen::Vector3d* tvec) const {
217  // Projection matrix P1 projects 3D object points to image plane and thus to
218  // 2D image points in the source coordinate system:
219  // x' = P1 * X1
220  // 3D object points can be transformed to the destination system by applying
221  // the similarity transformation S:
222  // X2 = S * X1
223  // To obtain the projection matrix P2 that transforms the object point in the
224  // destination system to the 2D image points, which do not change:
225  // x' = P2 * X2 = P2 * S * X1 = P1 * S^-1 * S * X1 = P1 * I * X1
226  // and thus:
227  // P2' = P1 * S^-1
228  // Finally, undo the inverse scaling of the rotation matrix:
229  // P2 = s * P2'
230 
231  Eigen::Matrix4d src_matrix = Eigen::MatrixXd::Identity(4, 4);
232  src_matrix.topLeftCorner<3, 4>() = ComposeProjectionMatrix(*qvec, *tvec);
233  Eigen::Matrix4d dst_matrix =
234  src_matrix.matrix() * transform_.inverse().matrix();
235  dst_matrix *= Scale();
236 
237  *qvec = RotationMatrixToQuaternion(dst_matrix.block<3, 3>(0, 0));
238  *tvec = dst_matrix.block<3, 1>(0, 3);
239 }
240 
241 Eigen::Matrix4d SimilarityTransform3::Matrix() const {
242  return transform_.matrix();
243 }
244 
246  return Matrix().block<1, 3>(0, 0).norm();
247 }
248 
249 Eigen::Vector4d SimilarityTransform3::Rotation() const {
250  return RotationMatrixToQuaternion(Matrix().block<3, 3>(0, 0) / Scale());
251 }
252 
253 Eigen::Vector3d SimilarityTransform3::Translation() const {
254  return Matrix().block<3, 1>(0, 3);
255 }
256 
258  std::ifstream file(path);
259  CHECK(file.is_open()) << path;
260 
261  Eigen::Matrix4d matrix = Eigen::MatrixXd::Identity(4, 4);
262  for (int i = 0; i < matrix.rows(); ++i) {
263  for (int j = 0; j < matrix.cols(); ++j) {
264  file >> matrix(i, j);
265  }
266  }
267  SimilarityTransform3 tform;
268  tform.transform_.matrix() = matrix;
269  return tform;
270 }
271 
273  const Reconstruction& src_reconstruction,
274  const Reconstruction& ref_reconstruction,
275  const double min_inlier_observations, const double max_reproj_error,
276  Eigen::Matrix3x4d* alignment) {
277  CHECK_GE(min_inlier_observations, 0.0);
278  CHECK_LE(min_inlier_observations, 1.0);
279 
280  RANSACOptions ransac_options;
281  ransac_options.max_error = 1.0 - min_inlier_observations;
282  ransac_options.min_inlier_ratio = 0.2;
283 
285  ransac(ransac_options);
286  ransac.estimator.SetMaxReprojError(max_reproj_error);
287  ransac.estimator.SetReconstructions(&src_reconstruction, &ref_reconstruction);
288  ransac.local_estimator.SetMaxReprojError(max_reproj_error);
289  ransac.local_estimator.SetReconstructions(&src_reconstruction,
290  &ref_reconstruction);
291 
292  const auto& common_image_ids =
293  src_reconstruction.FindCommonRegImageIds(ref_reconstruction);
294 
295  if (common_image_ids.size() < 3) {
296  return false;
297  }
298 
299  std::vector<const Image*> src_images(common_image_ids.size());
300  std::vector<const Image*> ref_images(common_image_ids.size());
301  for (size_t i = 0; i < common_image_ids.size(); ++i) {
302  src_images[i] = &src_reconstruction.Image(common_image_ids[i]);
303  ref_images[i] = &ref_reconstruction.Image(common_image_ids[i]);
304  }
305 
306  const auto report = ransac.Estimate(src_images, ref_images);
307 
308  if (report.success) {
309  *alignment = report.model;
310  }
311 
312  return report.success;
313 }
314 
315 } // namespace colmap
Report Estimate(const std::vector< typename Estimator::X_t > &X, const std::vector< typename Estimator::Y_t > &Y)
Definition: loransac.h:72
LocalEstimator local_estimator
Definition: loransac.h:47
Estimator estimator
Definition: ransac.h:107
std::vector< image_t > FindCommonRegImageIds(const Reconstruction &reconstruction) const
const class Image & Image(const image_t image_id) const
void Write(const std::string &path)
static SimilarityTransform3 FromFile(const std::string &path)
void TransformPose(Eigen::Vector4d *qvec, Eigen::Vector3d *tvec) const
Eigen::Matrix4d Matrix() const
SimilarityTransform3 Inverse() const
Eigen::Vector4d Rotation() const
void TransformPoint(Eigen::Vector3d *xyz) const
Eigen::Vector3d Translation() const
Definition: Eigen.h:103
Matrix< double, 3, 4 > Matrix3x4d
Definition: types.h:39
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
bool ComputeAlignmentBetweenReconstructions(const Reconstruction &src_reconstruction, const Reconstruction &ref_reconstruction, const double min_inlier_observations, const double max_reproj_error, Eigen::Matrix3x4d *alignment)
double CalculateSquaredReprojectionError(const Eigen::Vector2d &point2D, const Eigen::Vector3d &point3D, const Eigen::Vector4d &qvec, const Eigen::Vector3d &tvec, const Camera &camera)
Definition: projection.cc:111
Eigen::Vector4d ComposeIdentityQuaternion()
Definition: pose.h:209
uint32_t point2D_t
Definition: types.h:67
Eigen::Vector4d RotationMatrixToQuaternion(const Eigen::Matrix3d &rot_mat)
Definition: pose.cc:70
Eigen::Matrix3x4d ComposeProjectionMatrix(const Eigen::Vector4d &qvec, const Eigen::Vector3d &tvec)
Definition: projection.cc:39
static const int kMinNumSamples
double min_inlier_ratio
Definition: ransac.h:29