10#include <ceres/ceres.h>
11#include <ceres/rotation.h>
20 return std::accumulate(views.begin(), views.end(),
size_t{0},
21 [](
size_t total,
const auto& view) { return total + view.size(); });
24struct IntrinsicBlocks final :
public ProblemParamBlocks {
26 std::vector<std::array<double, 4>>
c_quat_t;
27 std::vector<std::array<double, 3>>
c_tra_t;
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,
37 for (
size_t i = 0; i < views.size(); ++i) {
46 std::vector<ParamBlock> blocks;
54 [](
const auto& q) { return ParamBlock{q.data(), q.size(), 3}; });
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}; });
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];
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]);
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());
87 CameraMatrix kmtx{blocks.intrinsics[0], blocks.intrinsics[1], blocks.intrinsics[2],
88 blocks.intrinsics[3], blocks.intrinsics[4]};
90 std::span<const int>(opts.fixed_distortion_indices),
91 std::span<const double>(opts.fixed_distortion_values));
95static ceres::Problem
build_problem(
const std::vector<PlanarView>& obs_views,
97 ceres::Problem problem;
98 auto* cost = CalibVPResidual::create(obs_views, opts.
num_radial);
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());
107 auto* loss = opts.
core.huber_delta > 0 ?
new ceres::HuberLoss(opts.
core.huber_delta) :
nullptr;
108 problem.AddResidualBlock(cost, loss, param_blocks);
110 for (
auto& i : blocks.c_quat_t) {
111 problem.SetManifold(i.data(),
new ceres::QuaternionManifold());
114 if (opts.
bounds.has_value()) {
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);
130 blocks.intrinsics.data(),
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;
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;
151 result.view_errors[i] = std::sqrt(view_error_sum / (view_points * 2));
156 const std::vector<PlanarView>& views,
const CameraMatrix& initial_guess,
162 const size_t num_views = views.size();
164 std::cerr <<
"Insufficient views for calibration (at least 4 required)." <<
'\n';
168 auto blocks = IntrinsicBlocks::create(views, initial_guess);
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");
178 result.
camera.distortion.coeffs = dr_opt->distortion;
182 blocks.populate_result(result);
184 double sum_squared_residuals = dr_opt->residuals.squaredNorm();
185 size_t total_residuals = total_obs * 2;
188 .value_or(Eigen::MatrixXd{});
Pinhole camera model with intrinsics and distortion correction.
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 >
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 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)
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.
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.
std::optional< CalibrationBounds > bounds
Parameter bounds.
int num_radial
Number of radial distortion coefficients.
bool optimize_skew
Estimate skew parameter.
OptimResult core
Optimization result details.
CameraT camera
Estimated camera parameters.
Eigen::MatrixXd covariance