You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过OpenCV获取检测到的ArUco标记的世界坐标系位置?

计算ArUco标记的世界坐标系位姿并实现导航

核心逻辑

要得到ArUco标记在odom(世界)坐标系下的位姿,需要完成三次坐标变换的叠加:

  1. 标记 → 相机(ArUco输出的rvec/tvec)
  2. 相机 → base_footprint(机器人底盘坐标系,需提前通过URDF或静态TF定义)
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:17:29