|
|
@@ -169,9 +169,71 @@ namespace camera_calibrator_impl {
|
|
|
return dis_sum / corner_num;
|
|
|
}
|
|
|
|
|
|
+ glm::mat4 hand_eye_calib::calib_hand_eye() {
|
|
|
+ // build hand-eye calibration problem
|
|
|
+ auto ref_r_vec = std::vector<cv::Mat>(); // tracker in reference
|
|
|
+ auto ref_t_vec = std::vector<cv::Mat>();
|
|
|
+ for (auto &info: img_pool) {
|
|
|
+ auto ref_mat = calc_track_mat(info.sample_ts + ts_offset);
|
|
|
+ ref_mat = glm::inverse(ref_mat);
|
|
|
+ auto r_mat = glm::mat3(ref_mat);
|
|
|
+ auto t_vec = glm::vec3(to_translation(ref_mat)); // fourth column of matrix
|
|
|
+ ref_r_vec.push_back(to_cv_mat(r_mat));
|
|
|
+ ref_t_vec.push_back(to_cv_mat(t_vec));
|
|
|
+ }
|
|
|
+
|
|
|
+ cv::Mat ret_r, ret_t;
|
|
|
+ cv::calibrateRobotWorldHandEye(
|
|
|
+ cam_r_vec, cam_t_vec, // chess board in camera
|
|
|
+ ref_r_vec, ref_t_vec, // tracker in reference
|
|
|
+ aux_r, aux_t, // tracker in chess board
|
|
|
+ ret_r, ret_t); // reference in camera
|
|
|
+ return to_transform_mat(to_vec3(ret_t), to_mat3(ret_r));
|
|
|
+ }
|
|
|
+
|
|
|
+ cv::Scalar hand_eye_calib::evaluate_hand_eye(const glm::mat4 &ret_mat) {
|
|
|
+ auto aux_mat = to_transform_mat(to_vec3(aux_t), to_mat3(aux_r));
|
|
|
+ auto chess_mat = glm::inverse(aux_mat); // chess board in traker
|
|
|
+ auto err_mat = cv::Mat(sample_num, corner_num, CV_32FC2);
|
|
|
+ auto obj_ps_err = std::vector<cv::Point3f>();
|
|
|
+ std::ranges::transform(obj_ps, std::back_inserter(obj_ps_err), [](cv::Vec3f &p) {
|
|
|
+ return cv::Point3f(p[0], p[1], p[2]);
|
|
|
+ });
|
|
|
+ for (auto j = 0; j < sample_num; ++j) {
|
|
|
+ auto &item = img_pool[j];
|
|
|
+ auto proj_mat = ret_mat // reference in camera
|
|
|
+ * glm::inverse(calc_track_mat(item.sample_ts + ts_offset)) // tracker in reference
|
|
|
+ * chess_mat; // chess board in tracker
|
|
|
+ auto cam_mat = to_transform_mat( // chess board in camera
|
|
|
+ to_vec3(cam_t_vec[j]), to_mat3(cam_r_vec[j]));
|
|
|
+ auto loop_mat = glm::inverse(proj_mat) // camera in chess board
|
|
|
+ * cam_mat; // chess board in camera
|
|
|
+ for (auto i = 0; i < corner_num; ++i) {
|
|
|
+ auto p_gold = to_vec3(obj_ps[i]);
|
|
|
+ auto p_pred = transform_p(loop_mat, p_gold);
|
|
|
+ auto err_3d = glm::distance(p_gold, p_pred);
|
|
|
+ err_mat.at<cv::Vec2f>(j, i)[0] = err_3d;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto proj_r = to_cv_rodigues(glm::mat3(proj_mat));
|
|
|
+ auto proj_t = to_cv_mat(to_translation(proj_mat));
|
|
|
+ auto c_pred = std::vector<cv::Point2f>();
|
|
|
+ cv::projectPoints(obj_ps_err, proj_r, proj_t,
|
|
|
+ intrinsic_cv_mat, dist_coeffs_mat,
|
|
|
+ c_pred);
|
|
|
+ assert(c_pred.size() == corner_num);
|
|
|
+ for (auto i = 0; i < corner_num; ++i) {
|
|
|
+ auto c_gold = to_vec2(item.corner[i]);
|
|
|
+ auto err_2d = glm::distance(c_gold, to_vec2(c_pred[i]));
|
|
|
+ err_mat.at<cv::Vec2f>(j, i)[1] = err_2d;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return cv::mean(err_mat);
|
|
|
+ }
|
|
|
+
|
|
|
void hand_eye_calib::calc() {
|
|
|
- auto sample_num = img_pool.size();
|
|
|
- auto corner_num = img_pool.begin()->corner.size();
|
|
|
+ sample_num = img_pool.size();
|
|
|
+ corner_num = img_pool.begin()->corner.size();
|
|
|
|
|
|
// select some images for calibrateCamera
|
|
|
static constexpr auto pix_dis_threshold = 128.0f;
|
|
|
@@ -192,13 +254,12 @@ namespace camera_calibrator_impl {
|
|
|
std::vector<object_points_type>(select_num, obj_ps);
|
|
|
|
|
|
cv::Mat r_vec_ignore, t_vec_ignore;
|
|
|
- auto intrinsic_cv_mat = cv::Mat();
|
|
|
- auto dist_coeffs_mat = cv::Mat();
|
|
|
auto err = cv::calibrateCamera(
|
|
|
obj_ps_in, img_ps_in, img_size,
|
|
|
intrinsic_cv_mat, dist_coeffs_mat,
|
|
|
r_vec_ignore, t_vec_ignore,
|
|
|
cv::CALIB_RATIONAL_MODEL); // use k4, k5, k6
|
|
|
+ assert(!isnan(err));
|
|
|
SPDLOG_INFO("Overall camera calibration error is {:.2f}pix", err);
|
|
|
intrinsic_mat = to_mat3(intrinsic_cv_mat);
|
|
|
|
|
|
@@ -212,8 +273,8 @@ namespace camera_calibrator_impl {
|
|
|
}
|
|
|
|
|
|
// solve PnP for each image
|
|
|
- auto cam_r_vec = std::vector<cv::Mat>(); // chess board in camera
|
|
|
- auto cam_t_vec = std::vector<cv::Mat>();
|
|
|
+ cam_r_vec = std::vector<cv::Mat>(); // chess board in camera
|
|
|
+ cam_t_vec = std::vector<cv::Mat>();
|
|
|
auto obj_ps_pnp = std::vector<cv::Point3d>();
|
|
|
std::ranges::transform(obj_ps, std::back_inserter(obj_ps_pnp), [](cv::Vec3f &p) {
|
|
|
return cv::Point3d(p[0], p[1], p[2]);
|
|
|
@@ -253,69 +314,37 @@ namespace camera_calibrator_impl {
|
|
|
cam_t_vec.push_back(cam_t);
|
|
|
}
|
|
|
|
|
|
- static constexpr auto ts_offset = -80000; // us
|
|
|
-
|
|
|
- // build hand-eye calibration problem
|
|
|
- auto ref_r_vec = std::vector<cv::Mat>(); // tracker in reference
|
|
|
- auto ref_t_vec = std::vector<cv::Mat>();
|
|
|
- for (auto &info: img_pool) {
|
|
|
- auto ref_mat = calc_track_mat(info.sample_ts + ts_offset);
|
|
|
- ref_mat = glm::inverse(ref_mat);
|
|
|
- auto r_mat = glm::mat3(ref_mat);
|
|
|
- auto t_vec = glm::vec3(to_translation(ref_mat)); // fourth column of matrix
|
|
|
- ref_r_vec.push_back(to_cv_mat(r_mat));
|
|
|
- ref_t_vec.push_back(to_cv_mat(t_vec));
|
|
|
- }
|
|
|
-
|
|
|
- cv::Mat ret_r, ret_t;
|
|
|
- cv::Mat aux_r, aux_t;
|
|
|
- cv::calibrateRobotWorldHandEye(
|
|
|
- cam_r_vec, cam_t_vec, // chess board in camera
|
|
|
- ref_r_vec, ref_t_vec, // tracker in reference
|
|
|
- aux_r, aux_t, // tracker in chess board
|
|
|
- ret_r, ret_t); // reference in camera
|
|
|
- auto ret_mat = to_transform_mat(to_vec3(ret_t), to_mat3(ret_r));
|
|
|
- result_mat = glm::inverse(ret_mat); // camera in reference
|
|
|
-
|
|
|
- // projection error
|
|
|
- auto aux_mat = to_transform_mat(to_vec3(aux_t), to_mat3(aux_r));
|
|
|
- auto chess_mat = glm::inverse(aux_mat); // chess board in traker
|
|
|
- auto err_mat = cv::Mat(sample_num, corner_num, CV_32FC2);
|
|
|
- auto obj_ps_err = std::vector<cv::Point3f>();
|
|
|
- std::ranges::transform(obj_ps, std::back_inserter(obj_ps_err), [](cv::Vec3f &p) {
|
|
|
- return cv::Point3f(p[0], p[1], p[2]);
|
|
|
- });
|
|
|
- for (auto j = 0; j < sample_num; ++j) {
|
|
|
- auto &item = img_pool[j];
|
|
|
- auto proj_mat = ret_mat // reference in camera
|
|
|
- * glm::inverse(calc_track_mat(item.sample_ts + ts_offset)) // tracker in reference
|
|
|
- * chess_mat; // chess board in tracker
|
|
|
- auto cam_mat = to_transform_mat( // chess board in camera
|
|
|
- to_vec3(cam_t_vec[j]), to_mat3(cam_r_vec[j]));
|
|
|
- auto loop_mat = glm::inverse(proj_mat) // camera in chess board
|
|
|
- * cam_mat; // chess board in camera
|
|
|
- for (auto i = 0; i < corner_num; ++i) {
|
|
|
- auto p_gold = to_vec3(obj_ps[i]);
|
|
|
- auto p_pred = transform_p(loop_mat, p_gold);
|
|
|
- auto err_3d = glm::distance(p_gold, p_pred);
|
|
|
- err_mat.at<cv::Vec2f>(j, i)[0] = err_3d;
|
|
|
- }
|
|
|
-
|
|
|
- auto proj_r = to_cv_rodigues(glm::mat3(proj_mat));
|
|
|
- auto proj_t = to_cv_mat(to_translation(proj_mat));
|
|
|
- auto c_pred = std::vector<cv::Point2f>();
|
|
|
- cv::projectPoints(obj_ps_err, proj_r, proj_t,
|
|
|
- intrinsic_cv_mat, dist_coeffs_mat,
|
|
|
- c_pred);
|
|
|
- assert(c_pred.size() == corner_num);
|
|
|
- for (auto i = 0; i < corner_num; ++i) {
|
|
|
- auto c_gold = to_vec2(item.corner[i]);
|
|
|
- auto err_2d = glm::distance(c_gold, to_vec2(c_pred[i]));
|
|
|
- err_mat.at<cv::Vec2f>(j, i)[1] = err_2d;
|
|
|
+ // find minimal error using trichotomy algorithm
|
|
|
+ auto offset_l = -128000; // -128ms
|
|
|
+ auto offset_r = 0; // 0ms
|
|
|
+ static constexpr auto eps = 5000; // 5ms
|
|
|
+ auto eval_func = [this](int offset) {
|
|
|
+ ts_offset = offset;
|
|
|
+ auto ret_mat = calib_hand_eye();
|
|
|
+ return evaluate_hand_eye(ret_mat);
|
|
|
+ };
|
|
|
+ auto better_op = [](const cv::Scalar &lhs, // is lhs better than rhs ?
|
|
|
+ const cv::Scalar &rhs) {
|
|
|
+ return lhs[1] < rhs[1]; // use 2d error
|
|
|
+ };
|
|
|
+ for (; offset_r - offset_l > eps;) {
|
|
|
+ auto l_sec = (2 * offset_l + offset_r) / 3;
|
|
|
+ auto r_sec = (offset_l + 2 * offset_r) / 3;
|
|
|
+ auto l_err = eval_func(l_sec);
|
|
|
+ auto r_err = eval_func(r_sec);
|
|
|
+ if (better_op(l_err, r_err)) {
|
|
|
+ offset_r = r_sec;
|
|
|
+ } else {
|
|
|
+ offset_l = l_sec;
|
|
|
}
|
|
|
}
|
|
|
+ result_ts_offset = (offset_l + offset_r) / 2;
|
|
|
+
|
|
|
+ ts_offset = result_ts_offset;
|
|
|
+ result_mat = calib_hand_eye();
|
|
|
+ SPDLOG_INFO("Estimated temporal offset is {}ms", result_ts_offset / 1000);
|
|
|
|
|
|
- auto err_val = cv::mean(err_mat);
|
|
|
+ auto err_val = evaluate_hand_eye(result_mat);
|
|
|
obj_reproj_err = err_val[0];
|
|
|
SPDLOG_INFO("Average 3D projection error is {:.2f}mm", obj_reproj_err);
|
|
|
img_reproj_err = err_val[1];
|
|
|
@@ -341,6 +370,7 @@ void camera_calibrator::impl::start() {
|
|
|
last_finish.store(nullptr);
|
|
|
img_pool.clear();
|
|
|
track_pool.clear();
|
|
|
+ img_ok_cnt = 0;
|
|
|
|
|
|
assert(tp == nullptr);
|
|
|
tp = std::make_unique<BS::thread_pool>(conf.num_threads);
|