如何通过OpenCV获取检测到的ArUco标记的世界坐标系位置?
计算ArUco标记的世界坐标系位姿并实现导航
核心逻辑
要得到ArUco标记在odom(世界)坐标系下的位姿,需要完成三次坐标变换的叠加:
- 标记 → 相机(ArUco输出的
rvec/tvec) - 相机 →
base_footprint(机器人底盘坐标系,需提前通过URDF或静态TF定义) base_footprint→odom(你已通过lookupTransform获取)
把这三个变换依次组合,就能得到标记在odom下的最终位姿,再用这个位姿生成MoveBaseGoal实现导航。
具体实现步骤
1. 确保相机与底盘的TF关系正常
首先必须在机器人的TF树中建立base_footprint到相机连杆(比如camera_link)的变换,这一步通常有两种方式:
- 在机器人URDF模型中定义相机的安装位置(推荐,适合固定安装的相机)
- 用静态TF发布器临时发布(适合调试):
rosrun tf2_ros static_transform_publisher 0.1 0 0.5 0 0 0 base_footprint camera_link
参数顺序为:x/y/z平移量、roll/pitch/yaw旋转角、父坐标系、子坐标系,务必根据你的相机实际安装位置调整。
2. 转换ArUco输出的位姿为TF兼容格式
ArUco的estimatePoseSingleMarkers返回的是多标记位姿数组,单标记需取第一个元素;同时rvec是旋转向量,要转换成TF常用的四元数:
import cv2 import numpy as np from scipy.spatial.transform import Rotation as R # 假设已通过ArUco检测得到corners、rvec、tvec if len(corners) > 0: # 提取单标记的旋转向量和平移向量 rvec_single = rvec[0][0] tvec_single = tvec[0][0] # 旋转向量转旋转矩阵,再转四元数 rot_mat, _ = cv2.Rodrigues(rvec_single) marker_cam_quat = R.from_matrix(rot_mat).as_quat() # 格式:[x,y,z,w] marker_cam_trans = tvec_single.tolist() # 格式:[x,y,z]
3. 获取相机相对于底盘的位姿
用TF listener获取camera_link到base_footprint的变换:
import tf2_ros import rospy from geometry_msgs.msg import TransformStamped tf_buffer = tf2_ros.Buffer() listener = tf2_ros.TransformListener(tf_buffer) try: # 获取相机相对于base_footprint的变换 cam_base_tf = tf_buffer.lookup_transform( 'base_footprint', 'camera_link', rospy.Time(0), rospy.Duration(1.0) ) cam_base_trans = [ cam_base_tf.transform.translation.x, cam_base_tf.transform.translation.y, cam_base_tf.transform.translation.z ] cam_base_quat = [ cam_base_tf.transform.rotation.x, cam_base_tf.transform.rotation.y, cam_base_tf.transform.rotation.z, cam_base_tf.transform.rotation.w ] except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException): rospy.logerr("无法获取相机到底盘的TF变换") continue
4. 计算标记在底盘坐标系的位姿
利用TF的变换组合功能,将标记相对于相机的位姿转换到base_footprint坐标系:
# 构造标记相对于相机的TF marker_cam_tf = TransformStamped() marker_cam_tf.header.frame_id = 'camera_link' marker_cam_tf.child_frame_id = 'aruco_marker_temp' marker_cam_tf.transform.translation.x = marker_cam_trans[0] marker_cam_tf.transform.translation.y = marker_cam_trans[1] marker_cam_tf.transform.translation.z = marker_cam_trans[2] marker_cam_tf.transform.rotation.x = marker_cam_quat[0] marker_cam_tf.transform.rotation.y = marker_cam_quat[1] marker_cam_tf.transform.rotation.z = marker_cam_quat[2] marker_cam_tf.transform.rotation.w = marker_cam_quat[3] # 转换到base_footprint坐标系 marker_base_tf = tf_buffer.transform(marker_cam_tf, 'base_footprint', rospy.Duration(1.0))
5. 转换标记位姿到odom坐标系
结合你已获取的base_footprint相对于odom的位姿,将标记转换到世界坐标系:
from geometry_msgs.msg import PoseStamped # 你原代码获取的base_footprint在odom下的位姿:trans是平移数组,rot是四元数数组 (trans, rot) = listener.lookupTransform('odom', 'base_footprint', rospy.Time(0)) # 构造标记在base_footprint下的PoseStamped marker_base_pose = PoseStamped() marker_base_pose.header.frame_id = 'base_footprint' marker_base_pose.pose.position.x = marker_base_tf.transform.translation.x marker_base_pose.pose.position.y = marker_base_tf.transform.translation.y marker_base_pose.pose.position.z = marker_base_tf.transform.translation.z marker_base_pose.pose.orientation = marker_base_tf.transform.rotation # 转换到odom坐标系 marker_odom_pose = tf_buffer.transform(marker_base_pose, 'odom', rospy.Duration(1.0))
6. 生成MoveBaseGoal实现导航
将标记的世界位姿包装成MoveBaseGoal,发送给move_base节点:
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal import actionlib # 初始化move_base客户端 move_base_client = actionlib.SimpleActionClient('move_base', MoveBaseAction) move_base_client.wait_for_server() # 构造导航目标(平面导航可将Z坐标设为0) goal = MoveBaseGoal() goal.target_pose.header.frame_id = 'odom' goal.target_pose.header.stamp = rospy.Time.now() goal.target_pose.pose = marker_odom_pose.pose goal.target_pose.pose.position.z = 0.0 # 发送目标并等待结果 move_base_client.send_goal(goal) success = move_base_client.wait_for_result() if success: rospy.loginfo("已到达ArUco标记位置!") else: rospy.logerr("导航到标记失败")
新手必看注意事项
- 检查TF树完整性:用
rosrun tf2_tools view_frames.py生成TF树可视化图,确保odom→base_footprint→camera_link的链路连通 - ArUco检测鲁棒性:每次处理前判断
len(corners) > 0,避免无标记时程序崩溃 - 平面导航适配:MoveBase默认处理2D导航,务必将目标位姿的Z坐标设为0,旋转只保留yaw角(可通过
tf.transformations.euler_from_quaternion提取) - 标定精度验证:如果位姿误差大,重新检查相机标定的内参
cam_mat和畸变系数dist_coef是否正确
内容的提问来源于stack exchange,提问作者chvister
相关产品推荐
相关产品推荐

