Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
intrinsicsdlt.cpp
Go to the documentation of this file.
2
3// std
4#include <algorithm>
5#include <cmath>
6#include <iostream>
7#include <limits>
8#include <numeric>
9#include <random>
10#include <span>
11#include <utility>
12
13#include "calib/estimation/common/intrinsics_utils.h" // for sanitize_intrinsics
15#include "calib/estimation/linear/posefromhomography.h" // for pose_from_homography
16#include "calib/estimation/linear/zhang.h" // for zhang_intrinsics_from_hs
18
19namespace calib {
20
21static auto symmetric_rms_px(const Eigen::Matrix3d& hmtx, const PlanarView& view,
22 std::span<const int> inliers) -> double {
23 if (inliers.empty()) {
24 return std::numeric_limits<double>::infinity();
25 }
26 const double sum_err = std::accumulate(
27 inliers.begin(), inliers.end(), 0.0,
28 [&](double acc, int idx) { return acc + HomographyEstimator::residual(hmtx, view[idx]); });
29 return std::sqrt(sum_err / (2.0 * static_cast<double>(inliers.size())));
30}
31
32static auto compute_planar_homographies(const std::vector<PlanarView>& views,
33 const std::optional<RansacOptions>& ransac_opts)
34 -> std::vector<HomographyResult> {
35 std::vector<HomographyResult> homographies;
36 homographies.reserve(views.size());
37
38 for (const auto& view : views) {
39 HomographyResult result;
40
41 if (view.size() < HomographyEstimator::k_min_samples) {
42 result.success = false;
43 homographies.push_back(std::move(result));
44 continue;
45 }
46
47 std::vector<int> indices(view.size());
48 std::iota(indices.begin(), indices.end(), 0);
49
50 if (ransac_opts.has_value()) {
51 auto ransac_res = ransac<HomographyEstimator>(view, ransac_opts.value());
52 if (!ransac_res.success) {
53 result.success = false;
54 homographies.push_back(std::move(result));
55 continue;
56 }
57 result.hmtx = ransac_res.model;
58 result.inliers = std::move(ransac_res.inliers);
59 if (std::abs(result.hmtx(2, 2)) > 1e-15) {
60 result.hmtx /= result.hmtx(2, 2);
61 }
62 result.symmetric_rms_px = symmetric_rms_px(result.hmtx, view, result.inliers);
63 result.success = true;
64 } else {
65 auto hopt = HomographyEstimator::fit(view, indices);
66 if (!hopt.has_value()) {
67 result.success = false;
68 homographies.push_back(std::move(result));
69 continue;
70 }
71 result.hmtx = hopt.value();
72 if (std::abs(result.hmtx(2, 2)) > 1e-15) {
73 result.hmtx /= result.hmtx(2, 2);
74 }
75 result.inliers = indices;
76 result.symmetric_rms_px = symmetric_rms_px(result.hmtx, view, result.inliers);
77 result.success = true;
78 }
79
80 homographies.push_back(std::move(result));
81 }
82
83 return homographies;
84}
85
86static auto process_planar_view(const CameraMatrix& kmtx, const HomographyResult& hres)
89 ved.forward_rms_px = hres.symmetric_rms_px;
90 ved.homography = hres;
91
92 auto pose_res = pose_from_homography(kmtx, hres.hmtx);
93 if (!pose_res.success) {
94 std::cerr << "Warning: Homography decomposition failed: " << pose_res.message << "\n";
95 } else {
96 ved.c_se3_t = std::move(pose_res.c_se3_t);
97 }
98 return ved;
99}
100
101auto estimate_intrinsics(const std::vector<PlanarView>& views, const IntrinsicsEstimOptions& opts)
104 if (views.empty()) {
105 return result;
106 }
107
108 auto planar_homographies = compute_planar_homographies(views, opts.homography_ransac);
109
110 std::vector<HomographyResult> valid_hs;
111 valid_hs.reserve(planar_homographies.size());
112 std::copy_if(planar_homographies.begin(), planar_homographies.end(),
113 std::back_inserter(valid_hs),
114 [](const HomographyResult& hr) { return hr.success; });
115 auto zhang_kmtx_opt = zhang_intrinsics_from_hs(valid_hs);
116 if (!zhang_kmtx_opt.has_value()) {
117 std::cout << "Zhang intrinsic estimation failed.\n";
118 return result;
119 }
120
121 // Set the estimated camera matrix
122 auto [sanitized_kmtx, modified] = sanitize_intrinsics(zhang_kmtx_opt.value(), opts.bounds);
123 result.kmtx = sanitized_kmtx;
124 result.success = true;
125
126 if (modified) {
127 result.log = "Intrinsics sanitized by bounds.";
128 }
129
130 result.views.resize(valid_hs.size());
131 std::transform(valid_hs.begin(), valid_hs.end(), result.views.begin(),
132 [&sanitized_kmtx](const HomographyResult& hres) {
133 return process_planar_view(sanitized_kmtx, hres);
134 });
135 // fill view indices
136 size_t vidx = 0;
137 for (size_t i = 0; i < planar_homographies.size(); ++i) {
138 if (planar_homographies[i].success) {
139 result.views[vidx].view_index = i;
140 ++vidx;
141 }
142 }
143
144 return result;
145}
146
147struct LinearSystem final {
148 Eigen::MatrixXd amtx;
149 Eigen::VectorXd bvec;
150};
151
152static auto build_u_system(const std::vector<Observation<double>>& obs, bool use_skew)
153 -> LinearSystem {
154 const size_t nobs = obs.size();
155 const int cols = use_skew ? 3 : 2;
156
157 Eigen::MatrixXd amtx(nobs, cols);
158 Eigen::VectorXd bvec(nobs);
159
160 for (size_t i = 0; i < nobs; ++i) {
161 const auto& observation = obs[i];
162 const int row = static_cast<int>(i);
163
164 amtx(row, 0) = observation.x; // fx coefficient
165 if (use_skew) {
166 amtx(row, 1) = observation.y; // skew coefficient
167 amtx(row, 2) = 1.0; // cx coefficient
168 } else {
169 amtx(row, 1) = 1.0; // cx coefficient
170 }
171 bvec(row) = observation.u;
172 }
173
174 return {std::move(amtx), std::move(bvec)};
175}
176
177static auto build_v_system(const std::vector<Observation<double>>& obs) -> LinearSystem {
178 const size_t nobs = obs.size();
179
180 Eigen::MatrixXd amtx(nobs, 2);
181 Eigen::VectorXd bvec(nobs);
182
183 for (size_t i = 0; i < nobs; ++i) {
184 const auto& observation = obs[i];
185 const int row = static_cast<int>(i);
186
187 amtx(row, 0) = observation.y; // fy coefficient
188 amtx(row, 1) = 1.0; // cy coefficient
189 bvec(row) = observation.v;
190 }
191
192 return {std::move(amtx), std::move(bvec)};
193}
194
195static auto solve_linear_system(const LinearSystem& system) -> std::optional<Eigen::VectorXd> {
196 Eigen::JacobiSVD<Eigen::MatrixXd> svd(system.amtx, Eigen::ComputeThinU | Eigen::ComputeThinV);
197
198 if (svd.singularValues().minCoeff() < 1e-12) {
199 return std::nullopt; // Degenerate system
200 }
201
202 return svd.solve(system.bvec);
203}
204
205static auto apply_bounds_and_fallback(const Eigen::VectorXd& xu, const Eigen::VectorXd& xv,
206 const std::vector<Observation<double>>& obs,
207 const CalibrationBounds& bounds, bool use_skew)
208 -> CameraMatrix {
209 const double fx = xu[0];
210 const double fy = xv[0];
211 const double cx = use_skew ? xu[2] : xu[1];
212 const double cy = xv[1];
213 const double skew = use_skew ? xu[1] : 0.0;
214
215 // Check if parameters are within bounds
216 const bool out_of_bounds = fx < bounds.fx_min || fx > bounds.fx_max || fy < bounds.fy_min ||
217 fy > bounds.fy_max || cx < bounds.cx_min || cx > bounds.cx_max ||
218 cy < bounds.cy_min || cy > bounds.cy_max ||
219 (use_skew && (skew < bounds.skew_min || skew > bounds.skew_max));
220
221 if (out_of_bounds) {
222 std::cerr << "Warning: Linear calibration produced unreasonable intrinsics\n";
223
224 // Compute fallback values
225 const double avg_u =
226 std::accumulate(obs.begin(), obs.end(), 0.0,
227 [](double sum, const auto& ob) { return sum + ob.u; }) /
228 static_cast<double>(obs.size());
229 const double avg_v =
230 std::accumulate(obs.begin(), obs.end(), 0.0,
231 [](double sum, const auto& ob) { return sum + ob.v; }) /
232 static_cast<double>(obs.size());
233
234 const double safe_fx = std::clamp(std::max(500.0, fx), bounds.fx_min, bounds.fx_max);
235 const double safe_fy = std::clamp(std::max(500.0, fy), bounds.fy_min, bounds.fy_max);
236 const double safe_cx = std::clamp(avg_u / 2.0, bounds.cx_min, bounds.cx_max);
237 const double safe_cy = std::clamp(avg_v / 2.0, bounds.cy_min, bounds.cy_max);
238 const double safe_skew =
239 use_skew ? std::clamp(skew, bounds.skew_min, bounds.skew_max) : 0.0;
240
241 return CameraMatrix{safe_fx, safe_fy, safe_cx, safe_cy, safe_skew};
242 }
243
244 return CameraMatrix{fx, fy, cx, cy, skew};
245}
246
248 const CameraMatrix& kmtx,
249 const Eigen::VectorXd& distortion)
250 -> std::vector<Observation<double>> {
251 std::vector<Observation<double>> corrected;
252 corrected.reserve(obs.size());
253
254 for (const auto& observation : obs) {
255 const Eigen::Vector2d norm(observation.x, observation.y);
256 const Eigen::Vector2d distorted = apply_distortion(norm, distortion);
257 const Eigen::Vector2d delta = distorted - norm;
258
259 const double u_corr = observation.u - kmtx.fx * delta.x() - kmtx.skew * delta.y();
260 const double v_corr = observation.v - kmtx.fy * delta.y();
261
262 corrected.push_back(Observation<double>{observation.x, observation.y, u_corr, v_corr});
263 }
264
265 return corrected;
266}
267
269 -> double {
270 return std::abs(k1.fx - k2.fx) + std::abs(k1.fy - k2.fy) + std::abs(k1.cx - k2.cx) +
271 std::abs(k1.cy - k2.cy) + std::abs(k1.skew - k2.skew);
272}
273
274static auto estimate_distortion_for_camera(const std::vector<Observation<double>>& obs,
275 const CameraMatrix& kmtx, int num_radial)
276 -> std::optional<Eigen::VectorXd> {
277 auto dist_opt = fit_distortion(obs, kmtx, num_radial);
278 return dist_opt ? std::make_optional(dist_opt->distortion) : std::nullopt;
279}
280
281// Compute a linear least-squares estimate of the camera intrinsics
282// (fx, fy, cx, cy[, skew]) from normalized correspondences. This ignores lens
283// distortion and solves either two or three independent systems depending on
284// whether skew is estimated:
285// u = fx * x + skew * y + cx
286// v = fy * y + cy
287// If there are insufficient observations or the design matrix is
288// degenerate, std::nullopt is returned.
289std::optional<CameraMatrix> estimate_intrinsics_linear(const std::vector<Observation<double>>& obs,
290 std::optional<CalibrationBounds> bounds_opt,
291 bool use_skew) {
292 if (obs.size() < 2) {
293 return std::nullopt;
294 }
295
296 // Build and solve u-equation system: u = fx*x + [skew*y] + cx
297 const auto u_system = build_u_system(obs, use_skew);
298 const auto xu_opt = solve_linear_system(u_system);
299 if (!xu_opt) {
300 return std::nullopt;
301 }
302
303 // Build and solve v-equation system: v = fy*y + cy
304 const auto v_system = build_v_system(obs);
305 const auto xv_opt = solve_linear_system(v_system);
306 if (!xv_opt) {
307 return std::nullopt;
308 }
309
310 const CalibrationBounds bounds = bounds_opt.value_or(CalibrationBounds{});
311 return apply_bounds_and_fallback(*xu_opt, *xv_opt, obs, bounds, use_skew);
312}
313
314// Alternate between fitting distortion coefficients and re-estimating
315// the camera matrix. This provides a better linear initialization for
316// subsequent non-linear optimization when moderate distortion is
317// present. If the initial linear estimate fails, std::nullopt is
318// returned.
320 int num_radial, int max_iterations, bool use_skew)
321 -> std::optional<PinholeCamera<BrownConradyd>> {
322 // Get initial linear estimate without distortion
323 auto kmtx_opt = estimate_intrinsics_linear(obs, std::nullopt, use_skew);
324 if (!kmtx_opt) {
325 return std::nullopt;
326 }
327
328 CameraMatrix kmtx = *kmtx_opt;
329 constexpr double convergence_threshold = 1e-6;
330
331 // Iteratively refine camera matrix by accounting for distortion
332 for (int iter = 0; iter < max_iterations; ++iter) {
333 // Estimate distortion for current camera matrix
334 const auto distortion_opt = estimate_distortion_for_camera(obs, kmtx, num_radial);
335 if (!distortion_opt) {
336 break; // Failed to estimate distortion
337 }
338
339 // Correct observations by removing estimated distortion
340 const auto corrected_obs = correct_observations_for_distortion(obs, kmtx, *distortion_opt);
341
342 // Re-estimate camera matrix using corrected observations
343 const auto kmtx_new_opt = estimate_intrinsics_linear(corrected_obs, std::nullopt, use_skew);
344 if (!kmtx_new_opt) {
345 break; // Failed to re-estimate camera matrix
346 }
347
348 // Check for convergence
349 const double change = compute_camera_matrix_difference(kmtx, *kmtx_new_opt);
350 kmtx = *kmtx_new_opt;
351
352 if (change < convergence_threshold) {
353 break; // Converged
354 }
355 }
356
357 // Final distortion estimation with refined camera matrix
358 const auto final_distortion_opt = fit_distortion_full(obs, kmtx, num_radial);
359 if (!final_distortion_opt) {
360 return std::nullopt;
361 }
362
364 camera.kmtx = kmtx;
365 camera.distortion.coeffs = final_distortion_opt->distortion;
366
367 return camera;
368}
369
370} // namespace calib
Pinhole camera model with intrinsics and distortion correction.
Definition pinhole.h:39
CameraMatrixT< Scalar > kmtx
Intrinsic camera matrix parameters.
Definition pinhole.h:42
DistortionT distortion
Distortion model instance.
Definition pinhole.h:43
bool success
Linear multi-camera extrinsics initialisation (DLT)
static auto build_u_system(const std::vector< Observation< double > > &obs, bool use_skew) -> LinearSystem
static auto symmetric_rms_px(const Eigen::Matrix3d &hmtx, const PlanarView &view, std::span< const int > inliers) -> double
static auto compute_planar_homographies(const std::vector< PlanarView > &views, const std::optional< RansacOptions > &ransac_opts) -> std::vector< HomographyResult >
static auto process_planar_view(const CameraMatrix &kmtx, const HomographyResult &hres) -> ViewEstimateData
auto estimate_intrinsics_linear(const std::vector< Observation< double > > &observations, std::optional< CalibrationBounds > bounds=std::nullopt, bool use_skew=false) -> std::optional< CameraMatrix >
Linear estimate with normalized observations.
auto fit_distortion(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:366
static auto correct_observations_for_distortion(const std::vector< Observation< double > > &obs, const CameraMatrix &kmtx, const Eigen::VectorXd &distortion) -> std::vector< Observation< double > >
auto estimate_intrinsics(const std::vector< PlanarView > &views, const IntrinsicsEstimOptions &opts={}) -> IntrinsicsEstimateResult
Estimate camera intrinsics from planar views using a linear method.
Eigen::Matrix3d skew(const Eigen::Vector3d &vec)
Definition se3_utils.h:21
auto apply_distortion(const Eigen::Matrix< T, 2, 1 > &norm_xy, const Eigen::Matrix< T, Eigen::Dynamic, 1 > &coeffs) -> Eigen::Matrix< T, 2, 1 >
Apply lens distortion to normalized coordinates.
Definition distortion.h:93
auto estimate_intrinsics_linear_iterative(const std::vector< Observation< double > > &observations, int num_radial, int max_iterations=k_default_max_iterations, bool use_skew=false) -> std::optional< PinholeCamera< BrownConradyd > >
static auto compute_camera_matrix_difference(const CameraMatrix &k1, const CameraMatrix &k2) -> double
auto pose_from_homography(const CameraMatrix &kmtx, const Eigen::Matrix3d &hmtx) -> PoseFromHResult
Estimates the camera pose from a homography matrix.
static auto apply_bounds_and_fallback(const Eigen::VectorXd &xu, const Eigen::VectorXd &xv, const std::vector< Observation< double > > &obs, const CalibrationBounds &bounds, bool use_skew) -> CameraMatrix
std::vector< PlanarObservation > PlanarView
Definition planarpose.h:26
static auto estimate_distortion_for_camera(const std::vector< Observation< double > > &obs, const CameraMatrix &kmtx, int num_radial) -> std::optional< Eigen::VectorXd >
auto zhang_intrinsics_from_hs(const std::vector< HomographyResult > &hs) -> std::optional< CameraMatrix >
Definition zhang.cpp:171
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 auto build_v_system(const std::vector< Observation< double > > &obs) -> LinearSystem
auto sanitize_intrinsics(const CameraMatrix &kmtx, const std::optional< CalibrationBounds > &bounds) -> std::pair< CameraMatrix, bool >
static auto solve_linear_system(const LinearSystem &system) -> std::optional< Eigen::VectorXd >
static auto fit(const std::vector< Datum > &data, std::span< const int > sample) -> std::optional< Model >
static constexpr size_t k_min_samples
std::vector< int > inliers
Definition homography.h:18
Eigen::Matrix3d hmtx
Definition homography.h:17
Options for linear intrinsic estimation from planar views.
Definition intrinsics.h:26
Result of linear intrinsic estimation.
Definition intrinsics.h:47
std::vector< ViewEstimateData > views
Per-view estimation data.
Definition intrinsics.h:52
CameraMatrix kmtx
Estimated intrinsic matrix.
Definition intrinsics.h:50
Eigen::VectorXd bvec
Eigen::MatrixXd amtx
Observation structure for distortion parameter estimation.
Definition distortion.h:69
HomographyResult homography
Definition intrinsics.h:36
Eigen::Isometry3d c_se3_t
Definition intrinsics.h:34