ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
coordinate_frame.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/gps.h"
35 #include "base/line.h"
36 #include "base/pose.h"
37 #include "base/undistortion.h"
38 #include "estimators/utils.h"
39 #include "optim/ransac.h"
40 #include "util/logging.h"
41 #include "util/misc.h"
42 
43 namespace colmap {
44 namespace {
45 
46 struct VanishingPointEstimator {
47  // The line segments.
48  typedef LineSegment X_t;
49  // The line representation of the segments.
50  typedef Eigen::Vector3d Y_t;
51  // The vanishing point.
52  typedef Eigen::Vector3d M_t;
53 
54  // The minimum number of samples needed to estimate a model.
55  static const int kMinNumSamples = 2;
56 
57  // Estimate the vanishing point from at least two line segments.
58  static std::vector<M_t> Estimate(const std::vector<X_t>& line_segments,
59  const std::vector<Y_t>& lines) {
60  CHECK_EQ(line_segments.size(), 2);
61  CHECK_EQ(lines.size(), 2);
62  return {lines[0].cross(lines[1])};
63  }
64 
65  // Calculate the squared distance of each line segment's end point to the line
66  // connecting the vanishing point and the midpoint of the line segment.
67  static void Residuals(const std::vector<X_t>& line_segments,
68  const std::vector<Y_t>& lines,
69  const M_t& vanishing_point,
70  std::vector<double>* residuals) {
71  residuals->resize(line_segments.size());
72 
73  // Check if vanishing point is at infinity.
74  if (vanishing_point[2] == 0) {
75  std::fill(residuals->begin(), residuals->end(),
76  std::numeric_limits<double>::max());
77  return;
78  }
79 
80  for (size_t i = 0; i < lines.size(); ++i) {
81  const Eigen::Vector3d midpoint =
82  (0.5 * (line_segments[i].start + line_segments[i].end)).homogeneous();
83  const Eigen::Vector3d connecting_line = midpoint.cross(vanishing_point);
84  const double signed_distance =
85  connecting_line.dot(line_segments[i].end.homogeneous()) /
86  connecting_line.head<2>().norm();
87  (*residuals)[i] = signed_distance * signed_distance;
88  }
89  }
90 };
91 
92 Eigen::Vector3d FindBestConsensusAxis(const std::vector<Eigen::Vector3d>& axes,
93  const double max_distance) {
94  if (axes.empty()) {
95  return Eigen::Vector3d::Zero();
96  }
97 
98  std::vector<int> inlier_idxs;
99  inlier_idxs.reserve(axes.size());
100 
101  std::vector<int> best_inlier_idxs;
102  best_inlier_idxs.reserve(axes.size());
103 
104  double best_inlier_distance_sum = std::numeric_limits<double>::max();
105 
106  for (size_t i = 0; i < axes.size(); ++i) {
107  const Eigen::Vector3d ref_axis = axes[i];
108  double inlier_distance_sum = 0;
109  inlier_idxs.clear();
110  for (size_t j = 0; j < axes.size(); ++j) {
111  if (i == j) {
112  inlier_idxs.push_back(j);
113  } else {
114  const double distance = 1 - ref_axis.dot(axes[j]);
115  if (distance <= max_distance) {
116  inlier_distance_sum += distance;
117  inlier_idxs.push_back(j);
118  }
119  }
120  }
121 
122  if (inlier_idxs.size() > best_inlier_idxs.size() ||
123  (inlier_idxs.size() == best_inlier_idxs.size() &&
124  inlier_distance_sum < best_inlier_distance_sum)) {
125  best_inlier_distance_sum = inlier_distance_sum;
126  best_inlier_idxs = inlier_idxs;
127  }
128  }
129 
130  if (best_inlier_idxs.empty()) {
131  return Eigen::Vector3d::Zero();
132  }
133 
134  Eigen::Vector3d best_axis(0, 0, 0);
135  for (const auto idx : best_inlier_idxs) {
136  best_axis += axes[idx];
137  }
138  best_axis /= best_inlier_idxs.size();
139 
140  return best_axis;
141 }
142 
143 } // namespace
144 
146  const Reconstruction& reconstruction, const double max_axis_distance) {
147  std::vector<Eigen::Vector3d> downward_axes;
148  downward_axes.reserve(reconstruction.NumRegImages());
149  for (const auto image_id : reconstruction.RegImageIds()) {
150  const auto& image = reconstruction.Image(image_id);
151  downward_axes.push_back(image.RotationMatrix().row(1));
152  }
153  return FindBestConsensusAxis(downward_axes, max_axis_distance);
154 }
155 
158  const Reconstruction& reconstruction, const std::string& image_path) {
159  std::vector<Eigen::Vector3d> rightward_axes;
160  std::vector<Eigen::Vector3d> downward_axes;
161  for (size_t i = 0; i < reconstruction.NumRegImages(); ++i) {
162  const auto image_id = reconstruction.RegImageIds()[i];
163  const auto& image = reconstruction.Image(image_id);
164  const auto& camera = reconstruction.Camera(image.CameraId());
165 
166  PrintHeading1(StringPrintf("Processing image %s (%d / %d)",
167  image.Name().c_str(), i + 1,
168  reconstruction.NumRegImages()));
169 
170  std::cout << "Reading image..." << std::endl;
171 
172  colmap::Bitmap bitmap;
173  CHECK(bitmap.Read(colmap::JoinPaths(image_path, image.Name())));
174 
175  std::cout << "Undistorting image..." << std::endl;
176 
177  UndistortCameraOptions undistortion_options;
178  undistortion_options.max_image_size = options.max_image_size;
179 
180  Bitmap undistorted_bitmap;
181  Camera undistorted_camera;
182  UndistortImage(undistortion_options, bitmap, camera, &undistorted_bitmap,
183  &undistorted_camera);
184 
185  std::cout << "Detecting lines...";
186 
187  const std::vector<LineSegment> line_segments =
188  DetectLineSegments(undistorted_bitmap, options.min_line_length);
189  const std::vector<LineSegmentOrientation> line_orientations =
190  ClassifyLineSegmentOrientations(line_segments,
192 
193  std::cout << StringPrintf(" %d", line_segments.size());
194 
195  std::vector<LineSegment> horizontal_line_segments;
196  std::vector<LineSegment> vertical_line_segments;
197  std::vector<Eigen::Vector3d> horizontal_lines;
198  std::vector<Eigen::Vector3d> vertical_lines;
199  for (size_t i = 0; i < line_segments.size(); ++i) {
200  const auto line_segment = line_segments[i];
201  const Eigen::Vector3d line_segment_start =
202  line_segment.start.homogeneous();
203  const Eigen::Vector3d line_segment_end = line_segment.end.homogeneous();
204  const Eigen::Vector3d line = line_segment_start.cross(line_segment_end);
205  if (line_orientations[i] == LineSegmentOrientation::HORIZONTAL) {
206  horizontal_line_segments.push_back(line_segment);
207  horizontal_lines.push_back(line);
208  } else if (line_orientations[i] == LineSegmentOrientation::VERTICAL) {
209  vertical_line_segments.push_back(line_segment);
210  vertical_lines.push_back(line);
211  }
212  }
213 
214  std::cout << StringPrintf(" (%d horizontal, %d vertical)",
215  horizontal_lines.size(), vertical_lines.size())
216  << std::endl;
217 
218  std::cout << "Estimating vanishing points...";
219 
220  RANSACOptions ransac_options;
221  ransac_options.max_error = options.max_line_vp_distance;
222  RANSAC<VanishingPointEstimator> ransac(ransac_options);
223  const auto horizontal_report =
224  ransac.Estimate(horizontal_line_segments, horizontal_lines);
225  const auto vertical_report =
226  ransac.Estimate(vertical_line_segments, vertical_lines);
227 
228  std::cout << StringPrintf(" (%d horizontal inliers, %d vertical inliers)",
229  horizontal_report.support.num_inliers,
230  vertical_report.support.num_inliers)
231  << std::endl;
232 
233  std::cout << "Composing coordinate axes..." << std::endl;
234 
235  const Eigen::Matrix3d inv_calib_matrix =
236  undistorted_camera.CalibrationMatrix().inverse();
237  const Eigen::Vector4d inv_qvec = InvertQuaternion(image.Qvec());
238 
239  if (horizontal_report.success) {
240  const Eigen::Vector3d horizontal_camera_axis =
241  (inv_calib_matrix * horizontal_report.model).normalized();
242  Eigen::Vector3d horizontal_axis =
243  QuaternionRotatePoint(inv_qvec, horizontal_camera_axis).normalized();
244  // Make sure all axes point into the same direction.
245  if (rightward_axes.size() > 0 &&
246  rightward_axes[0].dot(horizontal_axis) < 0) {
247  horizontal_axis = -horizontal_axis;
248  }
249  rightward_axes.push_back(horizontal_axis);
250  std::cout << " Horizontal: " << horizontal_axis.transpose() << std::endl;
251  }
252 
253  if (vertical_report.success) {
254  const Eigen::Vector3d vertical_camera_axis =
255  (inv_calib_matrix * vertical_report.model).normalized();
256  Eigen::Vector3d vertical_axis =
257  QuaternionRotatePoint(inv_qvec, vertical_camera_axis).normalized();
258  // Make sure axis points downwards in the image, assuming that the image
259  // was taken in upright orientation.
260  if (vertical_camera_axis.dot(Eigen::Vector3d(0, 1, 0)) < 0) {
261  vertical_axis = -vertical_axis;
262  }
263  downward_axes.push_back(vertical_axis);
264  std::cout << " Vertical: " << vertical_axis.transpose() << std::endl;
265  }
266  }
267 
268  PrintHeading1("Computing coordinate frame");
269 
270  Eigen::Matrix3d frame = Eigen::Matrix3d::Zero();
271 
272  if (rightward_axes.size() > 0) {
273  frame.col(0) =
274  FindBestConsensusAxis(rightward_axes, options.max_axis_distance);
275  }
276 
277  std::cout << "Found rightward axis: " << frame.col(0).transpose()
278  << std::endl;
279 
280  if (downward_axes.size() > 0) {
281  frame.col(1) =
282  FindBestConsensusAxis(downward_axes, options.max_axis_distance);
283  }
284 
285  std::cout << "Found downward axis: " << frame.col(1).transpose() << std::endl;
286 
287  if (rightward_axes.size() > 0 && downward_axes.size() > 0) {
288  frame.col(2) = frame.col(0).cross(frame.col(1));
289  Eigen::JacobiSVD<Eigen::Matrix3d> svd(
290  frame, Eigen::ComputeFullV | Eigen::ComputeFullU);
291  const Eigen::Matrix3d orthonormal_frame =
292  svd.matrixU() * Eigen::Matrix3d::Identity() * svd.matrixV().transpose();
293  frame = orthonormal_frame;
294  }
295 
296  std::cout << "Found orthonormal frame: " << std::endl;
297  std::cout << frame << std::endl;
298 
299  return frame;
300 }
301 
303  // Perform SVD on the 3D points to estimate the ground plane basis
304  const Eigen::Vector3d centroid = recon->ComputeCentroid(0.0, 1.0);
305  Eigen::MatrixXd points(3, recon->NumPoints3D());
306  int pidx = 0;
307  for (const auto& point : recon->Points3D()) {
308  points.col(pidx++) = point.second.XYZ() - centroid;
309  }
310  const Eigen::Matrix3d basis =
311  points.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).matrixU();
312  Eigen::Matrix3d rot_mat;
313  rot_mat << basis.col(0), basis.col(1), basis.col(0).cross(basis.col(1));
314  rot_mat.transposeInPlace();
315 
316  *tform = SimilarityTransform3(1.0, RotationMatrixToQuaternion(rot_mat),
317  -rot_mat * centroid);
318 
319  // if camera plane ends up below ground then flip basis vectors and create new
320  // transform
321  Image test_img = recon->Images().begin()->second;
322  tform->TransformPose(&test_img.Qvec(), &test_img.Tvec());
323  if (test_img.ProjectionCenter().z() < 0.0) {
324  rot_mat << basis.col(0), -basis.col(1), basis.col(0).cross(-basis.col(1));
325  rot_mat.transposeInPlace();
326  *tform = SimilarityTransform3(1.0, RotationMatrixToQuaternion(rot_mat),
327  -rot_mat * centroid);
328  }
329 
330  recon->Transform(*tform);
331 }
332 
334  bool unscaled) {
335  const Eigen::Vector3d centroid = recon->ComputeCentroid(0.0, 1.0);
336  GPSTransform gps_tform;
337  const Eigen::Vector3d ell_centroid = gps_tform.XYZToEll({centroid}).at(0);
338 
339  // Create rotation matrix from ECEF to ENU coordinates
340  const double sin_lat = sin(DegToRad(ell_centroid(0)));
341  const double sin_lon = sin(DegToRad(ell_centroid(1)));
342  const double cos_lat = cos(DegToRad(ell_centroid(0)));
343  const double cos_lon = cos(DegToRad(ell_centroid(1)));
344 
345  // Create ECEF to ENU rotation matrix
346  Eigen::Matrix3d rot_mat;
347  rot_mat << -sin_lon, cos_lon, 0, -cos_lon * sin_lat, -sin_lon * sin_lat,
348  cos_lat, cos_lon * cos_lat, sin_lon * cos_lat, sin_lat;
349 
350  const double scale = unscaled ? 1.0 / tform->Scale() : 1.0;
351  *tform = SimilarityTransform3(scale, RotationMatrixToQuaternion(rot_mat),
352  -(scale * rot_mat) * centroid);
353  recon->Transform(*tform);
354 }
355 
356 } // namespace colmap
Rect frame
std::shared_ptr< core::Tensor > image
int points
bool Read(const std::string &path, const bool as_rgb=true)
Definition: bitmap.cc:485
Eigen::Matrix3d CalibrationMatrix() const
Definition: camera.cc:75
std::vector< Eigen::Vector3d > XYZToEll(const std::vector< Eigen::Vector3d > &xyz) const
Definition: gps.cc:82
const Eigen::Vector3d & Tvec() const
Definition: image.h:325
const Eigen::Vector4d & Qvec() const
Definition: image.h:301
Eigen::Vector3d ProjectionCenter() const
Definition: image.cc:152
Report Estimate(const std::vector< typename Estimator::X_t > &X, const std::vector< typename Estimator::Y_t > &Y)
Definition: ransac.h:159
size_t NumRegImages() const
void Transform(const SimilarityTransform3 &tform)
const std::unordered_map< image_t, class Image > & Images() const
Eigen::Vector3d ComputeCentroid(const double p0=0.1, const double p1=0.9) const
const class Image & Image(const image_t image_id) const
size_t NumPoints3D() const
const class Camera & Camera(const camera_t camera_id) const
const std::unordered_map< point3D_t, class Point3D > & Points3D() const
const std::vector< image_t > & RegImageIds() const
void TransformPose(Eigen::Vector4d *qvec, Eigen::Vector3d *tvec) const
static const int kMinNumSamples
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static double distance(T *pot1, T *pot2)
Definition: utils.h:111
void UndistortImage(const UndistortCameraOptions &options, const Bitmap &distorted_bitmap, const Camera &distorted_camera, Bitmap *undistorted_bitmap, Camera *undistorted_camera)
std::vector< LineSegment > DetectLineSegments(const Bitmap &bitmap, const double min_length)
Definition: line.cc:42
Eigen::Vector4d RotationMatrixToQuaternion(const Eigen::Matrix3d &rot_mat)
Definition: pose.cc:70
Eigen::Matrix3d EstimateManhattanWorldFrame(const ManhattanWorldFrameEstimationOptions &options, const Reconstruction &reconstruction, const std::string &image_path)
Eigen::Vector4d InvertQuaternion(const Eigen::Vector4d &qvec)
Definition: pose.cc:93
std::string JoinPaths(T const &... paths)
Definition: misc.h:128
std::vector< LineSegmentOrientation > ClassifyLineSegmentOrientations(const std::vector< LineSegment > &segments, const double tolerance)
Definition: line.cc:79
Eigen::Vector3d EstimateGravityVectorFromImageOrientation(const Reconstruction &reconstruction, const double max_axis_distance)
Eigen::Vector3d QuaternionRotatePoint(const Eigen::Vector4d &qvec, const Eigen::Vector3d &point)
Definition: pose.cc:110
void PrintHeading1(const std::string &heading)
Definition: misc.cc:225
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
float DegToRad(const float deg)
Definition: math.h:171
void AlignToENUPlane(Reconstruction *recon, SimilarityTransform3 *tform, bool unscaled)
void AlignToPrincipalPlane(Reconstruction *recon, SimilarityTransform3 *tform)
Rgb at(size_t color_id)