09523340ed74bc15c9075b5ef4722840a8b1edb9
[blender.git] / extern / libmv / libmv / simple_pipeline / bundle.cc
1 // Copyright (c) 2011, 2012, 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 #include "libmv/simple_pipeline/bundle.h"
22
23 #include <map>
24
25 #include "ceres/ceres.h"
26 #include "ceres/rotation.h"
27 #include "libmv/base/vector.h"
28 #include "libmv/logging/logging.h"
29 #include "libmv/multiview/fundamental.h"
30 #include "libmv/multiview/projection.h"
31 #include "libmv/numeric/numeric.h"
32 #include "libmv/simple_pipeline/camera_intrinsics.h"
33 #include "libmv/simple_pipeline/reconstruction.h"
34 #include "libmv/simple_pipeline/tracks.h"
35
36 #ifdef _OPENMP
37 #  include <omp.h>
38 #endif
39
40 namespace libmv {
41
42 // The intrinsics need to get combined into a single parameter block; use these
43 // enums to index instead of numeric constants.
44 enum {
45   OFFSET_FOCAL_LENGTH,
46   OFFSET_PRINCIPAL_POINT_X,
47   OFFSET_PRINCIPAL_POINT_Y,
48   OFFSET_K1,
49   OFFSET_K2,
50   OFFSET_K3,
51   OFFSET_P1,
52   OFFSET_P2,
53 };
54
55 namespace {
56
57 // Cost functor which computes reprojection error of 3D point X
58 // on camera defined by angle-axis rotation and it's translation
59 // (which are in the same block due to optimization reasons).
60 //
61 // This functor uses a radial distortion model.
62 struct OpenCVReprojectionError {
63   OpenCVReprojectionError(const double observed_x,
64                           const double observed_y,
65                           const double weight)
66       : observed_x_(observed_x), observed_y_(observed_y),
67         weight_(weight) {}
68
69   template <typename T>
70   bool operator()(const T* const intrinsics,
71                   const T* const R_t,  // Rotation denoted by angle axis
72                                        // followed with translation
73                   const T* const X,    // Point coordinates 3x1.
74                   T* residuals) const {
75     // Unpack the intrinsics.
76     const T& focal_length      = intrinsics[OFFSET_FOCAL_LENGTH];
77     const T& principal_point_x = intrinsics[OFFSET_PRINCIPAL_POINT_X];
78     const T& principal_point_y = intrinsics[OFFSET_PRINCIPAL_POINT_Y];
79     const T& k1                = intrinsics[OFFSET_K1];
80     const T& k2                = intrinsics[OFFSET_K2];
81     const T& k3                = intrinsics[OFFSET_K3];
82     const T& p1                = intrinsics[OFFSET_P1];
83     const T& p2                = intrinsics[OFFSET_P2];
84
85     // Compute projective coordinates: x = RX + t.
86     T x[3];
87
88     ceres::AngleAxisRotatePoint(R_t, X, x);
89     x[0] += R_t[3];
90     x[1] += R_t[4];
91     x[2] += R_t[5];
92
93     // Prevent points from going behind the camera.
94     if (x[2] < T(0)) {
95       return false;
96     }
97
98     // Compute normalized coordinates: x /= x[2].
99     T xn = x[0] / x[2];
100     T yn = x[1] / x[2];
101
102     T predicted_x, predicted_y;
103
104     // Apply distortion to the normalized points to get (xd, yd).
105     // TODO(keir): Do early bailouts for zero distortion; these are expensive
106     // jet operations.
107     ApplyRadialDistortionCameraIntrinsics(focal_length,
108                                           focal_length,
109                                           principal_point_x,
110                                           principal_point_y,
111                                           k1, k2, k3,
112                                           p1, p2,
113                                           xn, yn,
114                                           &predicted_x,
115                                           &predicted_y);
116
117     // The error is the difference between the predicted and observed position.
118     residuals[0] = (predicted_x - T(observed_x_)) * weight_;
119     residuals[1] = (predicted_y - T(observed_y_)) * weight_;
120     return true;
121   }
122
123   const double observed_x_;
124   const double observed_y_;
125   const double weight_;
126 };
127
128 // Print a message to the log which camera intrinsics are gonna to be optimixed.
129 void BundleIntrinsicsLogMessage(const int bundle_intrinsics) {
130   if (bundle_intrinsics == BUNDLE_NO_INTRINSICS) {
131     LOG(INFO) << "Bundling only camera positions.";
132   } else {
133     std::string bundling_message = "";
134
135 #define APPEND_BUNDLING_INTRINSICS(name, flag) \
136     if (bundle_intrinsics & flag) { \
137       if (!bundling_message.empty()) { \
138         bundling_message += ", "; \
139       } \
140       bundling_message += name; \
141     } (void)0
142
143     APPEND_BUNDLING_INTRINSICS("f",      BUNDLE_FOCAL_LENGTH);
144     APPEND_BUNDLING_INTRINSICS("px, py", BUNDLE_PRINCIPAL_POINT);
145     APPEND_BUNDLING_INTRINSICS("k1",     BUNDLE_RADIAL_K1);
146     APPEND_BUNDLING_INTRINSICS("k2",     BUNDLE_RADIAL_K2);
147     APPEND_BUNDLING_INTRINSICS("p1",     BUNDLE_TANGENTIAL_P1);
148     APPEND_BUNDLING_INTRINSICS("p2",     BUNDLE_TANGENTIAL_P2);
149
150     LOG(INFO) << "Bundling " << bundling_message << ".";
151   }
152 }
153
154 // Pack intrinsics from object to an array for easier
155 // and faster minimization.
156 void PackIntrinisicsIntoArray(const CameraIntrinsics &intrinsics,
157                               double ceres_intrinsics[8]) {
158   ceres_intrinsics[OFFSET_FOCAL_LENGTH]       = intrinsics.focal_length();
159   ceres_intrinsics[OFFSET_PRINCIPAL_POINT_X]  = intrinsics.principal_point_x();
160   ceres_intrinsics[OFFSET_PRINCIPAL_POINT_Y]  = intrinsics.principal_point_y();
161   ceres_intrinsics[OFFSET_K1]                 = intrinsics.k1();
162   ceres_intrinsics[OFFSET_K2]                 = intrinsics.k2();
163   ceres_intrinsics[OFFSET_K3]                 = intrinsics.k3();
164   ceres_intrinsics[OFFSET_P1]                 = intrinsics.p1();
165   ceres_intrinsics[OFFSET_P2]                 = intrinsics.p2();
166 }
167
168 // Unpack intrinsics back from an array to an object.
169 void UnpackIntrinsicsFromArray(const double ceres_intrinsics[8],
170                                  CameraIntrinsics *intrinsics) {
171     intrinsics->SetFocalLength(ceres_intrinsics[OFFSET_FOCAL_LENGTH],
172                                ceres_intrinsics[OFFSET_FOCAL_LENGTH]);
173
174     intrinsics->SetPrincipalPoint(ceres_intrinsics[OFFSET_PRINCIPAL_POINT_X],
175                                   ceres_intrinsics[OFFSET_PRINCIPAL_POINT_Y]);
176
177     intrinsics->SetRadialDistortion(ceres_intrinsics[OFFSET_K1],
178                                     ceres_intrinsics[OFFSET_K2],
179                                     ceres_intrinsics[OFFSET_K3]);
180
181     intrinsics->SetTangentialDistortion(ceres_intrinsics[OFFSET_P1],
182                                         ceres_intrinsics[OFFSET_P2]);
183 }
184
185 // Get a vector of camera's rotations denoted by angle axis
186 // conjuncted with translations into single block
187 //
188 // Element with index i matches to a rotation+translation for
189 // camera at image i.
190 vector<Vec6> PackCamerasRotationAndTranslation(
191     const Tracks &tracks,
192     const EuclideanReconstruction &reconstruction) {
193   vector<Vec6> all_cameras_R_t;
194   int max_image = tracks.MaxImage();
195
196   all_cameras_R_t.resize(max_image + 1);
197
198   for (int i = 0; i <= max_image; i++) {
199     const EuclideanCamera *camera = reconstruction.CameraForImage(i);
200
201     if (!camera) {
202       continue;
203     }
204
205     ceres::RotationMatrixToAngleAxis(&camera->R(0, 0),
206                                      &all_cameras_R_t[i](0));
207     all_cameras_R_t[i].tail<3>() = camera->t;
208   }
209   return all_cameras_R_t;
210 }
211
212 // Convert cameras rotations fro mangle axis back to rotation matrix.
213 void UnpackCamerasRotationAndTranslation(
214     const Tracks &tracks,
215     const vector<Vec6> &all_cameras_R_t,
216     EuclideanReconstruction *reconstruction) {
217   int max_image = tracks.MaxImage();
218
219   for (int i = 0; i <= max_image; i++) {
220     EuclideanCamera *camera = reconstruction->CameraForImage(i);
221
222     if (!camera) {
223       continue;
224     }
225
226     ceres::AngleAxisToRotationMatrix(&all_cameras_R_t[i](0),
227                                      &camera->R(0, 0));
228     camera->t = all_cameras_R_t[i].tail<3>();
229   }
230 }
231
232 // Converts sparse CRSMatrix to Eigen matrix, so it could be used
233 // all over in the pipeline.
234 //
235 // TODO(sergey): currently uses dense Eigen matrices, best would
236 //               be to use sparse Eigen matrices
237 void CRSMatrixToEigenMatrix(const ceres::CRSMatrix &crs_matrix,
238                             Mat *eigen_matrix) {
239   eigen_matrix->resize(crs_matrix.num_rows, crs_matrix.num_cols);
240   eigen_matrix->setZero();
241
242   for (int row = 0; row < crs_matrix.num_rows; ++row) {
243     int start = crs_matrix.rows[row];
244     int end = crs_matrix.rows[row + 1] - 1;
245
246     for (int i = start; i <= end; i++) {
247       int col = crs_matrix.cols[i];
248       double value = crs_matrix.values[i];
249
250       (*eigen_matrix)(row, col) = value;
251     }
252   }
253 }
254
255 void EuclideanBundlerPerformEvaluation(const Tracks &tracks,
256                                        EuclideanReconstruction *reconstruction,
257                                        vector<Vec6> *all_cameras_R_t,
258                                        ceres::Problem *problem,
259                                        BundleEvaluation *evaluation) {
260     int max_track = tracks.MaxTrack();
261     // Number of camera rotations equals to number of translation,
262     int num_cameras = all_cameras_R_t->size();
263     int num_points = 0;
264
265     vector<EuclideanPoint*> minimized_points;
266     for (int i = 0; i <= max_track; i++) {
267       EuclideanPoint *point = reconstruction->PointForTrack(i);
268       if (point) {
269         // We need to know whether the track is constant zero weight,
270         // and it so it wouldn't have parameter block in the problem.
271         //
272         // Getting all markers for track is not so bac currently since
273         // this code is only used by keyframe selection when there are
274         // not so much tracks and only 2 frames anyway.
275         vector<Marker> markera_of_track = tracks.MarkersForTrack(i);
276         for (int j = 0; j < markera_of_track.size(); j++) {
277           if (markera_of_track.at(j).weight != 0.0) {
278             minimized_points.push_back(point);
279             num_points++;
280             break;
281           }
282         }
283       }
284     }
285
286     LG << "Number of cameras " << num_cameras;
287     LG << "Number of points " << num_points;
288
289     evaluation->num_cameras = num_cameras;
290     evaluation->num_points = num_points;
291
292     if (evaluation->evaluate_jacobian) {      // Evaluate jacobian matrix.
293       ceres::CRSMatrix evaluated_jacobian;
294       ceres::Problem::EvaluateOptions eval_options;
295
296       // Cameras goes first in the ordering.
297       int max_image = tracks.MaxImage();
298       for (int i = 0; i <= max_image; i++) {
299         const EuclideanCamera *camera = reconstruction->CameraForImage(i);
300         if (camera) {
301           double *current_camera_R_t = &(*all_cameras_R_t)[i](0);
302
303           // All cameras are variable now.
304           problem->SetParameterBlockVariable(current_camera_R_t);
305
306           eval_options.parameter_blocks.push_back(current_camera_R_t);
307         }
308       }
309
310       // Points goes at the end of ordering,
311       for (int i = 0; i < minimized_points.size(); i++) {
312         EuclideanPoint *point = minimized_points.at(i);
313         eval_options.parameter_blocks.push_back(&point->X(0));
314       }
315
316       problem->Evaluate(eval_options,
317                         NULL, NULL, NULL,
318                         &evaluated_jacobian);
319
320       CRSMatrixToEigenMatrix(evaluated_jacobian, &evaluation->jacobian);
321     }
322 }
323
324 // This is an utility function to only bundle 3D position of
325 // given markers list.
326 //
327 // Main purpose of this function is to adjust positions of tracks
328 // which does have constant zero weight and so far only were using
329 // algebraic intersection to obtain their 3D positions.
330 //
331 // At this point we only need to bundle points positions, cameras
332 // are to be totally still here.
333 void EuclideanBundlePointsOnly(const vector<Marker> &markers,
334                                vector<Vec6> &all_cameras_R_t,
335                                double ceres_intrinsics[8],
336                                EuclideanReconstruction *reconstruction) {
337   ceres::Problem::Options problem_options;
338   ceres::Problem problem(problem_options);
339   int num_residuals = 0;
340   for (int i = 0; i < markers.size(); ++i) {
341     const Marker &marker = markers[i];
342     EuclideanCamera *camera = reconstruction->CameraForImage(marker.image);
343     EuclideanPoint *point = reconstruction->PointForTrack(marker.track);
344     if (camera == NULL || point == NULL) {
345       continue;
346     }
347
348     // Rotation of camera denoted in angle axis followed with
349     // camera translaiton.
350     double *current_camera_R_t = &all_cameras_R_t[camera->image](0);
351
352     problem.AddResidualBlock(new ceres::AutoDiffCostFunction<
353         OpenCVReprojectionError, 2, 8, 6, 3>(
354             new OpenCVReprojectionError(
355                 marker.x,
356                 marker.y,
357                 1.0)),
358         NULL,
359         ceres_intrinsics,
360         current_camera_R_t,
361         &point->X(0));
362
363     problem.SetParameterBlockConstant(current_camera_R_t);
364     num_residuals++;
365   }
366
367   LG << "Number of residuals: " << num_residuals;
368   if (!num_residuals) {
369     LG << "Skipping running minimizer with zero residuals";
370     return;
371   }
372
373   problem.SetParameterBlockConstant(ceres_intrinsics);
374
375   // Configure the solver.
376   ceres::Solver::Options options;
377   options.use_nonmonotonic_steps = true;
378   options.preconditioner_type = ceres::SCHUR_JACOBI;
379   options.linear_solver_type = ceres::ITERATIVE_SCHUR;
380   options.use_inner_iterations = true;
381   options.max_num_iterations = 100;
382
383 #ifdef _OPENMP
384   options.num_threads = omp_get_max_threads();
385   options.num_linear_solver_threads = omp_get_max_threads();
386 #endif
387
388   // Solve!
389   ceres::Solver::Summary summary;
390   ceres::Solve(options, &problem, &summary);
391
392   LG << "Final report:\n" << summary.FullReport();
393
394 }
395
396 }  // namespace
397
398 void EuclideanBundle(const Tracks &tracks,
399                      EuclideanReconstruction *reconstruction) {
400   CameraIntrinsics intrinsics;
401   EuclideanBundleCommonIntrinsics(tracks,
402                                   BUNDLE_NO_INTRINSICS,
403                                   BUNDLE_NO_CONSTRAINTS,
404                                   reconstruction,
405                                   &intrinsics,
406                                   NULL);
407 }
408
409 void EuclideanBundleCommonIntrinsics(const Tracks &tracks,
410                                      const int bundle_intrinsics,
411                                      const int bundle_constraints,
412                                      EuclideanReconstruction *reconstruction,
413                                      CameraIntrinsics *intrinsics,
414                                      BundleEvaluation *evaluation) {
415   LG << "Original intrinsics: " << *intrinsics;
416   vector<Marker> markers = tracks.AllMarkers();
417
418   // N-th element denotes whether track N is a constant zero-weigthed track.
419   vector<bool> zero_weight_tracks_flags(tracks.MaxTrack(), true);
420
421   // Residual blocks with 10 parameters are unwieldly with Ceres, so pack the
422   // intrinsics into a single block and rely on local parameterizations to
423   // control which intrinsics are allowed to vary.
424   double ceres_intrinsics[8];
425   PackIntrinisicsIntoArray(*intrinsics, ceres_intrinsics);
426
427   // Convert cameras rotations to angle axis and merge with translation
428   // into single parameter block for maximal minimization speed.
429   //
430   // Block for minimization has got the following structure:
431   //   <3 elements for angle-axis> <3 elements for translation>
432   vector<Vec6> all_cameras_R_t =
433     PackCamerasRotationAndTranslation(tracks, *reconstruction);
434
435   // Parameterization used to restrict camera motion for modal solvers.
436   ceres::SubsetParameterization *constant_translation_parameterization = NULL;
437   if (bundle_constraints & BUNDLE_NO_TRANSLATION) {
438       std::vector<int> constant_translation;
439
440       // First three elements are rotation, ast three are translation.
441       constant_translation.push_back(3);
442       constant_translation.push_back(4);
443       constant_translation.push_back(5);
444
445       constant_translation_parameterization =
446         new ceres::SubsetParameterization(6, constant_translation);
447   }
448
449   // Add residual blocks to the problem.
450   ceres::Problem::Options problem_options;
451   ceres::Problem problem(problem_options);
452   int num_residuals = 0;
453   bool have_locked_camera = false;
454   for (int i = 0; i < markers.size(); ++i) {
455     const Marker &marker = markers[i];
456     EuclideanCamera *camera = reconstruction->CameraForImage(marker.image);
457     EuclideanPoint *point = reconstruction->PointForTrack(marker.track);
458     if (camera == NULL || point == NULL) {
459       continue;
460     }
461
462     // Rotation of camera denoted in angle axis followed with
463     // camera translaiton.
464     double *current_camera_R_t = &all_cameras_R_t[camera->image](0);
465
466     // Skip residual block for markers which does have absolutely
467     // no affect on the final solution.
468     // This way ceres is not gonna to go crazy.
469     if (marker.weight != 0.0) {
470       problem.AddResidualBlock(new ceres::AutoDiffCostFunction<
471           OpenCVReprojectionError, 2, 8, 6, 3>(
472               new OpenCVReprojectionError(
473                   marker.x,
474                   marker.y,
475                   marker.weight)),
476           NULL,
477           ceres_intrinsics,
478           current_camera_R_t,
479           &point->X(0));
480
481       // We lock the first camera to better deal with scene orientation ambiguity.
482       if (!have_locked_camera) {
483         problem.SetParameterBlockConstant(current_camera_R_t);
484         have_locked_camera = true;
485       }
486
487       if (bundle_constraints & BUNDLE_NO_TRANSLATION) {
488         problem.SetParameterization(current_camera_R_t,
489                                     constant_translation_parameterization);
490       }
491
492       zero_weight_tracks_flags[marker.track] = false;
493     }
494
495     num_residuals++;
496   }
497   LG << "Number of residuals: " << num_residuals;
498
499   if (!num_residuals) {
500     LG << "Skipping running minimizer with zero residuals";
501     return;
502   }
503
504   BundleIntrinsicsLogMessage(bundle_intrinsics);
505
506   if (bundle_intrinsics == BUNDLE_NO_INTRINSICS) {
507     // No camera intrinsics are being refined,
508     // set the whole parameter block as constant for best performance.
509     problem.SetParameterBlockConstant(ceres_intrinsics);
510   } else {
511     // Set the camera intrinsics that are not to be bundled as
512     // constant using some macro trickery.
513
514     std::vector<int> constant_intrinsics;
515 #define MAYBE_SET_CONSTANT(bundle_enum, offset) \
516     if (!(bundle_intrinsics & bundle_enum)) { \
517       constant_intrinsics.push_back(offset); \
518     }
519     MAYBE_SET_CONSTANT(BUNDLE_FOCAL_LENGTH,    OFFSET_FOCAL_LENGTH);
520     MAYBE_SET_CONSTANT(BUNDLE_PRINCIPAL_POINT, OFFSET_PRINCIPAL_POINT_X);
521     MAYBE_SET_CONSTANT(BUNDLE_PRINCIPAL_POINT, OFFSET_PRINCIPAL_POINT_Y);
522     MAYBE_SET_CONSTANT(BUNDLE_RADIAL_K1,       OFFSET_K1);
523     MAYBE_SET_CONSTANT(BUNDLE_RADIAL_K2,       OFFSET_K2);
524     MAYBE_SET_CONSTANT(BUNDLE_TANGENTIAL_P1,   OFFSET_P1);
525     MAYBE_SET_CONSTANT(BUNDLE_TANGENTIAL_P2,   OFFSET_P2);
526 #undef MAYBE_SET_CONSTANT
527
528     // Always set K3 constant, it's not used at the moment.
529     constant_intrinsics.push_back(OFFSET_K3);
530
531     ceres::SubsetParameterization *subset_parameterization =
532       new ceres::SubsetParameterization(8, constant_intrinsics);
533
534     problem.SetParameterization(ceres_intrinsics, subset_parameterization);
535   }
536
537   // Configure the solver.
538   ceres::Solver::Options options;
539   options.use_nonmonotonic_steps = true;
540   options.preconditioner_type = ceres::SCHUR_JACOBI;
541   options.linear_solver_type = ceres::ITERATIVE_SCHUR;
542   options.use_inner_iterations = true;
543   options.max_num_iterations = 100;
544
545 #ifdef _OPENMP
546   options.num_threads = omp_get_max_threads();
547   options.num_linear_solver_threads = omp_get_max_threads();
548 #endif
549
550   // Solve!
551   ceres::Solver::Summary summary;
552   ceres::Solve(options, &problem, &summary);
553
554   LG << "Final report:\n" << summary.FullReport();
555
556   // Copy rotations and translations back.
557   UnpackCamerasRotationAndTranslation(tracks,
558                                       all_cameras_R_t,
559                                       reconstruction);
560
561   // Copy intrinsics back.
562   if (bundle_intrinsics != BUNDLE_NO_INTRINSICS)
563     UnpackIntrinsicsFromArray(ceres_intrinsics, intrinsics);
564
565   LG << "Final intrinsics: " << *intrinsics;
566
567   if (evaluation) {
568     EuclideanBundlerPerformEvaluation(tracks, reconstruction, &all_cameras_R_t,
569                                       &problem, evaluation);
570   }
571
572   // Separate step to adjust positions of tracks which are
573   // constant zero-weighted.
574   vector<Marker> zero_weight_markers;
575   for (int track = 0; track < tracks.MaxTrack(); ++track) {
576     if (zero_weight_tracks_flags[track]) {
577       vector<Marker> current_markers = tracks.MarkersForTrack(track);
578       zero_weight_markers.reserve(zero_weight_markers.size() +
579                                   current_markers.size());
580       for (int i = 0; i < current_markers.size(); ++i) {
581         zero_weight_markers.push_back(current_markers[i]);
582       }
583     }
584   }
585
586   if (zero_weight_markers.size()) {
587     LG << "Refining position of constant zero-weighted tracks";
588     EuclideanBundlePointsOnly(zero_weight_markers,
589                               all_cameras_R_t,
590                               ceres_intrinsics,
591                               reconstruction);
592   }
593 }
594
595 void ProjectiveBundle(const Tracks & /*tracks*/,
596                       ProjectiveReconstruction * /*reconstruction*/) {
597   // TODO(keir): Implement this! This can't work until we have a better bundler
598   // than SSBA, since SSBA has no support for projective bundling.
599 }
600
601 }  // namespace libmv