Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
intrinsics.cpp
Go to the documentation of this file.
2
3#include <fstream>
4#include <iostream>
5#include <limits>
6#include <numeric>
7#include <sstream>
8#include <stdexcept>
9
13
14namespace calib::pipeline {
15
16namespace {
17
18[[nodiscard]] auto count_occurrences(std::string_view text, std::string_view needle)
19 -> std::size_t {
20 if (needle.empty()) {
21 return 0;
22 }
23 std::size_t count = 0;
24 std::size_t pos = 0;
25 while (true) {
26 pos = text.find(needle, pos);
27 if (pos == std::string_view::npos) {
28 break;
29 }
30 ++count;
31 pos += needle.size();
32 }
33 return count;
34}
35
36} // namespace
37
39 const IntrinsicCalibrationOptions& opts, std::vector<ActiveView>& views)
40 -> std::vector<PlanarView> {
41 std::vector<PlanarView> planar_views;
42 views.clear();
43 planar_views.reserve(detections.images.size());
44 for (const auto& img : detections.images) {
45 if (img.points.size() < opts.min_corners_per_view) {
46 continue;
47 }
48 PlanarView view(img.points.size());
49 std::transform(img.points.begin(), img.points.end(), view.begin(), [&](const auto& pt) {
50 PlanarObservation obs;
51 obs.object_xy = Eigen::Vector2d(pt.local_x, pt.local_y);
52 obs.image_uv = Eigen::Vector2d(pt.x, pt.y);
53 return obs;
54 });
55 views.push_back({img.file, view.size()});
56 planar_views.push_back(std::move(view));
57 }
58 return planar_views;
59}
60
61auto bounds_from_image_size(const std::array<int, 2>& image_size) -> CalibrationBounds {
62 const double width = static_cast<double>(image_size[0]);
63 const double height = static_cast<double>(image_size[1]);
64 const double short_side = std::min(width, height);
65 const double long_side = std::max(width, height);
66
68 b.fx_min = b.fy_min = std::max(1.0, 0.25 * short_side);
69 b.fx_max = b.fy_max = std::numeric_limits<double>::max();
70 b.cx_min = 0.05 * width;
71 b.cx_max = 0.95 * width;
72 b.cy_min = 0.05 * height;
73 b.cy_max = 0.95 * height;
74 const double skew_limit = 0.05 * long_side;
75 b.skew_min = -skew_limit;
76 b.skew_max = skew_limit;
77 return b;
78}
79
81 const CameraConfig& cam_cfg,
82 const PlanarDetections& detections) const
85 output.total_input_views = detections.images.size();
86 output.min_corner_threshold = cfg.options.min_corners_per_view;
87
88 std::vector<ActiveView> active_views;
89 auto planar_views = collect_planar_views(detections, cfg.options, active_views);
90 output.accepted_views = planar_views.size();
91
92 if (planar_views.size() < 4) {
93 std::ostringstream oss;
94 oss << "Need at least 4 views with >= " << cfg.options.min_corners_per_view
95 << " corners. Only " << planar_views.size() << " usable views.";
96 throw std::runtime_error(oss.str());
97 }
98
100 std::string captured_warnings;
101 {
102 StreamCapture capture(std::cerr);
103 linear = estimate_intrinsics(planar_views, cfg.options.estim_options);
104 captured_warnings = capture.str();
105 }
106 output.invalid_k_warnings = count_occurrences(captured_warnings, "Invalid camera matrix K");
107 output.pose_warnings = count_occurrences(captured_warnings, "Homography decomposition failed");
108 if (output.invalid_k_warnings > 0 || output.pose_warnings > 0) {
109 std::cerr << "[" << cam_cfg.camera_id
110 << "] Linear stage warnings: " << output.invalid_k_warnings
111 << " invalid camera matrices, " << output.pose_warnings
112 << " decomposition failures" << '\n';
113 }
114 if (!linear.success) {
115 throw std::runtime_error("Linear intrinsic estimation failed to converge.");
116 }
117
118 std::vector<std::size_t> linear_view_indices(linear.views.size());
119 std::transform(linear.views.begin(), linear.views.end(), linear_view_indices.begin(),
120 [](const auto& v) { return v.view_index; });
121
123 if (cfg.options.refine) {
124 // Estimate initial poses for each view
125 std::vector<Eigen::Isometry3d> init_c_se3_t(planar_views.size());
126 std::transform(planar_views.begin(), planar_views.end(), init_c_se3_t.begin(),
127 [&](const auto& view) { return estimate_planar_pose(view, linear.kmtx); });
128
129 PinholeCamera<BrownConradyd> init_camera(linear.kmtx, Eigen::VectorXd::Zero(5));
130 refine =
131 optimize_intrinsics(planar_views, init_camera, init_c_se3_t, cfg.options.optim_options);
132 if (!refine.core.success) {
133 std::cerr << "Warning: Non-linear refinement did not converge. Using linear result."
134 << '\n';
135 refine.camera = PinholeCamera<BrownConradyd>(linear.kmtx, Eigen::VectorXd::Zero(5));
136 }
137 } else {
138 refine.core.success = true;
139 refine.camera = PinholeCamera<BrownConradyd>(linear.kmtx, Eigen::VectorXd::Zero(5));
140 }
141
142 if (refine.camera.distortion.coeffs.size() == 0) {
143 refine.camera.distortion.coeffs = Eigen::VectorXd::Zero(5);
144 }
145
146 output.linear_kmtx = linear.kmtx;
147 output.linear_view_indices = std::move(linear_view_indices);
148 output.refine_result = std::move(refine);
149 output.active_views = std::move(active_views);
150 output.used_views = planar_views.size();
151 output.total_points_used = 0;
152 for (const auto& v : output.active_views) {
153 output.total_points_used += v.corner_count;
154 }
155
156 return output;
157}
158
159void print_calibration_summary(std::ostream& out, const CameraConfig& cam_cfg,
160 const IntrinsicCalibrationOutputs& outputs) {
161 out << "== Camera " << cam_cfg.camera_id << " ==\n";
162 if (outputs.invalid_k_warnings > 0 || outputs.pose_warnings > 0) {
163 out << "Linear stage warnings: " << outputs.invalid_k_warnings
164 << " invalid camera matrices, " << outputs.pose_warnings
165 << " homography decompositions\n";
166 }
167 out << "Initial fx/fy/cx/cy: " << outputs.linear_kmtx.fx << ", " << outputs.linear_kmtx.fy
168 << ", " << outputs.linear_kmtx.cx << ", " << outputs.linear_kmtx.cy << '\n';
169 const auto& refined = outputs.refine_result.camera;
170 out << "Refined fx/fy/cx/cy: " << refined.kmtx.fx << ", " << refined.kmtx.fy << ", "
171 << refined.kmtx.cx << ", " << refined.kmtx.cy << '\n';
172 out << "Distortion coeffs: " << outputs.refine_result.camera.distortion.coeffs.transpose()
173 << '\n';
174 out << "Views considered: " << outputs.total_input_views
175 << ", after threshold: " << outputs.accepted_views << '\n';
176 out << "Per-view RMS (px):";
177 for (double err : outputs.refine_result.view_errors) {
178 out << ' ' << err;
179 }
180 out << "\n";
181}
182
183auto load_calibration_config_impl(const std::filesystem::path& path) -> IntrinsicCalibrationConfig {
184 std::ifstream stream(path);
185 nlohmann::json json_cfg;
186 stream >> json_cfg;
187 IntrinsicCalibrationConfig cfg = json_cfg;
188 return cfg;
189}
190
191auto load_calibration_config(const std::filesystem::path& path)
192 -> std::optional<IntrinsicCalibrationConfig> {
193 try {
194 return load_calibration_config_impl(path);
195 } catch (const std::exception& e) {
196 std::cerr << "Failed to load calibration config from " << path << ": " << e.what()
197 << std::endl;
198 return std::nullopt;
199 }
200}
201
202} // namespace calib::pipeline
Pinhole camera model with intrinsics and distortion correction.
Definition pinhole.h:39
auto str() const -> std::string
auto calibrate(const IntrinsicCalibrationConfig &cfg, const CameraConfig &cam_cfg, const PlanarDetections &detections) const -> IntrinsicCalibrationOutputs
PlanarDetections detections
Definition loaders.cpp:15
auto collect_planar_views(const PlanarDetections &detections, const IntrinsicCalibrationOptions &opts, std::vector< ActiveView > &views) -> std::vector< PlanarView >
auto load_calibration_config(const std::filesystem::path &path) -> std::optional< IntrinsicCalibrationConfig >
auto bounds_from_image_size(const std::array< int, 2 > &image_size) -> CalibrationBounds
void print_calibration_summary(std::ostream &out, const CameraConfig &cam_cfg, const IntrinsicCalibrationOutputs &outputs)
auto load_calibration_config_impl(const std::filesystem::path &path) -> IntrinsicCalibrationConfig
auto estimate_intrinsics(const std::vector< PlanarView > &views, const IntrinsicsEstimOptions &opts={}) -> IntrinsicsEstimateResult
Estimate camera intrinsics from planar views using a linear method.
auto optimize_intrinsics(const std::vector< PlanarView > &views, const CameraT &init_camera, std::vector< Eigen::Isometry3d > init_c_se3_t, const IntrinsicsOptimOptions &opts={}) -> IntrinsicsOptimizationResult< CameraT >
std::vector< PlanarObservation > PlanarView
Definition planarpose.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
OptimResult core
Optimization result details.
Definition intrinsics.h:24
CameraT camera
Estimated camera parameters.
Definition intrinsics.h:25
IntrinsicsOptimizationResult< PinholeCamera< BrownConradyd > > refine_result
Definition intrinsics.h:55
std::vector< std::size_t > linear_view_indices
Definition intrinsics.h:54
std::vector< ActiveView > active_views
Definition intrinsics.h:56
std::vector< PlanarImageDetections > images
Definition dataset.h:38