diff --git a/apriltag_ros/CMakeLists.txt b/apriltag_ros/CMakeLists.txt
index fd2f019d..048bb995 100644
--- a/apriltag_ros/CMakeLists.txt
+++ b/apriltag_ros/CMakeLists.txt
@@ -107,6 +107,10 @@ target_link_libraries(${PROJECT_NAME}_continuous_detector ${PROJECT_NAME}_common
add_library(${PROJECT_NAME}_single_image_detector src/single_image_detector.cpp)
target_link_libraries(${PROJECT_NAME}_single_image_detector ${PROJECT_NAME}_common ${catkin_LIBRARIES})
+add_executable(${PROJECT_NAME}_calibration_bundle src/${PROJECT_NAME}_calibration_bundle.cpp)
+add_dependencies(${PROJECT_NAME}_calibration_bundle ${PROJECT_NAME}_generate_messages_cpp)
+target_link_libraries(${PROJECT_NAME}_calibration_bundle ${catkin_LIBRARIES})
+
add_executable(${PROJECT_NAME}_continuous_node src/${PROJECT_NAME}_continuous_node.cpp)
add_dependencies(${PROJECT_NAME}_continuous_node ${PROJECT_NAME}_generate_messages_cpp)
target_link_libraries(${PROJECT_NAME}_continuous_node ${PROJECT_NAME}_continuous_detector ${catkin_LIBRARIES})
@@ -120,6 +124,8 @@ add_dependencies(${PROJECT_NAME}_single_image_client_node ${PROJECT_NAME}_genera
target_link_libraries(${PROJECT_NAME}_single_image_client_node ${PROJECT_NAME}_common ${catkin_LIBRARIES})
+
+
#############
## Install ##
#############
@@ -143,6 +149,7 @@ install(TARGETS
${PROJECT_NAME}_single_image_client_node
${PROJECT_NAME}_single_image_detector
${PROJECT_NAME}_single_image_server_node
+ ${PROJECT_NAME}_calibration_bundle
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
diff --git a/apriltag_ros/launch/calibration_bundle.launch b/apriltag_ros/launch/calibration_bundle.launch
new file mode 100644
index 00000000..94bd8802
--- /dev/null
+++ b/apriltag_ros/launch/calibration_bundle.launch
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apriltag_ros/src/apriltag_ros_calibration_bundle.cpp b/apriltag_ros/src/apriltag_ros_calibration_bundle.cpp
new file mode 100644
index 00000000..845df777
--- /dev/null
+++ b/apriltag_ros/src/apriltag_ros_calibration_bundle.cpp
@@ -0,0 +1,204 @@
+#include
+#include
+#include
+#include
+#include
+
+using namespace apriltag_ros;
+#define MASTER_ID 0
+
+int g_frame_cnt = 0;
+int input_frame_count = 0;
+
+std::vector detection_array_deque;
+
+Eigen::Matrix3d quat2Rotmap(const geometry_msgs::Quaternion &ros_q)
+{
+ Eigen::Quaterniond q(ros_q.w, ros_q.x, ros_q.y, ros_q.z);
+ return q.matrix();
+}
+
+Eigen::Matrix4d createTransform(const geometry_msgs::PoseWithCovarianceStamped &pc)
+{
+ Eigen::Affine3d transform = Eigen::Affine3d::Identity();
+ Eigen::Vector3d t(pc.pose.pose.position.x, pc.pose.pose.position.y, pc.pose.pose.position.z);
+ Eigen::Quaterniond q(pc.pose.pose.orientation.w,
+ pc.pose.pose.orientation.x,
+ pc.pose.pose.orientation.y,
+ pc.pose.pose.orientation.z);
+ transform.rotate(q);
+ transform.pretranslate(t);
+ return transform.matrix();
+}
+
+void tagDetectionCallback(const apriltag_ros::AprilTagDetectionArrayConstPtr &detection_array)
+{
+ // Use 500 frame detections to generate a calibration result
+ if (g_frame_cnt++ > input_frame_count)
+ {
+ double master_size;
+ std::map> rel_mats; // 某帧某个tag
+ for (const AprilTagDetectionArray &detections : detection_array_deque)
+ {
+ auto it = std::find_if(detections.detections.begin(), detections.detections.end(), [&](const AprilTagDetection &d) {
+ return d.id.front() == MASTER_ID;
+ });
+ // Master not detected in this detection, so this particular
+ // detection is useless
+ if (it == detections.detections.end())
+ continue;
+
+ // Get the master tag's rigid body transform to the camera frame
+ Eigen::Matrix4d T_cm = createTransform(it->pose);
+ master_size = it->size.front();
+
+ // Get the rigid body transform of every other tag to the camera frame
+ std::vector mats;
+ for (const auto &detection : detections.detections)
+ {
+ // Skip the master, but get its size first
+ if (detection.id.front() == MASTER_ID)
+ continue;
+ //We already have the rigid body transform from the master tag to the camera frame (T_cm)
+ int other_id = detection.id.front();
+
+ //Get this tag's rigid body transform to the camera frame
+ Eigen::Matrix4d other_T_cm = createTransform(detection.pose);
+
+ // Deduce this tag's rigid body transform to the master tag's frame
+ auto tt = T_cm.inverse();
+ Eigen::Matrix4d T_m = T_cm.inverse() * other_T_cm;
+
+ // Save the relative position and orientation of this tag to the master tag
+ rel_mats[detection.id.front()].push_back(T_m);
+ }
+ }
+
+ // Compute the mean position as the initial value for the minimization problem
+ auto meanTranspose = [&](const std::vector &mats) -> Eigen::Vector3d {
+ double mean_x(0), mean_y(0), mean_z(0);
+ for (const auto &mat : mats)
+ {
+ Eigen::Affine3d a3d(mat);
+ mean_x += a3d.translation().x();
+ mean_y += a3d.translation().y();
+ mean_z += a3d.translation().z();
+ }
+ mean_x /= mats.size();
+ mean_y /= mats.size();
+ mean_z /= mats.size();
+ return Eigen::Vector3d(mean_x, mean_y, mean_z);
+ };
+
+ auto quatmult = [&](const Eigen::Quaterniond &a, const Eigen::Quaterniond &b) -> Eigen::Quaterniond {
+ double w = a.w() * b.w() - a.x() * b.x() - a.y() * b.y() - a.z() * b.z();
+ double x = a.w() * b.x() + a.x() * b.w() - a.y() * b.z() + a.z() * b.y();
+ double y = a.w() * b.y() + a.y() * b.w() + a.x() * b.z() - a.z() * b.x();
+ double z = a.w() * b.z() - a.x() * b.y() + a.y() * b.x() + a.z() * b.w();
+ return Eigen::Quaterniond(w, x, y, z);
+ };
+
+ auto meanQuaternions = [&](const std::vector &mats) -> Eigen::Quaterniond {
+ double mean_x(0), mean_y(0), mean_z(0), mean_w(0);
+
+ for (int i = 0; i < mats.size(); ++i)
+ {
+ Eigen::Affine3d a4d(mats[i]);
+ Eigen::Quaterniond q1(Eigen::Affine3d(mats[i]).rotation());
+ for (int j = 0; j < mats.size(); ++j)
+ {
+ if (i == j)
+ continue;
+
+ Eigen::Quaterniond q2(Eigen::Affine3d(mats[j]).rotation());
+ auto q_error = quatmult(q1.inverse(), q2);
+ double q_error_w = std::min(1.0, std::max(q_error.w(), -1.0));
+ if (2 * std::acos(q_error_w) >= M_PI / 2.0)
+ printf("Quaternion pair q_%d and q_%d are more than 90 degrees apart!", i, j);
+ }
+ }
+ };
+
+ // Compute the geometric median
+ std::map rel_mats_median;
+ for (const auto &ms : rel_mats)
+ {
+ // avereage transpose
+ auto p_0 = meanTranspose(ms.second);
+ // TODO In matlab code, it applied a optimization search algorithm to search a result
+ // In this version, use mean result only
+ rel_mats_median[ms.first] = Eigen::Affine3d::Identity();
+ rel_mats_median[ms.first].pretranslate(p_0);
+ }
+
+ // Compute the quaternion median
+ for (const auto &ms : rel_mats)
+ {
+ auto q_0 = meanQuaternions(ms.second);
+ Eigen::MatrixXd Q(4, ms.second.size());
+ for (size_t i = 0; i < ms.second.size(); ++i)
+ {
+ Eigen::Affine3d a3d(ms.second[i]);
+ Eigen::Quaterniond qq(a3d.rotation());
+ double w = qq.w();
+ double x = qq.x();
+ double y = qq.y();
+ double z = qq.z();
+ Q.col(i) = Eigen::Vector4d(w, x, y, z);
+ }
+
+ Eigen::SelfAdjointEigenSolver eigensolver(Q * Q.transpose());
+ Eigen::Vector4d D = eigensolver.eigenvalues();
+ Eigen::Matrix4d V = eigensolver.eigenvectors();
+ double max_value(-9999999);
+ int imax;
+ for (int j = 0; j < 4; j++)
+ {
+ if (D[j] > max_value)
+ {
+ imax = j;
+ max_value = D[j];
+ }
+ }
+ Eigen::Quaterniond q(V.col(imax)[0], V.col(imax)[1], V.col(imax)[2], V.col(imax)[3]);
+ if (q.w() < 0)
+ q.coeffs()[0] = -q.w();
+ rel_mats_median[ms.first].rotate(q);
+ }
+
+ for (const std::pair &trans : rel_mats_median)
+ {
+ std::cout << "{id: " << trans.first
+ << ", size: " << 0.1077
+ << ", x: " << trans.second.translation()[0]
+ << ", y: " << trans.second.translation()[1]
+ << ", z: " << trans.second.translation()[2];
+ Eigen::Quaterniond q(trans.second.rotation());
+ std::cout << ", qw: " << q.w()
+ << ", qx: " << q.x()
+ << ", qy: " << q.y()
+ << ", qz: " << q.z()
+ << "}"
+ << std::endl;
+ }
+
+ g_frame_cnt = 0;
+ detection_array_deque.clear();
+ }
+ else
+ {
+ detection_array_deque.emplace_back(*detection_array);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ ros::init(argc, argv, "calib_bundel");
+ ros::NodeHandle nh;
+ ros::NodeHandle private_nh("~");
+ private_nh.param("input_frame_count", input_frame_count, 100);
+
+ ros::Subscriber dection_sub = nh.subscribe("/tag_detections", 1, tagDetectionCallback);
+
+ ros::spin();
+}