blob: 9315ed79ff6f32f748953ed553be8317284f976c [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Copyright (c) 2013 libmv authors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to
5// deal in the Software without restriction, including without limitation the
6// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7// sell copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19// IN THE SOFTWARE.
20//
21// Author: mierle@gmail.com (Keir Mierle)
22// sergey.vfx@gmail.com (Sergey Sharybin)
23//
24// This is an example application which contains bundle adjustment code used
25// in the Libmv library and Blender. It reads problems from files passed via
26// the command line and runs the bundle adjuster on the problem.
27//
28// File with problem a binary file, for which it is crucial to know in which
29// order bytes of float values are stored in. This information is provided
30// by a single character in the beginning of the file. There're two possible
31// values of this byte:
32// - V, which means values in the file are stored with big endian type
33// - v, which means values in the file are stored with little endian type
34//
35// The rest of the file contains data in the following order:
36// - Space in which markers' coordinates are stored in
37// - Camera intrinsics
38// - Number of cameras
39// - Cameras
40// - Number of 3D points
41// - 3D points
42// - Number of markers
43// - Markers
44//
45// Markers' space could either be normalized or image (pixels). This is defined
46// by the single character in the file. P means markers in the file is in image
47// space, and N means markers are in normalized space.
48//
49// Camera intrinsics are 8 described by 8 float 8.
50// This values goes in the following order:
51//
52// - Focal length, principal point X, principal point Y, k1, k2, k3, p1, p2
53//
54// Every camera is described by:
55//
56// - Image for which camera belongs to (single 4 bytes integer value).
57// - Column-major camera rotation matrix, 9 float values.
58// - Camera translation, 3-component vector of float values.
59//
60// Image number shall be greater or equal to zero. Order of cameras does not
61// matter and gaps are possible.
62//
Austin Schuh3de38b02024-06-25 18:25:10 -070063// Every 3D point is described by:
Austin Schuh70cc9552019-01-21 19:46:48 -080064//
65// - Track number point belongs to (single 4 bytes integer value).
66// - 3D position vector, 3-component vector of float values.
67//
68// Track number shall be greater or equal to zero. Order of tracks does not
69// matter and gaps are possible.
70//
71// Finally every marker is described by:
72//
73// - Image marker belongs to single 4 bytes integer value).
74// - Track marker belongs to single 4 bytes integer value).
75// - 2D marker position vector, (two float values).
76//
77// Marker's space is used a default value for refine_intrinsics command line
78// flag. This means if there's no refine_intrinsics flag passed via command
79// line, camera intrinsics will be refined if markers in the problem are
80// stored in image space and camera intrinsics will not be refined if markers
81// are in normalized space.
82//
83// Passing refine_intrinsics command line flag defines explicitly whether
84// refinement of intrinsics will happen. Currently, only none and all
85// intrinsics refinement is supported.
86//
87// There're existing problem files dumped from blender stored in folder
88// ../data/libmv-ba-problems.
89
Austin Schuh70cc9552019-01-21 19:46:48 -080090#include <fcntl.h>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080091
92#include <cstdio>
Austin Schuh70cc9552019-01-21 19:46:48 -080093#include <sstream>
94#include <string>
95#include <vector>
96
97#ifdef _MSC_VER
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080098#include <io.h>
99#define open _open
100#define close _close
Austin Schuh70cc9552019-01-21 19:46:48 -0800101typedef unsigned __int32 uint32_t;
102#else
Austin Schuh70cc9552019-01-21 19:46:48 -0800103#include <unistd.h>
104
Austin Schuh3de38b02024-06-25 18:25:10 -0700105#include <cstdint>
106
107// NOTE MinGW does define the macro.
108#ifndef O_BINARY
Austin Schuh70cc9552019-01-21 19:46:48 -0800109// O_BINARY is not defined on unix like platforms, as there is no
110// difference between binary and text files.
111#define O_BINARY 0
Austin Schuh3de38b02024-06-25 18:25:10 -0700112#endif
Austin Schuh70cc9552019-01-21 19:46:48 -0800113
114#endif
115
116#include "ceres/ceres.h"
117#include "ceres/rotation.h"
118#include "gflags/gflags.h"
119#include "glog/logging.h"
120
Austin Schuh3de38b02024-06-25 18:25:10 -0700121using Mat3 = Eigen::Matrix<double, 3, 3>;
122using Vec6 = Eigen::Matrix<double, 6, 1>;
123using Vec3 = Eigen::Vector3d;
124using Vec4 = Eigen::Vector4d;
Austin Schuh70cc9552019-01-21 19:46:48 -0800125
126DEFINE_string(input, "", "Input File name");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800127DEFINE_string(refine_intrinsics,
128 "",
129 "Camera intrinsics to be refined. Options are: none, radial.");
Austin Schuh70cc9552019-01-21 19:46:48 -0800130
131namespace {
132
133// A EuclideanCamera is the location and rotation of the camera
134// viewing an image.
135//
136// image identifies which image this camera represents.
137// R is a 3x3 matrix representing the rotation of the camera.
138// t is a translation vector representing its positions.
139struct EuclideanCamera {
Austin Schuh3de38b02024-06-25 18:25:10 -0700140 EuclideanCamera() = default;
141 EuclideanCamera(const EuclideanCamera& c) = default;
Austin Schuh70cc9552019-01-21 19:46:48 -0800142
Austin Schuh3de38b02024-06-25 18:25:10 -0700143 int image{-1};
Austin Schuh70cc9552019-01-21 19:46:48 -0800144 Mat3 R;
145 Vec3 t;
146};
147
148// A Point is the 3D location of a track.
149//
150// track identifies which track this point corresponds to.
151// X represents the 3D position of the track.
152struct EuclideanPoint {
Austin Schuh3de38b02024-06-25 18:25:10 -0700153 EuclideanPoint() = default;
154 EuclideanPoint(const EuclideanPoint& p) = default;
155 int track{-1};
Austin Schuh70cc9552019-01-21 19:46:48 -0800156 Vec3 X;
157};
158
159// A Marker is the 2D location of a tracked point in an image.
160//
161// x and y is the position of the marker in pixels from the top left corner
162// in the image identified by an image. All markers for to the same target
163// form a track identified by a common track number.
164struct Marker {
165 int image;
166 int track;
167 double x, y;
168};
169
170// Cameras intrinsics to be bundled.
171//
172// BUNDLE_RADIAL actually implies bundling of k1 and k2 coefficients only,
173// no bundling of k3 is possible at this moment.
174enum BundleIntrinsics {
175 BUNDLE_NO_INTRINSICS = 0,
176 BUNDLE_FOCAL_LENGTH = 1,
177 BUNDLE_PRINCIPAL_POINT = 2,
178 BUNDLE_RADIAL_K1 = 4,
179 BUNDLE_RADIAL_K2 = 8,
180 BUNDLE_RADIAL = 12,
181 BUNDLE_TANGENTIAL_P1 = 16,
182 BUNDLE_TANGENTIAL_P2 = 32,
183 BUNDLE_TANGENTIAL = 48,
184};
185
186// Denotes which blocks to keep constant during bundling.
187// For example it is useful to keep camera translations constant
188// when bundling tripod motions.
189enum BundleConstraints {
190 BUNDLE_NO_CONSTRAINTS = 0,
191 BUNDLE_NO_TRANSLATION = 1,
192};
193
194// The intrinsics need to get combined into a single parameter block; use these
195// enums to index instead of numeric constants.
196enum {
197 OFFSET_FOCAL_LENGTH,
198 OFFSET_PRINCIPAL_POINT_X,
199 OFFSET_PRINCIPAL_POINT_Y,
200 OFFSET_K1,
201 OFFSET_K2,
202 OFFSET_K3,
203 OFFSET_P1,
204 OFFSET_P2,
205};
206
207// Returns a pointer to the camera corresponding to a image.
Austin Schuh3de38b02024-06-25 18:25:10 -0700208EuclideanCamera* CameraForImage(std::vector<EuclideanCamera>* all_cameras,
Austin Schuh70cc9552019-01-21 19:46:48 -0800209 const int image) {
210 if (image < 0 || image >= all_cameras->size()) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700211 return nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800212 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800213 EuclideanCamera* camera = &(*all_cameras)[image];
Austin Schuh70cc9552019-01-21 19:46:48 -0800214 if (camera->image == -1) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700215 return nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800216 }
217 return camera;
218}
219
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800220const EuclideanCamera* CameraForImage(
Austin Schuh3de38b02024-06-25 18:25:10 -0700221 const std::vector<EuclideanCamera>& all_cameras, const int image) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800222 if (image < 0 || image >= all_cameras.size()) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700223 return nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800224 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800225 const EuclideanCamera* camera = &all_cameras[image];
Austin Schuh70cc9552019-01-21 19:46:48 -0800226 if (camera->image == -1) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700227 return nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800228 }
229 return camera;
230}
231
232// Returns maximal image number at which marker exists.
Austin Schuh3de38b02024-06-25 18:25:10 -0700233int MaxImage(const std::vector<Marker>& all_markers) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800234 if (all_markers.size() == 0) {
235 return -1;
236 }
237
238 int max_image = all_markers[0].image;
239 for (int i = 1; i < all_markers.size(); i++) {
240 max_image = std::max(max_image, all_markers[i].image);
241 }
242 return max_image;
243}
244
245// Returns a pointer to the point corresponding to a track.
Austin Schuh3de38b02024-06-25 18:25:10 -0700246EuclideanPoint* PointForTrack(std::vector<EuclideanPoint>* all_points,
Austin Schuh70cc9552019-01-21 19:46:48 -0800247 const int track) {
248 if (track < 0 || track >= all_points->size()) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700249 return nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800250 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800251 EuclideanPoint* point = &(*all_points)[track];
Austin Schuh70cc9552019-01-21 19:46:48 -0800252 if (point->track == -1) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700253 return nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800254 }
255 return point;
256}
257
258// Reader of binary file which makes sure possibly needed endian
259// conversion happens when loading values like floats and integers.
260//
261// File's endian type is reading from a first character of file, which
262// could either be V for big endian or v for little endian. This
263// means you need to design file format assuming first character
264// denotes file endianness in this way.
265class EndianAwareFileReader {
266 public:
Austin Schuh3de38b02024-06-25 18:25:10 -0700267 EndianAwareFileReader() {
Austin Schuh70cc9552019-01-21 19:46:48 -0800268 // Get an endian type of the host machine.
269 union {
270 unsigned char bytes[4];
271 uint32_t value;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800272 } endian_test = {{0, 1, 2, 3}};
Austin Schuh70cc9552019-01-21 19:46:48 -0800273 host_endian_type_ = endian_test.value;
274 file_endian_type_ = host_endian_type_;
275 }
276
Austin Schuh3de38b02024-06-25 18:25:10 -0700277 ~EndianAwareFileReader() {
Austin Schuh70cc9552019-01-21 19:46:48 -0800278 if (file_descriptor_ > 0) {
279 close(file_descriptor_);
280 }
281 }
282
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800283 bool OpenFile(const std::string& file_name) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800284 file_descriptor_ = open(file_name.c_str(), O_RDONLY | O_BINARY);
285 if (file_descriptor_ < 0) {
286 return false;
287 }
288 // Get an endian tpye of data in the file.
Austin Schuh3de38b02024-06-25 18:25:10 -0700289 auto file_endian_type_flag = Read<unsigned char>();
Austin Schuh70cc9552019-01-21 19:46:48 -0800290 if (file_endian_type_flag == 'V') {
291 file_endian_type_ = kBigEndian;
292 } else if (file_endian_type_flag == 'v') {
293 file_endian_type_ = kLittleEndian;
294 } else {
295 LOG(FATAL) << "Problem file is stored in unknown endian type.";
296 }
297 return true;
298 }
299
300 // Read value from the file, will switch endian if needed.
301 template <typename T>
Austin Schuh3de38b02024-06-25 18:25:10 -0700302 T Read() const {
Austin Schuh70cc9552019-01-21 19:46:48 -0800303 T value;
Austin Schuh3de38b02024-06-25 18:25:10 -0700304 CERES_DISABLE_DEPRECATED_WARNING
Austin Schuh70cc9552019-01-21 19:46:48 -0800305 CHECK_GT(read(file_descriptor_, &value, sizeof(value)), 0);
Austin Schuh3de38b02024-06-25 18:25:10 -0700306 CERES_RESTORE_DEPRECATED_WARNING
Austin Schuh70cc9552019-01-21 19:46:48 -0800307 // Switch endian type if file contains data in different type
308 // that current machine.
309 if (file_endian_type_ != host_endian_type_) {
310 value = SwitchEndian<T>(value);
311 }
312 return value;
313 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800314
Austin Schuh70cc9552019-01-21 19:46:48 -0800315 private:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800316 static constexpr long int kLittleEndian = 0x03020100ul;
317 static constexpr long int kBigEndian = 0x00010203ul;
Austin Schuh70cc9552019-01-21 19:46:48 -0800318
319 // Switch endian type between big to little.
320 template <typename T>
321 T SwitchEndian(const T value) const {
322 if (sizeof(T) == 4) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700323 auto temp_value = static_cast<unsigned int>(value);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800324 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800325 return ((temp_value >> 24)) |
326 ((temp_value << 8) & 0x00ff0000) |
327 ((temp_value >> 8) & 0x0000ff00) |
328 ((temp_value << 24));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800329 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800330 } else if (sizeof(T) == 1) {
331 return value;
332 } else {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800333 LOG(FATAL) << "Entered non-implemented part of endian "
334 "switching function.";
Austin Schuh70cc9552019-01-21 19:46:48 -0800335 }
336 }
337
338 int host_endian_type_;
339 int file_endian_type_;
Austin Schuh3de38b02024-06-25 18:25:10 -0700340 int file_descriptor_{-1};
Austin Schuh70cc9552019-01-21 19:46:48 -0800341};
342
343// Read 3x3 column-major matrix from the file
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800344void ReadMatrix3x3(const EndianAwareFileReader& file_reader, Mat3* matrix) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800345 for (int i = 0; i < 9; i++) {
346 (*matrix)(i % 3, i / 3) = file_reader.Read<float>();
347 }
348}
349
350// Read 3-vector from file
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800351void ReadVector3(const EndianAwareFileReader& file_reader, Vec3* vector) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800352 for (int i = 0; i < 3; i++) {
353 (*vector)(i) = file_reader.Read<float>();
354 }
355}
356
357// Reads a bundle adjustment problem from the file.
358//
359// file_name denotes from which file to read the problem.
360// camera_intrinsics will contain initial camera intrinsics values.
361//
362// all_cameras is a vector of all reconstructed cameras to be optimized,
363// vector element with number i will contain camera for image i.
364//
365// all_points is a vector of all reconstructed 3D points to be optimized,
366// vector element with number i will contain point for track i.
367//
368// all_markers is a vector of all tracked markers existing in
369// the problem. Only used for reprojection error calculation, stay
370// unchanged during optimization.
371//
372// Returns false if any kind of error happened during
373// reading.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800374bool ReadProblemFromFile(const std::string& file_name,
Austin Schuh70cc9552019-01-21 19:46:48 -0800375 double camera_intrinsics[8],
Austin Schuh3de38b02024-06-25 18:25:10 -0700376 std::vector<EuclideanCamera>* all_cameras,
377 std::vector<EuclideanPoint>* all_points,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800378 bool* is_image_space,
Austin Schuh3de38b02024-06-25 18:25:10 -0700379 std::vector<Marker>* all_markers) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800380 EndianAwareFileReader file_reader;
381 if (!file_reader.OpenFile(file_name)) {
382 return false;
383 }
384
385 // Read markers' space flag.
Austin Schuh3de38b02024-06-25 18:25:10 -0700386 auto is_image_space_flag = file_reader.Read<unsigned char>();
Austin Schuh70cc9552019-01-21 19:46:48 -0800387 if (is_image_space_flag == 'P') {
388 *is_image_space = true;
389 } else if (is_image_space_flag == 'N') {
390 *is_image_space = false;
391 } else {
392 LOG(FATAL) << "Problem file contains markers stored in unknown space.";
393 }
394
395 // Read camera intrinsics.
396 for (int i = 0; i < 8; i++) {
397 camera_intrinsics[i] = file_reader.Read<float>();
398 }
399
400 // Read all cameras.
401 int number_of_cameras = file_reader.Read<int>();
402 for (int i = 0; i < number_of_cameras; i++) {
403 EuclideanCamera camera;
404
405 camera.image = file_reader.Read<int>();
406 ReadMatrix3x3(file_reader, &camera.R);
407 ReadVector3(file_reader, &camera.t);
408
409 if (camera.image >= all_cameras->size()) {
410 all_cameras->resize(camera.image + 1);
411 }
412
413 (*all_cameras)[camera.image].image = camera.image;
414 (*all_cameras)[camera.image].R = camera.R;
415 (*all_cameras)[camera.image].t = camera.t;
416 }
417
418 LOG(INFO) << "Read " << number_of_cameras << " cameras.";
419
420 // Read all reconstructed 3D points.
421 int number_of_points = file_reader.Read<int>();
422 for (int i = 0; i < number_of_points; i++) {
423 EuclideanPoint point;
424
425 point.track = file_reader.Read<int>();
426 ReadVector3(file_reader, &point.X);
427
428 if (point.track >= all_points->size()) {
429 all_points->resize(point.track + 1);
430 }
431
432 (*all_points)[point.track].track = point.track;
433 (*all_points)[point.track].X = point.X;
434 }
435
436 LOG(INFO) << "Read " << number_of_points << " points.";
437
438 // And finally read all markers.
439 int number_of_markers = file_reader.Read<int>();
440 for (int i = 0; i < number_of_markers; i++) {
441 Marker marker;
442
443 marker.image = file_reader.Read<int>();
444 marker.track = file_reader.Read<int>();
445 marker.x = file_reader.Read<float>();
446 marker.y = file_reader.Read<float>();
447
448 all_markers->push_back(marker);
449 }
450
451 LOG(INFO) << "Read " << number_of_markers << " markers.";
452
453 return true;
454}
455
456// Apply camera intrinsics to the normalized point to get image coordinates.
457// This applies the radial lens distortion to a point which is in normalized
458// camera coordinates (i.e. the principal point is at (0, 0)) to get image
459// coordinates in pixels. Templated for use with autodifferentiation.
460template <typename T>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800461inline void ApplyRadialDistortionCameraIntrinsics(const T& focal_length_x,
462 const T& focal_length_y,
463 const T& principal_point_x,
464 const T& principal_point_y,
465 const T& k1,
466 const T& k2,
467 const T& k3,
468 const T& p1,
469 const T& p2,
470 const T& normalized_x,
471 const T& normalized_y,
472 T* image_x,
473 T* image_y) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800474 T x = normalized_x;
475 T y = normalized_y;
476
477 // Apply distortion to the normalized points to get (xd, yd).
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800478 T r2 = x * x + y * y;
Austin Schuh70cc9552019-01-21 19:46:48 -0800479 T r4 = r2 * r2;
480 T r6 = r4 * r2;
481 T r_coeff = 1.0 + k1 * r2 + k2 * r4 + k3 * r6;
482 T xd = x * r_coeff + 2.0 * p1 * x * y + p2 * (r2 + 2.0 * x * x);
483 T yd = y * r_coeff + 2.0 * p2 * x * y + p1 * (r2 + 2.0 * y * y);
484
485 // Apply focal length and principal point to get the final image coordinates.
486 *image_x = focal_length_x * xd + principal_point_x;
487 *image_y = focal_length_y * yd + principal_point_y;
488}
489
490// Cost functor which computes reprojection error of 3D point X
491// on camera defined by angle-axis rotation and it's translation
492// (which are in the same block due to optimization reasons).
493//
494// This functor uses a radial distortion model.
495struct OpenCVReprojectionError {
496 OpenCVReprojectionError(const double observed_x, const double observed_y)
497 : observed_x(observed_x), observed_y(observed_y) {}
498
499 template <typename T>
500 bool operator()(const T* const intrinsics,
501 const T* const R_t, // Rotation denoted by angle axis
502 // followed with translation
503 const T* const X, // Point coordinates 3x1.
504 T* residuals) const {
505 // Unpack the intrinsics.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800506 const T& focal_length = intrinsics[OFFSET_FOCAL_LENGTH];
Austin Schuh70cc9552019-01-21 19:46:48 -0800507 const T& principal_point_x = intrinsics[OFFSET_PRINCIPAL_POINT_X];
508 const T& principal_point_y = intrinsics[OFFSET_PRINCIPAL_POINT_Y];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800509 const T& k1 = intrinsics[OFFSET_K1];
510 const T& k2 = intrinsics[OFFSET_K2];
511 const T& k3 = intrinsics[OFFSET_K3];
512 const T& p1 = intrinsics[OFFSET_P1];
513 const T& p2 = intrinsics[OFFSET_P2];
Austin Schuh70cc9552019-01-21 19:46:48 -0800514
515 // Compute projective coordinates: x = RX + t.
516 T x[3];
517
518 ceres::AngleAxisRotatePoint(R_t, X, x);
519 x[0] += R_t[3];
520 x[1] += R_t[4];
521 x[2] += R_t[5];
522
523 // Compute normalized coordinates: x /= x[2].
524 T xn = x[0] / x[2];
525 T yn = x[1] / x[2];
526
527 T predicted_x, predicted_y;
528
529 // Apply distortion to the normalized points to get (xd, yd).
530 // TODO(keir): Do early bailouts for zero distortion; these are expensive
531 // jet operations.
532 ApplyRadialDistortionCameraIntrinsics(focal_length,
533 focal_length,
534 principal_point_x,
535 principal_point_y,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800536 k1,
537 k2,
538 k3,
539 p1,
540 p2,
541 xn,
542 yn,
Austin Schuh70cc9552019-01-21 19:46:48 -0800543 &predicted_x,
544 &predicted_y);
545
546 // The error is the difference between the predicted and observed position.
547 residuals[0] = predicted_x - observed_x;
548 residuals[1] = predicted_y - observed_y;
549
550 return true;
551 }
552
553 const double observed_x;
554 const double observed_y;
555};
556
557// Print a message to the log which camera intrinsics are gonna to be optimized.
558void BundleIntrinsicsLogMessage(const int bundle_intrinsics) {
559 if (bundle_intrinsics == BUNDLE_NO_INTRINSICS) {
560 LOG(INFO) << "Bundling only camera positions.";
561 } else {
562 std::string bundling_message = "";
563
564#define APPEND_BUNDLING_INTRINSICS(name, flag) \
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800565 if (bundle_intrinsics & flag) { \
566 if (!bundling_message.empty()) { \
567 bundling_message += ", "; \
568 } \
569 bundling_message += name; \
570 } \
571 (void)0
Austin Schuh70cc9552019-01-21 19:46:48 -0800572
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800573 APPEND_BUNDLING_INTRINSICS("f", BUNDLE_FOCAL_LENGTH);
Austin Schuh70cc9552019-01-21 19:46:48 -0800574 APPEND_BUNDLING_INTRINSICS("px, py", BUNDLE_PRINCIPAL_POINT);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800575 APPEND_BUNDLING_INTRINSICS("k1", BUNDLE_RADIAL_K1);
576 APPEND_BUNDLING_INTRINSICS("k2", BUNDLE_RADIAL_K2);
577 APPEND_BUNDLING_INTRINSICS("p1", BUNDLE_TANGENTIAL_P1);
578 APPEND_BUNDLING_INTRINSICS("p2", BUNDLE_TANGENTIAL_P2);
Austin Schuh70cc9552019-01-21 19:46:48 -0800579
580 LOG(INFO) << "Bundling " << bundling_message << ".";
581 }
582}
583
584// Print a message to the log containing all the camera intriniscs values.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800585void PrintCameraIntrinsics(const char* text, const double* camera_intrinsics) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800586 std::ostringstream intrinsics_output;
587
588 intrinsics_output << "f=" << camera_intrinsics[OFFSET_FOCAL_LENGTH];
589
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800590 intrinsics_output << " cx=" << camera_intrinsics[OFFSET_PRINCIPAL_POINT_X]
591 << " cy=" << camera_intrinsics[OFFSET_PRINCIPAL_POINT_Y];
Austin Schuh70cc9552019-01-21 19:46:48 -0800592
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800593#define APPEND_DISTORTION_COEFFICIENT(name, offset) \
594 { \
595 if (camera_intrinsics[offset] != 0.0) { \
596 intrinsics_output << " " name "=" << camera_intrinsics[offset]; \
597 } \
598 } \
599 (void)0
Austin Schuh70cc9552019-01-21 19:46:48 -0800600
601 APPEND_DISTORTION_COEFFICIENT("k1", OFFSET_K1);
602 APPEND_DISTORTION_COEFFICIENT("k2", OFFSET_K2);
603 APPEND_DISTORTION_COEFFICIENT("k3", OFFSET_K3);
604 APPEND_DISTORTION_COEFFICIENT("p1", OFFSET_P1);
605 APPEND_DISTORTION_COEFFICIENT("p2", OFFSET_P2);
606
607#undef APPEND_DISTORTION_COEFFICIENT
608
609 LOG(INFO) << text << intrinsics_output.str();
610}
611
612// Get a vector of camera's rotations denoted by angle axis
613// conjuncted with translations into single block
614//
615// Element with index i matches to a rotation+translation for
616// camera at image i.
Austin Schuh3de38b02024-06-25 18:25:10 -0700617std::vector<Vec6> PackCamerasRotationAndTranslation(
618 const std::vector<Marker>& all_markers,
619 const std::vector<EuclideanCamera>& all_cameras) {
620 std::vector<Vec6> all_cameras_R_t;
Austin Schuh70cc9552019-01-21 19:46:48 -0800621 int max_image = MaxImage(all_markers);
622
623 all_cameras_R_t.resize(max_image + 1);
624
625 for (int i = 0; i <= max_image; i++) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800626 const EuclideanCamera* camera = CameraForImage(all_cameras, i);
Austin Schuh70cc9552019-01-21 19:46:48 -0800627
628 if (!camera) {
629 continue;
630 }
631
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800632 ceres::RotationMatrixToAngleAxis(&camera->R(0, 0), &all_cameras_R_t[i](0));
Austin Schuh70cc9552019-01-21 19:46:48 -0800633 all_cameras_R_t[i].tail<3>() = camera->t;
634 }
635
636 return all_cameras_R_t;
637}
638
639// Convert cameras rotations fro mangle axis back to rotation matrix.
Austin Schuh3de38b02024-06-25 18:25:10 -0700640void UnpackCamerasRotationAndTranslation(
641 const std::vector<Marker>& all_markers,
642 const std::vector<Vec6>& all_cameras_R_t,
643 std::vector<EuclideanCamera>* all_cameras) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800644 int max_image = MaxImage(all_markers);
645
646 for (int i = 0; i <= max_image; i++) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800647 EuclideanCamera* camera = CameraForImage(all_cameras, i);
Austin Schuh70cc9552019-01-21 19:46:48 -0800648
649 if (!camera) {
650 continue;
651 }
652
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800653 ceres::AngleAxisToRotationMatrix(&all_cameras_R_t[i](0), &camera->R(0, 0));
Austin Schuh70cc9552019-01-21 19:46:48 -0800654 camera->t = all_cameras_R_t[i].tail<3>();
655 }
656}
657
Austin Schuh3de38b02024-06-25 18:25:10 -0700658void EuclideanBundleCommonIntrinsics(const std::vector<Marker>& all_markers,
Austin Schuh70cc9552019-01-21 19:46:48 -0800659 const int bundle_intrinsics,
660 const int bundle_constraints,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800661 double* camera_intrinsics,
Austin Schuh3de38b02024-06-25 18:25:10 -0700662 std::vector<EuclideanCamera>* all_cameras,
663 std::vector<EuclideanPoint>* all_points) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800664 PrintCameraIntrinsics("Original intrinsics: ", camera_intrinsics);
665
666 ceres::Problem::Options problem_options;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800667 problem_options.cost_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP;
Austin Schuh70cc9552019-01-21 19:46:48 -0800668 ceres::Problem problem(problem_options);
669
670 // Convert cameras rotations to angle axis and merge with translation
671 // into single parameter block for maximal minimization speed
672 //
673 // Block for minimization has got the following structure:
674 // <3 elements for angle-axis> <3 elements for translation>
Austin Schuh3de38b02024-06-25 18:25:10 -0700675 std::vector<Vec6> all_cameras_R_t =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800676 PackCamerasRotationAndTranslation(all_markers, *all_cameras);
Austin Schuh70cc9552019-01-21 19:46:48 -0800677
Austin Schuh3de38b02024-06-25 18:25:10 -0700678 // Manifold used to restrict camera motion for modal solvers.
679 ceres::SubsetManifold* constant_transform_manifold = nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800680 if (bundle_constraints & BUNDLE_NO_TRANSLATION) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800681 std::vector<int> constant_translation;
Austin Schuh70cc9552019-01-21 19:46:48 -0800682
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800683 // First three elements are rotation, last three are translation.
684 constant_translation.push_back(3);
685 constant_translation.push_back(4);
686 constant_translation.push_back(5);
Austin Schuh70cc9552019-01-21 19:46:48 -0800687
Austin Schuh3de38b02024-06-25 18:25:10 -0700688 constant_transform_manifold =
689 new ceres::SubsetManifold(6, constant_translation);
Austin Schuh70cc9552019-01-21 19:46:48 -0800690 }
691
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800692 std::vector<OpenCVReprojectionError> errors;
693 std::vector<ceres::AutoDiffCostFunction<OpenCVReprojectionError, 2, 8, 6, 3>>
694 costFunctions;
695 errors.reserve(all_markers.size());
696 costFunctions.reserve(all_markers.size());
697
Austin Schuh70cc9552019-01-21 19:46:48 -0800698 int num_residuals = 0;
699 bool have_locked_camera = false;
Austin Schuh3de38b02024-06-25 18:25:10 -0700700 for (const auto& marker : all_markers) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800701 EuclideanCamera* camera = CameraForImage(all_cameras, marker.image);
702 EuclideanPoint* point = PointForTrack(all_points, marker.track);
Austin Schuh3de38b02024-06-25 18:25:10 -0700703 if (camera == nullptr || point == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800704 continue;
705 }
706
707 // Rotation of camera denoted in angle axis followed with
708 // camera translaiton.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800709 double* current_camera_R_t = &all_cameras_R_t[camera->image](0);
Austin Schuh70cc9552019-01-21 19:46:48 -0800710
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800711 errors.emplace_back(marker.x, marker.y);
712 costFunctions.emplace_back(&errors.back(), ceres::DO_NOT_TAKE_OWNERSHIP);
713
714 problem.AddResidualBlock(&costFunctions.back(),
Austin Schuh3de38b02024-06-25 18:25:10 -0700715 nullptr,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800716 camera_intrinsics,
717 current_camera_R_t,
718 &point->X(0));
Austin Schuh70cc9552019-01-21 19:46:48 -0800719
720 // We lock the first camera to better deal with scene orientation ambiguity.
721 if (!have_locked_camera) {
722 problem.SetParameterBlockConstant(current_camera_R_t);
723 have_locked_camera = true;
724 }
725
726 if (bundle_constraints & BUNDLE_NO_TRANSLATION) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700727 problem.SetManifold(current_camera_R_t, constant_transform_manifold);
Austin Schuh70cc9552019-01-21 19:46:48 -0800728 }
729
730 num_residuals++;
731 }
732 LOG(INFO) << "Number of residuals: " << num_residuals;
733
734 if (!num_residuals) {
735 LOG(INFO) << "Skipping running minimizer with zero residuals";
736 return;
737 }
738
739 BundleIntrinsicsLogMessage(bundle_intrinsics);
740
741 if (bundle_intrinsics == BUNDLE_NO_INTRINSICS) {
742 // No camera intrinsics are being refined,
743 // set the whole parameter block as constant for best performance.
744 problem.SetParameterBlockConstant(camera_intrinsics);
745 } else {
746 // Set the camera intrinsics that are not to be bundled as
747 // constant using some macro trickery.
748
749 std::vector<int> constant_intrinsics;
750#define MAYBE_SET_CONSTANT(bundle_enum, offset) \
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800751 if (!(bundle_intrinsics & bundle_enum)) { \
752 constant_intrinsics.push_back(offset); \
753 }
754 MAYBE_SET_CONSTANT(BUNDLE_FOCAL_LENGTH, OFFSET_FOCAL_LENGTH);
Austin Schuh70cc9552019-01-21 19:46:48 -0800755 MAYBE_SET_CONSTANT(BUNDLE_PRINCIPAL_POINT, OFFSET_PRINCIPAL_POINT_X);
756 MAYBE_SET_CONSTANT(BUNDLE_PRINCIPAL_POINT, OFFSET_PRINCIPAL_POINT_Y);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800757 MAYBE_SET_CONSTANT(BUNDLE_RADIAL_K1, OFFSET_K1);
758 MAYBE_SET_CONSTANT(BUNDLE_RADIAL_K2, OFFSET_K2);
759 MAYBE_SET_CONSTANT(BUNDLE_TANGENTIAL_P1, OFFSET_P1);
760 MAYBE_SET_CONSTANT(BUNDLE_TANGENTIAL_P2, OFFSET_P2);
Austin Schuh70cc9552019-01-21 19:46:48 -0800761#undef MAYBE_SET_CONSTANT
762
763 // Always set K3 constant, it's not used at the moment.
764 constant_intrinsics.push_back(OFFSET_K3);
765
Austin Schuh3de38b02024-06-25 18:25:10 -0700766 auto* subset_manifold = new ceres::SubsetManifold(8, constant_intrinsics);
767 problem.SetManifold(camera_intrinsics, subset_manifold);
Austin Schuh70cc9552019-01-21 19:46:48 -0800768 }
769
770 // Configure the solver.
771 ceres::Solver::Options options;
772 options.use_nonmonotonic_steps = true;
773 options.preconditioner_type = ceres::SCHUR_JACOBI;
774 options.linear_solver_type = ceres::ITERATIVE_SCHUR;
775 options.use_inner_iterations = true;
776 options.max_num_iterations = 100;
777 options.minimizer_progress_to_stdout = true;
778
779 // Solve!
780 ceres::Solver::Summary summary;
781 ceres::Solve(options, &problem, &summary);
782
783 std::cout << "Final report:\n" << summary.FullReport();
784
785 // Copy rotations and translations back.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800786 UnpackCamerasRotationAndTranslation(
787 all_markers, all_cameras_R_t, all_cameras);
Austin Schuh70cc9552019-01-21 19:46:48 -0800788
789 PrintCameraIntrinsics("Final intrinsics: ", camera_intrinsics);
790}
791} // namespace
792
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800793int main(int argc, char** argv) {
794 GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
Austin Schuh70cc9552019-01-21 19:46:48 -0800795 google::InitGoogleLogging(argv[0]);
796
Austin Schuh3de38b02024-06-25 18:25:10 -0700797 if (CERES_GET_FLAG(FLAGS_input).empty()) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800798 LOG(ERROR) << "Usage: libmv_bundle_adjuster --input=blender_problem";
799 return EXIT_FAILURE;
800 }
801
802 double camera_intrinsics[8];
Austin Schuh3de38b02024-06-25 18:25:10 -0700803 std::vector<EuclideanCamera> all_cameras;
804 std::vector<EuclideanPoint> all_points;
Austin Schuh70cc9552019-01-21 19:46:48 -0800805 bool is_image_space;
Austin Schuh3de38b02024-06-25 18:25:10 -0700806 std::vector<Marker> all_markers;
Austin Schuh70cc9552019-01-21 19:46:48 -0800807
Austin Schuh3de38b02024-06-25 18:25:10 -0700808 if (!ReadProblemFromFile(CERES_GET_FLAG(FLAGS_input),
Austin Schuh70cc9552019-01-21 19:46:48 -0800809 camera_intrinsics,
810 &all_cameras,
811 &all_points,
812 &is_image_space,
813 &all_markers)) {
814 LOG(ERROR) << "Error reading problem file";
815 return EXIT_FAILURE;
816 }
817
818 // If there's no refine_intrinsics passed via command line
819 // (in this case FLAGS_refine_intrinsics will be an empty string)
820 // we use problem's settings to detect whether intrinsics
821 // shall be refined or not.
822 //
823 // Namely, if problem has got markers stored in image (pixel)
824 // space, we do full intrinsics refinement. If markers are
825 // stored in normalized space, and refine_intrinsics is not
826 // set, no refining will happen.
827 //
828 // Using command line argument refine_intrinsics will explicitly
829 // declare which intrinsics need to be refined and in this case
830 // refining flags does not depend on problem at all.
831 int bundle_intrinsics = BUNDLE_NO_INTRINSICS;
Austin Schuh3de38b02024-06-25 18:25:10 -0700832 if (CERES_GET_FLAG(FLAGS_refine_intrinsics).empty()) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800833 if (is_image_space) {
834 bundle_intrinsics = BUNDLE_FOCAL_LENGTH | BUNDLE_RADIAL;
835 }
836 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700837 if (CERES_GET_FLAG(FLAGS_refine_intrinsics) == "radial") {
Austin Schuh70cc9552019-01-21 19:46:48 -0800838 bundle_intrinsics = BUNDLE_FOCAL_LENGTH | BUNDLE_RADIAL;
Austin Schuh3de38b02024-06-25 18:25:10 -0700839 } else if (CERES_GET_FLAG(FLAGS_refine_intrinsics) != "none") {
Austin Schuh70cc9552019-01-21 19:46:48 -0800840 LOG(ERROR) << "Unsupported value for refine-intrinsics";
841 return EXIT_FAILURE;
842 }
843 }
844
845 // Run the bundler.
846 EuclideanBundleCommonIntrinsics(all_markers,
847 bundle_intrinsics,
848 BUNDLE_NO_CONSTRAINTS,
849 camera_intrinsics,
850 &all_cameras,
851 &all_points);
852
853 return EXIT_SUCCESS;
854}