Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
handeyedlt.cpp
Go to the documentation of this file.
2
3// std
5
6#include <iostream>
7#include <numbers>
8
9namespace calib {
10
11static auto make_motion_pair(const Eigen::Isometry3d& base_se3_gripper_a,
12 const Eigen::Isometry3d& cam_se3_target_a,
13 const Eigen::Isometry3d& base_se3_gripper_b,
14 const Eigen::Isometry3d& cam_se3_target_b) -> MotionPair {
15 Eigen::Isometry3d affine_a = base_se3_gripper_a.inverse() * base_se3_gripper_b;
16 Eigen::Isometry3d affine_b = cam_se3_target_a * cam_se3_target_b.inverse();
17 MotionPair motion_pair;
18 motion_pair.rot_a = project_to_so3(affine_a.linear());
19 motion_pair.rot_b = project_to_so3(affine_b.linear());
20 motion_pair.tra_a = affine_a.translation();
21 motion_pair.tra_b = affine_b.translation();
22 return motion_pair;
23}
24
25static auto is_good_pair(const MotionPair& motion_pair, double min_angle, bool reject_axis_parallel,
26 double axis_parallel_eps) -> bool {
27 Eigen::Vector3d alpha = log_so3(motion_pair.rot_a);
28 Eigen::Vector3d beta = log_so3(motion_pair.rot_b);
29 const double norm_a = alpha.norm();
30 const double norm_b = beta.norm();
31 const double min_rot = std::min(norm_a, norm_b);
32 if (min_rot < min_angle) {
33 std::cerr << "Motion pair with too small motion: " << (min_rot * 180.0 / std::numbers::pi)
34 << " deg\n";
35 return false;
36 }
37 if (reject_axis_parallel) {
38 const double aa = (norm_a < 1e-9) ? 0.0 : 1.0;
39 const double bb = (norm_b < 1e-9) ? 0.0 : 1.0;
40 if (aa * bb > 0.0) {
41 double sin_axis = (alpha.normalized().cross(beta.normalized())).norm();
42 if (sin_axis < axis_parallel_eps) {
43 std::cerr << "Motion pair with near-parallel axes\n";
44 return false;
45 }
46 }
47 }
48 return true;
49}
50
51auto build_all_pairs(const std::vector<Eigen::Isometry3d>& base_se3_gripper,
52 const std::vector<Eigen::Isometry3d>& cam_se3_target,
53 double min_angle_deg, // discard too-small motions
54 bool reject_axis_parallel, // guard against ill-conditioning
55 double axis_parallel_eps) -> std::vector<MotionPair> {
56 if (base_se3_gripper.size() < 2 || base_se3_gripper.size() != cam_se3_target.size()) {
57 throw std::runtime_error("Inconsistent hand-eye input sizes");
58 }
59 const size_t num_poses = base_se3_gripper.size();
60 const double min_angle = min_angle_deg * std::numbers::pi / 180.0;
61 std::vector<MotionPair> pairs;
62 pairs.reserve(num_poses * (num_poses - 1) / 2);
63 for (size_t idx_i = 0; idx_i + 1 < num_poses; ++idx_i) {
64 for (size_t idx_j = idx_i + 1; idx_j < num_poses; ++idx_j) {
65 MotionPair motion_pair =
66 make_motion_pair(base_se3_gripper[idx_i], cam_se3_target[idx_i],
67 base_se3_gripper[idx_j], cam_se3_target[idx_j]);
68
69 if (is_good_pair(motion_pair, min_angle, reject_axis_parallel, axis_parallel_eps)) {
70 pairs.push_back(std::move(motion_pair));
71 } else {
72 std::cerr << "Skipping pair (" << idx_i << "," << idx_j << '\n';
73 }
74 }
75 }
76 if (pairs.empty()) {
77 throw std::runtime_error(
78 "No valid motion pairs after filtering. Increase motion or relax thresholds.");
79 }
80 return pairs;
81}
82
83// ---------- weighted Tsai–Lenz rotation over all pairs ----------
84static auto estimate_rotation_allpairs_weighted(const std::vector<MotionPair>& pairs)
85 -> Eigen::Matrix3d {
86 const int num_pairs = static_cast<int>(pairs.size());
87 Eigen::MatrixXd mat_m(3 * num_pairs, 3);
88 Eigen::VectorXd vec_d(3 * num_pairs);
89 for (int idx = 0; idx < num_pairs; ++idx) {
90 Eigen::Vector3d alpha = log_so3(pairs[idx].rot_a);
91 Eigen::Vector3d beta = log_so3(pairs[idx].rot_b);
92 constexpr double k_weight = 1.0;
93 mat_m.block<3, 3>(static_cast<Eigen::Index>(3) * idx, 0) = k_weight * skew(alpha + beta);
94 vec_d.segment<3>(static_cast<Eigen::Index>(3) * idx) = k_weight * (beta - alpha);
95 }
96 constexpr double k_ridge = 1e-12;
97 Eigen::Vector3d rot_vec = ridge_llsq(mat_m, vec_d, k_ridge);
98 return exp_so3(rot_vec);
99}
100
101// ---------- weighted Tsai–Lenz translation over all pairs ----------
102static auto estimate_translation_allpairs_weighted(const std::vector<MotionPair>& pairs,
103 const Eigen::Matrix3d& rot_x)
104 -> Eigen::Vector3d {
105 const int num_pairs = static_cast<int>(pairs.size());
106 Eigen::MatrixXd mat_c(3 * num_pairs, 3);
107 Eigen::VectorXd vec_w(3 * num_pairs);
108 for (int idx = 0; idx < num_pairs; ++idx) {
109 const Eigen::Matrix3d& rot_a = pairs[idx].rot_a;
110 const Eigen::Vector3d& tran_a = pairs[idx].tra_a;
111 const Eigen::Vector3d& tran_b = pairs[idx].tra_b;
112 constexpr double k_weight = 1.0;
113 mat_c.block<3, 3>(static_cast<Eigen::Index>(3) * idx, 0) =
114 k_weight * (rot_a - Eigen::Matrix3d::Identity());
115 vec_w.segment<3>(static_cast<Eigen::Index>(3) * idx) = k_weight * (rot_x * tran_b - tran_a);
116 }
117 constexpr double k_ridge = 1e-12;
118 return ridge_llsq(mat_c, vec_w, k_ridge);
119}
120
121// ---------- public API: linear init + non-linear refine ----------
122auto estimate_handeye_dlt(const std::vector<Eigen::Isometry3d>& base_se3_gripper,
123 const std::vector<Eigen::Isometry3d>& camera_se3_target,
124 double min_angle_deg) -> Eigen::Isometry3d {
125 auto pairs = build_all_pairs(base_se3_gripper, camera_se3_target, min_angle_deg);
126 const Eigen::Matrix3d rot_x = estimate_rotation_allpairs_weighted(pairs);
127 const Eigen::Vector3d g_tra_c = estimate_translation_allpairs_weighted(pairs, rot_x);
128 Eigen::Isometry3d pose = Eigen::Isometry3d::Identity();
129 pose.linear() = rot_x;
130 pose.translation() = g_tra_c;
131 return pose;
132}
133
134} // namespace calib
Hand-eye calibration algorithms and utilities.
Linear multi-camera extrinsics initialisation (DLT)
Eigen::Vector3d log_so3(const Eigen::Matrix3d &rot_in)
Definition se3_utils.h:27
static auto is_good_pair(const MotionPair &motion_pair, double min_angle, bool reject_axis_parallel, double axis_parallel_eps) -> bool
Eigen::Matrix3d skew(const Eigen::Vector3d &vec)
Definition se3_utils.h:21
static auto make_motion_pair(const Eigen::Isometry3d &base_se3_gripper_a, const Eigen::Isometry3d &cam_se3_target_a, const Eigen::Isometry3d &base_se3_gripper_b, const Eigen::Isometry3d &cam_se3_target_b) -> MotionPair
Eigen::VectorXd ridge_llsq(const Mat &amtx, const Vec &bvec, double lambda=1e-10)
Definition se3_utils.h:58
Eigen::Matrix3d exp_so3(const Eigen::Vector3d &wvec)
Definition se3_utils.h:42
static auto estimate_translation_allpairs_weighted(const std::vector< MotionPair > &pairs, const Eigen::Matrix3d &rot_x) -> Eigen::Vector3d
Eigen::Matrix3d project_to_so3(const Eigen::Matrix3d &rmtx)
Definition se3_utils.h:10
auto estimate_handeye_dlt(const std::vector< Eigen::Isometry3d > &base_se3_gripper, const std::vector< Eigen::Isometry3d > &camera_se3_target, double min_angle_deg=1.0) -> Eigen::Isometry3d
Estimates the hand-eye transformation using the Tsai-Lenz algorithm with all pairs of input transform...
static auto estimate_rotation_allpairs_weighted(const std::vector< MotionPair > &pairs) -> Eigen::Matrix3d
auto build_all_pairs(const std::vector< Eigen::Isometry3d > &base_se3_gripper, const std::vector< Eigen::Isometry3d > &cam_se3_target, double min_angle_deg=1.0, bool reject_axis_parallel=true, double axis_parallel_eps=1e-3) -> std::vector< MotionPair >
Generate all valid motion pairs from pose sequences.
Motion pair structure for hand-eye calibration.
Definition handeye.h:34
Eigen::Vector3d tra_a
Definition handeye.h:36
Eigen::Matrix3d rot_b
Rotation matrices for motions A and B.
Definition handeye.h:35
Eigen::Vector3d tra_b
Translation vectors for motions A and B.
Definition handeye.h:36
Eigen::Matrix3d rot_a
Definition handeye.h:35