Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
homographyestimator.cpp
Go to the documentation of this file.
2
3// std
4#include <algorithm>
5#include <numbers>
6#include <numeric>
7#include <random>
8#include <span>
9#include <vector>
10
11#include "calib/estimation/linear/homography.h" // for estimate_homography
12
13namespace calib {
14
15// Hartley normalization
16static auto normalize_points_2d(const std::vector<Eigen::Vector2d>& pts,
17 std::vector<Eigen::Vector2d>& out) -> Eigen::Matrix3d {
18 out.resize(pts.size());
19
20 const Eigen::Vector2d centroid =
21 std::accumulate(pts.begin(), pts.end(), Eigen::Vector2d{0, 0}) /
22 std::max<size_t>(1, pts.size());
23 const double mean_dist = std::accumulate(pts.begin(), pts.end(), 0.0,
24 [&centroid](double sum, const Eigen::Vector2d& p) {
25 return sum + (p - centroid).norm();
26 }) /
27 static_cast<double>(std::max<size_t>(1, pts.size()));
28 const double sigma = (mean_dist > 0) ? std::numbers::sqrt2 / mean_dist : 1.0;
29
30 Eigen::Matrix3d transform = Eigen::Matrix3d::Identity();
31 transform(0, 0) = sigma;
32 transform(1, 1) = sigma;
33 transform(0, 2) = -sigma * centroid.x();
34 transform(1, 2) = -sigma * centroid.y();
35
36 std::transform(pts.begin(), pts.end(), out.begin(),
37 [&transform](const Eigen::Vector2d& pt) -> Eigen::Vector2d {
38 Eigen::Vector3d hp(pt.x(), pt.y(), 1.0);
39 Eigen::Vector3d hn = transform * hp;
40 return hn.hnormalized();
41 });
42 return {transform};
43}
44
45static auto dlt_homography_normalized(const std::vector<Eigen::Vector2d>& src_xy,
46 const std::vector<Eigen::Vector2d>& dst_uv)
47 -> Eigen::Matrix3d {
48 // Build A from normalized correspondences (2N x 9)
49 const auto npts = static_cast<Eigen::Index>(src_xy.size());
50 Eigen::MatrixXd amtx(2 * npts, 9);
51 for (Eigen::Index i = 0; i < npts; ++i) {
52 const double xcoord = src_xy[i].x();
53 const double ycoord = src_xy[i].y();
54 const double ucoord = dst_uv[i].x();
55 const double vcoord = dst_uv[i].y();
56
57 amtx.row(2 * i) << -xcoord, -ycoord, -1, 0, 0, 0, ucoord * xcoord, ucoord * ycoord, ucoord;
58 amtx.row(2 * i + 1) << 0, 0, 0, -xcoord, -ycoord, -1, vcoord * xcoord, vcoord * ycoord,
59 vcoord;
60 }
61
62 Eigen::JacobiSVD<Eigen::MatrixXd> svd(amtx, Eigen::ComputeFullV);
63 Eigen::VectorXd hvec = svd.matrixV().col(8);
64 Eigen::Matrix3d hmtx;
65 hmtx << hvec(0), hvec(1), hvec(2), hvec(3), hvec(4), hvec(5), hvec(6), hvec(7), hvec(8);
66 return hmtx / hmtx(2, 2);
67}
68
69static auto normalize_and_estimate_homography(const std::vector<Eigen::Vector2d>& src,
70 const std::vector<Eigen::Vector2d>& dst)
71 -> Eigen::Matrix3d {
72 std::vector<Eigen::Vector2d> src_n;
73 std::vector<Eigen::Vector2d> dst_n;
74 const Eigen::Matrix3d t_src = normalize_points_2d(src, src_n);
75 const Eigen::Matrix3d t_dst = normalize_points_2d(dst, dst_n);
76 Eigen::Matrix3d hnorm = dlt_homography_normalized(src_n, dst_n); // Hn: src_n -> dst_n
77 return t_dst.inverse() * hnorm * t_src;
78}
79
80static auto symmetric_transfer_error(const Eigen::Matrix3d& hmtx, const Eigen::Vector2d& xy,
81 const Eigen::Vector2d& uv) -> double {
82 auto hmul = [](const Eigen::Matrix3d& M, const Eigen::Vector2d& p) -> Eigen::Vector2d {
83 Eigen::Vector3d hp(p.x(), p.y(), 1.0);
84 Eigen::Vector3d q = M * hp;
85 return Eigen::Vector2d{q.hnormalized()};
86 };
87 const Eigen::Matrix3d hmtxinv = hmtx.inverse();
88 const Eigen::Vector2d uv_hat = hmul(hmtx, xy);
89 const Eigen::Vector2d xy_hat = hmul(hmtxinv, uv);
90 const double e1 = (uv - uv_hat).norm();
91 const double e2 = (xy - xy_hat).norm();
92 return std::sqrt(0.5 * (e1 * e1 + e2 * e2));
93}
94
97
98namespace {
99
100[[nodiscard]] auto has_near_collinear_triplet(const std::vector<Datum>& data,
101 std::span<const int> sample) -> bool {
102 const auto tri_area2 = [](const Eigen::Vector2d& a, const Eigen::Vector2d& b,
103 const Eigen::Vector2d& c) {
104 return std::abs((b - a).x() * (c - a).y() - (b - a).y() * (c - a).x());
105 };
106 constexpr double eps = 1e-6;
107 for (size_t i = 0; i < sample.size(); ++i) {
108 for (size_t j = i + 1; j < sample.size(); ++j) {
109 for (size_t k = j + 1; k < sample.size(); ++k) {
110 const double area = tri_area2(data[sample[i]].object_xy, data[sample[j]].object_xy,
111 data[sample[k]].object_xy);
112 if (area < eps) {
113 return true;
114 }
115 }
116 }
117 }
118 return false;
119}
120
121} // namespace
122
123auto HomographyEstimator::fit(const std::vector<Datum>& data, std::span<const int> sample)
124 -> std::optional<Model> {
125 if (sample.size() < k_min_samples) {
126 std::cout << "HomographyEstimator::fit: sample too small\n";
127 return std::nullopt;
128 }
129 std::vector<Eigen::Vector2d> src;
130 std::vector<Eigen::Vector2d> dst;
131 src.reserve(sample.size());
132 dst.reserve(sample.size());
133 for (int idx : sample) {
134 src.push_back(data[idx].object_xy);
135 dst.push_back(data[idx].image_uv);
136 }
137 Eigen::Matrix3d hmtx = normalize_and_estimate_homography(src, dst);
138 if (!std::isfinite(hmtx(0, 0))) {
139 std::cout << "HomographyEstimator::fit: non-finite homography\n";
140 return std::nullopt;
141 }
142 return hmtx;
143}
144
145auto HomographyEstimator::residual(const Model& hmtx, const Datum& observation) -> double {
146 return symmetric_transfer_error(hmtx, observation.object_xy, observation.image_uv);
147}
148
149// Optional: better final model on all inliers
150auto HomographyEstimator::refit(const std::vector<Datum>& data, std::span<const int> inliers)
151 -> std::optional<Model> {
152 if (inliers.size() < k_min_samples) {
153 return std::nullopt;
154 }
155 std::vector<Eigen::Vector2d> src;
156 std::vector<Eigen::Vector2d> dst;
157 src.reserve(inliers.size());
158 dst.reserve(inliers.size());
159 for (int idx : inliers) {
160 src.push_back(data[idx].object_xy);
161 dst.push_back(data[idx].image_uv);
162 }
163 Eigen::Matrix3d hmtx = normalize_and_estimate_homography(src, dst);
164 if (!std::isfinite(hmtx(0, 0))) {
165 return std::nullopt;
166 }
167 return hmtx;
168}
169
170// Optional: reject degenerate minimal sets (near-collinear points)
171auto HomographyEstimator::is_degenerate(const std::vector<Datum>& data, std::span<const int> sample)
172 -> bool {
173 return has_near_collinear_triplet(data, sample);
174}
175
176} // namespace calib
Linear multi-camera extrinsics initialisation (DLT)
static auto normalize_points_2d(const std::vector< Eigen::Vector2d > &pts, std::vector< Eigen::Vector2d > &out) -> Eigen::Matrix3d
static auto normalize_and_estimate_homography(const std::vector< Eigen::Vector2d > &src, const std::vector< Eigen::Vector2d > &dst) -> Eigen::Matrix3d
static auto symmetric_transfer_error(const Eigen::Matrix3d &hmtx, const Eigen::Vector2d &xy, const Eigen::Vector2d &uv) -> double
HomographyEstimator::Model Model
static auto dlt_homography_normalized(const std::vector< Eigen::Vector2d > &src_xy, const std::vector< Eigen::Vector2d > &dst_uv) -> Eigen::Matrix3d
static auto is_degenerate(const std::vector< Datum > &data, std::span< const int > sample) -> bool
static auto fit(const std::vector< Datum > &data, std::span< const int > sample) -> std::optional< Model >
static auto residual(const Model &hmtx, const Datum &observation) -> double
static auto refit(const std::vector< Datum > &data, std::span< const int > inliers) -> std::optional< Model >