22 std::span<const int> inliers) ->
double {
23 if (inliers.empty()) {
24 return std::numeric_limits<double>::infinity();
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())));
33 const std::optional<RansacOptions>& ransac_opts)
34 -> std::vector<HomographyResult> {
35 std::vector<HomographyResult> homographies;
36 homographies.reserve(views.size());
38 for (
const auto& view : views) {
43 homographies.push_back(std::move(result));
47 std::vector<int> indices(view.size());
48 std::iota(indices.begin(), indices.end(), 0);
50 if (ransac_opts.has_value()) {
51 auto ransac_res = ransac<HomographyEstimator>(view, ransac_opts.value());
52 if (!ransac_res.success) {
54 homographies.push_back(std::move(result));
57 result.
hmtx = ransac_res.model;
58 result.
inliers = std::move(ransac_res.inliers);
59 if (std::abs(result.
hmtx(2, 2)) > 1e-15) {
66 if (!hopt.has_value()) {
68 homographies.push_back(std::move(result));
71 result.
hmtx = hopt.value();
72 if (std::abs(result.
hmtx(2, 2)) > 1e-15) {
80 homographies.push_back(std::move(result));
93 if (!pose_res.success) {
94 std::cerr <<
"Warning: Homography decomposition failed: " << pose_res.message <<
"\n";
96 ved.
c_se3_t = std::move(pose_res.c_se3_t);
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),
116 if (!zhang_kmtx_opt.has_value()) {
117 std::cout <<
"Zhang intrinsic estimation failed.\n";
122 auto [sanitized_kmtx, modified] =
sanitize_intrinsics(zhang_kmtx_opt.value(), opts.bounds);
123 result.
kmtx = sanitized_kmtx;
127 result.
log =
"Intrinsics sanitized by bounds.";
130 result.
views.resize(valid_hs.size());
131 std::transform(valid_hs.begin(), valid_hs.end(), result.
views.begin(),
133 return process_planar_view(sanitized_kmtx, hres);
137 for (
size_t i = 0; i < planar_homographies.size(); ++i) {
138 if (planar_homographies[i].
success) {
139 result.
views[vidx].view_index = i;
154 const size_t nobs = obs.size();
155 const int cols = use_skew ? 3 : 2;
157 Eigen::MatrixXd amtx(nobs, cols);
158 Eigen::VectorXd bvec(nobs);
160 for (
size_t i = 0; i < nobs; ++i) {
161 const auto& observation = obs[i];
162 const int row =
static_cast<int>(i);
164 amtx(row, 0) = observation.x;
166 amtx(row, 1) = observation.y;
171 bvec(row) = observation.u;
174 return {std::move(amtx), std::move(bvec)};
178 const size_t nobs = obs.size();
180 Eigen::MatrixXd amtx(nobs, 2);
181 Eigen::VectorXd bvec(nobs);
183 for (
size_t i = 0; i < nobs; ++i) {
184 const auto& observation = obs[i];
185 const int row =
static_cast<int>(i);
187 amtx(row, 0) = observation.y;
189 bvec(row) = observation.v;
192 return {std::move(amtx), std::move(bvec)};
196 Eigen::JacobiSVD<Eigen::MatrixXd> svd(system.amtx, Eigen::ComputeThinU | Eigen::ComputeThinV);
198 if (svd.singularValues().minCoeff() < 1e-12) {
202 return svd.solve(system.bvec);
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;
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));
222 std::cerr <<
"Warning: Linear calibration produced unreasonable intrinsics\n";
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());
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());
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;
241 return CameraMatrix{safe_fx, safe_fy, safe_cx, safe_cy, safe_skew};
249 const Eigen::VectorXd& distortion)
250 -> std::vector<Observation<double>> {
251 std::vector<Observation<double>> corrected;
252 corrected.reserve(obs.size());
254 for (
const auto& observation : obs) {
255 const Eigen::Vector2d norm(observation.x, observation.y);
257 const Eigen::Vector2d delta = distorted - norm;
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();
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);
276 -> std::optional<Eigen::VectorXd> {
278 return dist_opt ? std::make_optional(dist_opt->distortion) : std::nullopt;
290 std::optional<CalibrationBounds> bounds_opt,
292 if (obs.size() < 2) {
320 int num_radial,
int max_iterations,
bool use_skew)
321 -> std::optional<PinholeCamera<BrownConradyd>> {
329 constexpr double convergence_threshold = 1e-6;
332 for (
int iter = 0; iter < max_iterations; ++iter) {
335 if (!distortion_opt) {
350 kmtx = *kmtx_new_opt;
352 if (change < convergence_threshold) {
359 if (!final_distortion_opt) {
365 camera.
distortion.coeffs = final_distortion_opt->distortion;
Pinhole camera model with intrinsics and distortion correction.
CameraMatrixT< Scalar > kmtx
Intrinsic camera matrix parameters.
DistortionT distortion
Distortion model instance.
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 > >
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)
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.
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
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 >
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 > >
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
Options for linear intrinsic estimation from planar views.
Result of linear intrinsic estimation.
std::vector< ViewEstimateData > views
Per-view estimation data.
CameraMatrix kmtx
Estimated intrinsic matrix.
Observation structure for distortion parameter estimation.
HomographyResult homography
Eigen::Isometry3d c_se3_t