Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
homography.cpp
Go to the documentation of this file.
2
3// std
4#include <array>
5#include <numbers>
6#include <numeric>
7#include <thread>
8
9// ceres
10#include <ceres/ceres.h>
11#include <ceres/rotation.h>
12
13#include "detail/ceresutils.h"
15
16namespace calib {
17
18static auto symmetric_rms_px(const Eigen::Matrix3d& hmtx,
19 const std::vector<PlanarObservation>& data,
20 std::span<const int> inliers) -> double {
21 if (inliers.empty()) {
22 return std::numeric_limits<double>::infinity();
23 }
24 const double sum_sq_err = std::accumulate(
25 inliers.begin(), inliers.end(), 0.0,
26 [&](double acc, int idx) { return acc + HomographyEstimator::residual(hmtx, data[idx]); });
27 return std::sqrt(sum_sq_err / (2.0 * static_cast<double>(inliers.size())));
28}
29
30static void estimate_homography_dlt(const PlanarView& data, HomographyResult& result) {
31 std::vector<int> inliers(data.size());
32 std::iota(inliers.begin(), inliers.end(), 0);
33 auto hmtx_opt = HomographyEstimator::fit(data, inliers);
34 if (!hmtx_opt.has_value()) {
35 std::cout << "Homography estimation failed.\n";
36 result.success = false;
37 return;
38 }
39 result.hmtx = hmtx_opt.value();
40 result.symmetric_rms_px = symmetric_rms_px(result.hmtx, data, inliers);
41 result.inliers = inliers;
42 result.success = true;
43}
44
46 const RansacOptions& ransac_opts) {
47 auto ransac_result = ransac<HomographyEstimator>(data, ransac_opts);
48 if (!ransac_result.success) {
49 std::cout << "Homography RANSAC failed.\n";
50 result.success = false;
51 return;
52 }
53 result.hmtx = ransac_result.model;
54 result.inliers = std::move(ransac_result.inliers);
55 result.symmetric_rms_px = symmetric_rms_px(result.hmtx, data, result.inliers);
56 result.success = true;
57
58 std::cout << "Homography inliers: " << result.inliers.size() << " / " << data.size()
59 << ", symmetric RMS: " << result.symmetric_rms_px << " px\n";
60}
61
62auto estimate_homography(const PlanarView& data, std::optional<RansacOptions> ransac_opts)
64 HomographyResult result;
65
66 if (!ransac_opts.has_value()) {
67 estimate_homography_dlt(data, result);
68 } else {
69 estimate_homography_ransac(data, result, ransac_opts.value());
70 }
71
72 return result;
73}
74
75using Vec2 = Eigen::Vector2d;
76using Mat3 = Eigen::Matrix3d;
77using HomographyParams = std::array<double, 8>;
78
79struct HomographyBlocks final : public ProblemParamBlocks {
81
82 static auto create(const Eigen::Matrix3d& init_h) -> HomographyBlocks {
83 HomographyBlocks blocks;
84 blocks.params = {init_h(0, 0), init_h(0, 1), init_h(0, 2), init_h(1, 0),
85 init_h(1, 1), init_h(1, 2), init_h(2, 0), init_h(2, 1)};
86 return blocks;
87 }
88
89 [[nodiscard]]
90 auto get_param_blocks() const -> std::vector<ParamBlock> override {
91 return {{params.data(), params.size(), 8}};
92 }
93};
94
95static auto params_to_h(const HomographyParams& params) -> Mat3 {
96 Mat3 mat_h;
97 mat_h << params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
98 1.0;
99 return mat_h;
100}
101
102// Ceres residual: maps (x,y) -> (u,v) using H(h) and compares to (u*, v*)
103struct HomographyResidual final {
104 double x_, y_, u_, v_;
105
106 HomographyResidual(double x, double y, double u, double v) : x_(x), y_(y), u_(u), v_(v) {}
107
108 template <typename T>
109 bool operator()(const T* const h, T* residuals) const {
110 // h[0..7]: 8 params, H22 = 1
111 // 0 1 2
112 // 3 4 5
113 // 6 7 8
114 Eigen::Matrix<T, 3, 3> hmtx;
115 hmtx << h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], T(1);
116 Eigen::Vector3<T> xyvec(T(x_), T(y_), T(1));
117
118 Eigen::Vector3<T> uvw = hmtx * xyvec;
119 Eigen::Vector2<T> uv_hat = uvw.hnormalized();
120 residuals[0] = uv_hat.x() - T(u_);
121 residuals[1] = uv_hat.y() - T(v_);
122 return true;
123 }
124
125 static ceres::CostFunction* create(double x, double y, double u, double v) {
126 return new ceres::AutoDiffCostFunction<HomographyResidual, 2, 8>(
127 new HomographyResidual(x, y, u, v));
128 }
129};
130
131static ceres::Problem build_problem(const PlanarView& data, const OptimOptions& options,
132 HomographyBlocks& blocks) {
133 ceres::Problem problem;
134 std::for_each(data.begin(), data.end(), [&](const auto& obs) {
135 problem.AddResidualBlock(
136 HomographyResidual::create(obs.object_xy.x(), obs.object_xy.y(), obs.image_uv.x(),
137 obs.image_uv.y()),
138 options.huber_delta > 0 ? new ceres::HuberLoss(options.huber_delta) : nullptr,
139 blocks.params.data());
140 });
141 return problem;
142}
143
144OptimizeHomographyResult optimize_homography(const PlanarView& data, const Eigen::Matrix3d& init_h,
145 const OptimOptions& options) {
146 if (data.size() < 4) {
147 throw std::invalid_argument("At least 4 correspondences are required.");
148 }
149
150 auto blocks = HomographyBlocks::create(init_h);
151 ceres::Problem problem = build_problem(data, options, blocks);
152
154 solve_problem(problem, options, &result.core);
155
156 Mat3 hmtx = params_to_h(blocks.params);
157 if (std::abs(hmtx(2, 2)) > 1e-15) {
158 hmtx /= hmtx(2, 2);
159 }
160 result.homography = hmtx;
161
162 if (options.compute_covariance) {
163 std::vector<double> residuals(data.size() * 2);
164 ceres::Problem::EvaluateOptions evopts;
165 problem.Evaluate(evopts, nullptr, &residuals, nullptr, nullptr);
166 const double ssr = std::accumulate(residuals.begin(), residuals.end(), 0.0,
167 [](double sum, double r) { return sum + r * r; });
168 auto optcov = compute_covariance(blocks, problem, ssr, residuals.size());
169 if (optcov.has_value()) {
170 result.core.covariance = std::move(optcov.value());
171 }
172 }
173
174 return result;
175}
176
177} // namespace calib
Linear multi-camera extrinsics initialisation (DLT)
static auto symmetric_rms_px(const Eigen::Matrix3d &hmtx, const PlanarView &view, std::span< const int > inliers) -> double
auto optimize_homography(const PlanarView &data, const Eigen::Matrix3d &init_h, const OptimOptions &options={}) -> OptimizeHomographyResult
Eigen::Matrix3d Mat3
static void estimate_homography_ransac(const PlanarView &data, HomographyResult &result, const RansacOptions &ransac_opts)
Eigen::Vector2d Vec2
auto estimate_homography(const PlanarView &data, std::optional< RansacOptions > ransac_opts=std::nullopt) -> HomographyResult
static auto params_to_h(const HomographyParams &params) -> Mat3
std::vector< PlanarObservation > PlanarView
Definition planarpose.h:26
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
std::array< double, 8 > HomographyParams
static auto build_problem(const std::vector< BundleObservation > &observations, const BundleOptions &opts, BundleBlocks< CameraT > &blocks) -> ceres::Problem
Definition bundle.cpp:84
void solve_problem(ceres::Problem &problem, const OptimOptions &opts, OptimResult *result)
Definition ceresutils.h:27
static void estimate_homography_dlt(const PlanarView &data, HomographyResult &result)
auto get_param_blocks() const -> std::vector< ParamBlock > override
HomographyParams params
static auto create(const Eigen::Matrix3d &init_h) -> HomographyBlocks
static auto fit(const std::vector< Datum > &data, std::span< const int > sample) -> std::optional< Model >
bool operator()(const T *const h, T *residuals) const
static ceres::CostFunction * create(double x, double y, double u, double v)
HomographyResidual(double x, double y, double u, double v)
std::vector< int > inliers
Definition homography.h:18
Eigen::Matrix3d hmtx
Definition homography.h:17
Eigen::MatrixXd covariance
Definition optimize.h:37
OptimResult core
Core optimization result.
Definition homography.h:13
const double * data
Definition ceresutils.h:46