Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
intrinsicssemidlt.cpp
Go to the documentation of this file.
2
3// std
4#include <algorithm>
5#include <numeric>
6#include <optional>
7#include <span>
8
9// ceres
10#include <ceres/ceres.h>
11#include <ceres/rotation.h>
12
13#include "detail/ceresutils.h"
16
17namespace calib {
18
19static size_t count_total_observations(const std::vector<PlanarView>& views) {
20 return std::accumulate(views.begin(), views.end(), size_t{0},
21 [](size_t total, const auto& view) { return total + view.size(); });
22}
23
24struct IntrinsicBlocks final : public ProblemParamBlocks {
25 std::array<double, 5> intrinsics;
26 std::vector<std::array<double, 4>> c_quat_t;
27 std::vector<std::array<double, 3>> c_tra_t;
28
29 static IntrinsicBlocks create(const std::vector<PlanarView>& views,
30 const CameraMatrix& initial_guess) {
31 IntrinsicBlocks blocks;
32 blocks.c_quat_t.resize(views.size());
33 blocks.c_tra_t.resize(views.size());
34 blocks.intrinsics = {initial_guess.fx, initial_guess.fy, initial_guess.cx, initial_guess.cy,
35 initial_guess.skew};
36
37 for (size_t i = 0; i < views.size(); ++i) {
38 Eigen::Isometry3d pose = estimate_planar_pose(views[i], initial_guess);
39 populate_quat_tran(pose, blocks.c_quat_t[i], blocks.c_tra_t[i]);
40 }
41
42 return blocks;
43 }
44
45 [[nodiscard]] std::vector<ParamBlock> get_param_blocks() const override {
46 std::vector<ParamBlock> blocks;
47 blocks.emplace_back(intrinsics.data(), intrinsics.size(), 5);
48
49 // Reserve space for efficiency
50 blocks.reserve(1 + c_quat_t.size() + c_tra_t.size());
51
52 // Add quaternion blocks using std::transform
53 std::transform(c_quat_t.begin(), c_quat_t.end(), std::back_inserter(blocks),
54 [](const auto& q) { return ParamBlock{q.data(), q.size(), 3}; });
55
56 // Add translation blocks using std::transform
57 std::transform(c_tra_t.begin(), c_tra_t.end(), std::back_inserter(blocks),
58 [](const auto& t) { return ParamBlock{t.data(), t.size(), 3}; });
59
60 return blocks;
61 }
62
64 result.camera.kmtx.fx = intrinsics[0];
65 result.camera.kmtx.fy = intrinsics[1];
66 result.camera.kmtx.cx = intrinsics[2];
67 result.camera.kmtx.cy = intrinsics[3];
68 result.camera.kmtx.skew = intrinsics[4];
69
70 result.c_se3_t.resize(c_quat_t.size());
71 for (size_t i = 0; i < c_quat_t.size(); ++i) {
72 result.c_se3_t[i] = restore_pose(c_quat_t[i], c_tra_t[i]);
73 }
74 }
75};
76
77static auto solve_full(const std::vector<PlanarView>& views, const IntrinsicBlocks& blocks,
78 const IntrinsicsOptimOptions& opts)
79 -> std::optional<DistortionWithResiduals<double>> {
80 std::vector<Observation<double>> obs;
81 for (size_t i = 0; i < views.size(); ++i) {
82 auto c_se3_t = restore_pose(blocks.c_quat_t[i], blocks.c_tra_t[i]);
83 std::vector<Observation<double>> new_obs(views[i].size());
84 planar_observables_to_observables(views[i], new_obs, c_se3_t);
85 obs.insert(obs.end(), new_obs.begin(), new_obs.end());
86 }
87 CameraMatrix kmtx{blocks.intrinsics[0], blocks.intrinsics[1], blocks.intrinsics[2],
88 blocks.intrinsics[3], blocks.intrinsics[4]};
89 return fit_distortion_full(obs, kmtx, opts.num_radial,
90 std::span<const int>(opts.fixed_distortion_indices),
91 std::span<const double>(opts.fixed_distortion_values));
92}
93
94// Set up the Ceres optimization problem
95static ceres::Problem build_problem(const std::vector<PlanarView>& obs_views,
96 IntrinsicBlocks& blocks, const IntrinsicsOptimOptions& opts) {
97 ceres::Problem problem;
98 auto* cost = CalibVPResidual::create(obs_views, opts.num_radial);
99
100 // Add parameter blocks to the problem
101 std::vector<double*> param_blocks;
102 param_blocks.push_back(blocks.intrinsics.data());
103 for (size_t i = 0; i < blocks.c_quat_t.size(); ++i) {
104 param_blocks.push_back(blocks.c_quat_t[i].data());
105 param_blocks.push_back(blocks.c_tra_t[i].data());
106 }
107 auto* loss = opts.core.huber_delta > 0 ? new ceres::HuberLoss(opts.core.huber_delta) : nullptr;
108 problem.AddResidualBlock(cost, loss, param_blocks);
109
110 for (auto& i : blocks.c_quat_t) {
111 problem.SetManifold(i.data(), new ceres::QuaternionManifold());
112 }
113
114 if (opts.bounds.has_value()) {
115 CalibrationBounds bounds = opts.bounds.value_or(CalibrationBounds{});
117 problem.SetParameterLowerBound(blocks.intrinsics.data(), Traits::idx_fx, bounds.fx_min);
118 problem.SetParameterLowerBound(blocks.intrinsics.data(), Traits::idx_fy, bounds.fy_min);
119 problem.SetParameterLowerBound(blocks.intrinsics.data(), 2, bounds.cx_min);
120 problem.SetParameterLowerBound(blocks.intrinsics.data(), 3, bounds.cy_min);
121 problem.SetParameterLowerBound(blocks.intrinsics.data(), Traits::idx_skew, bounds.skew_min);
122 problem.SetParameterUpperBound(blocks.intrinsics.data(), Traits::idx_skew, bounds.skew_max);
123 problem.SetParameterUpperBound(blocks.intrinsics.data(), Traits::idx_fx, bounds.fx_max);
124 problem.SetParameterUpperBound(blocks.intrinsics.data(), Traits::idx_fy, bounds.fy_max);
125 problem.SetParameterUpperBound(blocks.intrinsics.data(), 2, bounds.cx_max);
126 problem.SetParameterUpperBound(blocks.intrinsics.data(), 3, bounds.cy_max);
127 }
128 if (!opts.optimize_skew) {
129 problem.SetManifold(
130 blocks.intrinsics.data(),
131 new ceres::SubsetManifold(5, {CameraTraits<Camera<BrownConradyd>>::idx_skew}));
132 }
133
134 return problem;
135}
136
138 const std::vector<PlanarView>& obs_views, const Eigen::VectorXd& residuals,
140 const size_t num_views = obs_views.size();
141 result.view_errors.resize(num_views);
142 int residual_idx = 0;
143
144 for (size_t i = 0; i < num_views; ++i) {
145 int view_points = static_cast<int>(obs_views[i].size());
146 double view_error_sum = 0.0;
147 for (int j = 0; j < view_points * 2; ++j) {
148 double r = residuals[residual_idx++];
149 view_error_sum += r * r;
150 }
151 result.view_errors[i] = std::sqrt(view_error_sum / (view_points * 2));
152 }
153}
154
156 const std::vector<PlanarView>& views, const CameraMatrix& initial_guess,
157 const IntrinsicsOptimOptions& opts) {
159
160 // Prepare observations per view
161 const size_t total_obs = count_total_observations(views);
162 const size_t num_views = views.size();
163 if (num_views < 4) {
164 std::cerr << "Insufficient views for calibration (at least 4 required)." << '\n';
165 return result;
166 }
167
168 auto blocks = IntrinsicBlocks::create(views, initial_guess);
169 // Set up and solve the optimization problem
170 ceres::Problem problem = build_problem(views, blocks, opts);
171
172 solve_problem(problem, opts.core, &result.core);
173
174 auto dr_opt = solve_full(views, blocks, opts);
175 if (!dr_opt.has_value()) {
176 throw std::runtime_error("Failed to compute distortion parameters");
177 }
178 result.camera.distortion.coeffs = dr_opt->distortion;
179
180 // Process results
181 compute_per_view_errors(views, dr_opt->residuals, result);
182 blocks.populate_result(result);
183
184 double sum_squared_residuals = dr_opt->residuals.squaredNorm();
185 size_t total_residuals = total_obs * 2;
186 result.core.covariance =
187 compute_covariance(blocks, problem, sum_squared_residuals, total_residuals)
188 .value_or(Eigen::MatrixXd{});
189
190 return result;
191}
192
193} // namespace calib
Pinhole camera model with intrinsics and distortion correction.
Definition pinhole.h:39
Linear multi-camera extrinsics initialisation (DLT)
auto optimize_intrinsics_semidlt(const std::vector< PlanarView > &views, const CameraMatrix &initial_guess, const IntrinsicsOptimOptions &opts={}) -> IntrinsicsOptimizationResult< PinholeCamera< BrownConradyd > >
void populate_quat_tran(const Eigen::Isometry3d &pose, std::array< double, 4 > &quat, std::array< double, 3 > &translation)
auto estimate_planar_pose(PlanarView view, const CameraMatrix &intrinsics) -> Eigen::Isometry3d
Eigen::Isometry3d restore_pose(const std::array< double, 4 > &quat, const std::array< double, 3 > &translation)
auto compute_covariance(const ProblemParamBlocks &problem_param_blocks, ceres::Problem &problem, double sum_squared_residuals=0, size_t total_residuals=0) -> std::optional< Eigen::MatrixXd >
Definition ceresutils.h:69
auto fit_distortion_full(const std::vector< Observation< T > > &observations, const CameraMatrixT< T > &intrinsics, int num_radial=2, std::span< const int > fixed_indices={}, std::span< const T > fixed_values={}) -> std::optional< DistortionWithResiduals< T > >
Definition distortion.h:231
static void compute_per_view_errors(const std::vector< PlanarView > &obs_views, const Eigen::VectorXd &residuals, IntrinsicsOptimizationResult< PinholeCamera< BrownConradyd > > &result)
static ceres::Problem build_problem(const std::vector< PlanarView > &obs_views, IntrinsicBlocks &blocks, const IntrinsicsOptimOptions &opts)
static size_t count_total_observations(const std::vector< PlanarView > &views)
void solve_problem(ceres::Problem &problem, const OptimOptions &opts, OptimResult *result)
Definition ceresutils.h:27
static auto solve_full(const std::vector< PlanarView > &views, const IntrinsicBlocks &blocks, const IntrinsicsOptimOptions &opts) -> std::optional< DistortionWithResiduals< double > >
Type traits template for camera model implementations.
Definition cameramodel.h:64
std::vector< std::array< double, 3 > > c_tra_t
void populate_result(IntrinsicsOptimizationResult< PinholeCamera< BrownConradyd > > &result) const
std::vector< ParamBlock > get_param_blocks() const override
std::vector< std::array< double, 4 > > c_quat_t
std::array< double, 5 > intrinsics
static IntrinsicBlocks create(const std::vector< PlanarView > &views, const CameraMatrix &initial_guess)
OptimOptions core
Non-linear optimization options.
Definition intrinsics.h:14
std::optional< CalibrationBounds > bounds
Parameter bounds.
Definition intrinsics.h:17
int num_radial
Number of radial distortion coefficients.
Definition intrinsics.h:15
bool optimize_skew
Estimate skew parameter.
Definition intrinsics.h:16
OptimResult core
Optimization result details.
Definition intrinsics.h:24
CameraT camera
Estimated camera parameters.
Definition intrinsics.h:25
Eigen::MatrixXd covariance
Definition optimize.h:37