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();
20 motion_pair.
tra_a = affine_a.translation();
21 motion_pair.
tra_b = affine_b.translation();
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)
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;
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";
52 const std::vector<Eigen::Isometry3d>& cam_se3_target,
54 bool reject_axis_parallel,
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");
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) {
67 base_se3_gripper[idx_j], cam_se3_target[idx_j]);
69 if (
is_good_pair(motion_pair, min_angle, reject_axis_parallel, axis_parallel_eps)) {
70 pairs.push_back(std::move(motion_pair));
72 std::cerr <<
"Skipping pair (" << idx_i <<
"," << idx_j <<
'\n';
77 throw std::runtime_error(
78 "No valid motion pairs after filtering. Increase motion or relax thresholds.");
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);
96 constexpr double k_ridge = 1e-12;
97 Eigen::Vector3d rot_vec =
ridge_llsq(mat_m, vec_d, k_ridge);
103 const Eigen::Matrix3d& rot_x)
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);
117 constexpr double k_ridge = 1e-12;
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);
128 Eigen::Isometry3d pose = Eigen::Isometry3d::Identity();
129 pose.linear() = rot_x;
130 pose.translation() = g_tra_c;
Hand-eye calibration algorithms and utilities.
Linear multi-camera extrinsics initialisation (DLT)
Eigen::Vector3d log_so3(const Eigen::Matrix3d &rot_in)
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)
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)
Eigen::Matrix3d exp_so3(const Eigen::Vector3d &wvec)
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)
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.
Eigen::Matrix3d rot_b
Rotation matrices for motions A and B.
Eigen::Vector3d tra_b
Translation vectors for motions A and B.