3#include <Eigen/Geometry>
9struct PlaneRansacEstimator final {
10 using Datum = Eigen::Vector3d;
11 using Model = Eigen::Vector4d;
12 static constexpr std::size_t k_min_samples = 3;
14 static auto fit(
const std::vector<Datum>& data, std::span<const int> sample)
15 -> std::optional<Model> {
16 if (sample.size() < k_min_samples) {
19 const Eigen::Vector3d& p0 = data[
static_cast<std::size_t
>(sample[0])];
20 const Eigen::Vector3d& p1 = data[
static_cast<std::size_t
>(sample[1])];
21 const Eigen::Vector3d& p2 = data[
static_cast<std::size_t
>(sample[2])];
23 const Eigen::Vector3d v1 = p1 - p0;
24 const Eigen::Vector3d v2 = p2 - p0;
25 Eigen::Vector3d normal = v1.cross(v2);
26 const double norm = normal.norm();
31 const double d = -normal.dot(p0);
32 return Model{normal.x(), normal.y(), normal.z(), d};
35 static auto residual(
const Model& plane,
const Datum& point) ->
double {
36 const Eigen::Vector3d normal = plane.head<3>();
37 return std::abs(normal.dot(point) + plane[3]);
40 static auto refit(
const std::vector<Datum>& data, std::span<const int> inliers)
41 -> std::optional<Model> {
42 if (inliers.size() <
static_cast<std::ptrdiff_t
>(k_min_samples)) {
45 std::vector<Eigen::Vector3d> pts(inliers.size());
46 std::transform(inliers.begin(), inliers.end(), pts.begin(),
47 [&data](
int idx) { return data[static_cast<std::size_t>(idx)]; });
51 static auto is_degenerate(
const std::vector<Datum>& data, std::span<const int> sample) ->
bool {
52 if (sample.size() < k_min_samples) {
55 const Eigen::Vector3d& p0 = data[
static_cast<std::size_t
>(sample[0])];
56 const Eigen::Vector3d& p1 = data[
static_cast<std::size_t
>(sample[1])];
57 const Eigen::Vector3d& p2 = data[
static_cast<std::size_t
>(sample[2])];
58 const Eigen::Vector3d v1 = p1 - p0;
59 const Eigen::Vector3d v2 = p2 - p0;
60 return v1.cross(v2).norm() < 1e-12;
68auto fit_plane_svd(
const std::vector<Eigen::Vector3d>& pts) -> Eigen::Vector4d {
70 throw std::invalid_argument(
"Not enough points to fit a plane");
73 const Eigen::Vector3d centroid =
74 std::accumulate(pts.begin(), pts.end(), Eigen::Vector3d(Eigen::Vector3d::Zero())) /
75 static_cast<double>(pts.size());
76 Eigen::MatrixXd A(
static_cast<Eigen::Index
>(pts.size()), 3);
77 for (
size_t i = 0; i < pts.size(); ++i) {
78 A.row(
static_cast<Eigen::Index
>(i)) = (pts[i] - centroid).transpose();
80 Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeFullV);
81 Eigen::Vector3d normal = svd.matrixV().col(2);
82 double d = -normal.dot(centroid);
83 const double nrm = normal.norm();
84 return {normal.x() / nrm, normal.y() / nrm, normal.z() / nrm, d / nrm};
90 if (pts.size() < PlaneRansacEstimator::k_min_samples) {
94 auto ransac_res = ransac<PlaneRansacEstimator>(pts, opts);
95 if (!ransac_res.success) {
100 result.
plane = ransac_res.model;
101 result.
inliers = std::move(ransac_res.inliers);
Linear multi-camera extrinsics initialisation (DLT)
HomographyEstimator::Datum Datum
auto fit_plane_ransac(const std::vector< Eigen::Vector3d > &pts, const RansacOptions &opts={}) -> PlaneRansacResult
HomographyEstimator::Model Model
auto fit_plane_svd(const std::vector< Eigen::Vector3d > &pts) -> Eigen::Vector4d
std::vector< int > inliers