Xarm-DataCollection/ufactory_lerobot/devices/umi/vive_tracker/transformations.py
Vinman d959d82dbe refactor(teleop): 重命名 set_ctrl_status 为 set_teleop_enabled
启用时支持传入机械臂观测数据以同步初始位姿映射,
重构 Pika/UMI 遥操作内部状态管理。
2026-06-12 18:37:53 +08:00

257 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import numpy as np
class Transformations:
@staticmethod
def quaternion_to_rotation_matrix(q):
"""
将四元数转换为旋转矩阵
注: 四元素顺序为xyzw
"""
norm = np.linalg.norm(q)
if norm < 1e-6:
raise ValueError('零四元数无法归一化')
x, y, z, w = q / norm # 归一化
xx, yy, zz = x * x, y * y, z * z
xy, xz, yz = x * y, x * z, y * z
wx, wy, wz = w * x, w * y, w * z
R = np.array([
[1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy)],
[ 2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx)],
[ 2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy)]
])
return R
@staticmethod
def rotation_matrix_to_quaternion(R):
"""
将3x3变换矩阵转换四元数
注: 四元素顺序为xyzw
"""
# 提取旋转矩阵部分
rot_matrix = R[:3, :3]
# 计算四元数
trace = rot_matrix[0, 0] + rot_matrix[1, 1] + rot_matrix[2, 2]
if trace > 0:
s = 0.5 / np.sqrt(trace + 1.0)
qw = 0.25 / s
qx = (rot_matrix[2, 1] - rot_matrix[1, 2]) * s
qy = (rot_matrix[0, 2] - rot_matrix[2, 0]) * s
qz = (rot_matrix[1, 0] - rot_matrix[0, 1]) * s
elif rot_matrix[0, 0] > rot_matrix[1, 1] and rot_matrix[0, 0] > rot_matrix[2, 2]:
s = 2.0 * np.sqrt(1.0 + rot_matrix[0, 0] - rot_matrix[1, 1] - rot_matrix[2, 2])
qw = (rot_matrix[2, 1] - rot_matrix[1, 2]) / s
qx = 0.25 * s
qy = (rot_matrix[0, 1] + rot_matrix[1, 0]) / s
qz = (rot_matrix[0, 2] + rot_matrix[2, 0]) / s
elif rot_matrix[1, 1] > rot_matrix[2, 2]:
s = 2.0 * np.sqrt(1.0 + rot_matrix[1, 1] - rot_matrix[0, 0] - rot_matrix[2, 2])
qw = (rot_matrix[0, 2] - rot_matrix[2, 0]) / s
qx = (rot_matrix[0, 1] + rot_matrix[1, 0]) / s
qy = 0.25 * s
qz = (rot_matrix[1, 2] + rot_matrix[2, 1]) / s
else:
s = 2.0 * np.sqrt(1.0 + rot_matrix[2, 2] - rot_matrix[0, 0] - rot_matrix[1, 1])
qw = (rot_matrix[1, 0] - rot_matrix[0, 1]) / s
qx = (rot_matrix[0, 2] + rot_matrix[2, 0]) / s
qy = (rot_matrix[1, 2] + rot_matrix[2, 1]) / s
qz = 0.25 * s
return [qx, qy, qz, qw]
@staticmethod
def rpy_to_rotation_matrix(roll, pitch, yaw):
"""RPY角到旋转矩阵的转换"""
cr, sr = np.cos(roll), np.sin(roll)
cp, sp = np.cos(pitch), np.sin(pitch)
cy, sy = np.cos(yaw), np.sin(yaw)
R = np.array([
[cp*cy, -cr*sy + sr*sp*cy, sr*sy + cr*sp*cy],
[cp*sy, cr*cy + sr*sp*sy, -sr*cy + cr*sp*sy],
[ -sp, sr*cp, cr*cp],
])
return R
@staticmethod
def rotation_matrix_to_rpy(R, yaw_zero=True):
"""
旋转矩阵到RPY角的转换
yaw_zero: 万向节锁情况下, True就把yaw置0, False就把roll置0
返回: roll, pitch, yaw
"""
epsilon = 1e-6
if abs(R[2, 0]) > 1 - epsilon: # 万向节锁(pitch=±90°)
pitch = np.arcsin(-R[2, 0])
roll_yaw = np.arctan2(-R[0, 1], R[1, 1])
if yaw_zero:
# 保留roll, 把yaw置0
roll, yaw = roll_yaw, 0
else:
# 保留yaw, 把roll置0
roll, yaw = 0, roll_yaw
else:
roll = np.arctan2(R[2, 1], R[2, 2])
pitch = np.arcsin(-R[2, 0])
yaw = np.arctan2(R[1, 0], R[0, 0])
return roll, pitch, yaw
@staticmethod
def rxryrz_to_rotation_matrix(axis_angle):
"""
将轴角向量 (rx, ry, rz) 转换为 3x3 旋转矩阵。
输入: np.array([rx, ry, rz])
- 方向: 旋转轴
- 模长: 旋转角度 (弧度)
"""
theta = np.linalg.norm(axis_angle)
# 如果角度接近0返回单位矩阵
if theta < 1e-8:
return np.eye(3)
# 归一化旋转轴
axis = axis_angle / theta
x, y, z = axis
c = np.cos(theta)
s = np.sin(theta)
t = 1 - c
# 罗德里格斯旋转公式
# R = I + sin(theta)*K + (1-cos(theta))*K^2
# 展开为矩阵形式:
R = np.array([
[t*x*x + c, t*x*y - s*z, t*x*z + s*y],
[t*x*y + s*z, t*y*y + c, t*y*z - s*x],
[t*x*z - s*y, t*y*z + s*x, t*z*z + c]
])
return R
@staticmethod
def rotation_matrix_to_rxryrz(R):
"""
旋转矩阵到轴角的转换 (rx, ry, rz = aixs * angle)
返回: rx, ry, rz
"""
R = np.asarray(R)
if R.shape[-2:] != (3, 3):
raise ValueError("Input must be (..., 3, 3)")
# 计算旋转角度 theta
trace = np.trace(R)
cos_theta = (trace - 1) / 2.0
cos_theta = np.clip(cos_theta, -1.0, 1.0) # 防止数值误差导致 arccos 越界
theta = np.arccos(cos_theta)
eps = 1e-8
# 情况 1: 无旋转 (theta ≈ 0)
if theta < eps:
axis = np.array([1.0, 0.0, 0.0])
return axis * 0.0
# 情况 2: 旋转角度接近 pi (180 度)
if np.pi - theta < eps:
# 此时 sin(theta) ≈ 0不能用反对称公式
# 从 R 对角线提取轴R = I + 2 * (uu^T - I) => uu^T = (R + I)/2
# 所以 u_i^2 = (R_ii + 1)/2
diag = np.diag(R)
axis = np.sqrt(np.maximum(diag + 1, 0)) # 取非负根
# 确定符号:利用非对角元素,例如 R[0,1] = 2*u0*u1
if axis[0] > eps:
if R[0, 1] < 0:
axis[1] *= -1
if R[0, 2] < 0:
axis[2] *= -1
elif axis[1] > eps:
if R[1, 2] < 0:
axis[2] *= -1
# 注意:可能存在符号歧义,但旋转效果相同
axis = axis / np.linalg.norm(axis)
return axis * theta
# 情况 3: 一般情况 (0 < theta < pi)
sin_theta = np.sin(theta)
axis = np.array([
R[2, 1] - R[1, 2],
R[0, 2] - R[2, 0],
R[1, 0] - R[0, 1]
]) / (2 * sin_theta)
axis = axis / np.linalg.norm(axis) # 确保单位长度(数值误差可能破坏)
return axis * theta
@classmethod
def xyzq_to_rotation_matrix(cls, x, y, z, q):
T = np.eye(4)
T[:3, :3] = cls.quaternion_to_rotation_matrix(q)
T[:3, 3] = [x, y, z]
return T
@classmethod
def xyzrpy_to_rotation_matrix(cls, x, y, z, roll, pitch, yaw):
"""构造4x4齐次变换矩阵"""
T = np.eye(4)
T[:3, :3] = cls.rpy_to_rotation_matrix(roll, pitch, yaw)
T[:3, 3] = [x, y, z]
return T
@classmethod
def xyzrxryrz_to_rotation_matrix(cls, x, y, z, rx, ry, rz):
"""构造4x4齐次变换矩阵"""
T = np.eye(4)
T[:3, :3] = cls.rxryrz_to_rotation_matrix([rx, ry, rz])
T[:3, 3] = [x, y, z]
return T
@classmethod
def rotation_matrix_to_xyzq(cls, rotation_matrix):
"""从4x4齐次变换矩阵到xyzq的转换"""
x, y, z = rotation_matrix[0, 3], rotation_matrix[1, 3], rotation_matrix[2, 3]
q = cls.rotation_matrix_to_quaternion(rotation_matrix[:3, :3])
return [x, y, z, q]
@classmethod
def rotation_matrix_to_xyzrpy(cls, rotation_matrix):
"""从4x4齐次变换矩阵到xyzrpy的转换"""
x, y, z = rotation_matrix[0, 3], rotation_matrix[1, 3], rotation_matrix[2, 3]
roll, pitch, yaw = cls.rotation_matrix_to_rpy(rotation_matrix)
return [x, y, z, roll, pitch, yaw]
@classmethod
def rotation_matrix_to_xyzrxryrz(cls, rotation_matrix):
"""从4x4齐次变换矩阵到xyzrxryrz的转换"""
x, y, z = rotation_matrix[0, 3], rotation_matrix[1, 3], rotation_matrix[2, 3]
rx, ry, rz = cls.rotation_matrix_to_rxryrz(rotation_matrix[:3,:3])
return [x, y, z, rx, ry, rz]
@classmethod
def tracker_pose_to_robot_matrix(cls, x, y, z, q, tracker_to_robot_matrix):
# Tracker位置对应的变换矩阵
tracker_matrix = cls.xyzq_to_rotation_matrix(x, y, z, q)
# Tracker位置转换到机械臂坐标系后对应的变换矩阵
robot_matrix = np.dot(tracker_matrix, tracker_to_robot_matrix)
return robot_matrix
@classmethod
def tracker_robot_matrix_to_robot_pose(cls, begin_tracker_robot_matrix, end_tracker_robot_matrix, robot_base_matrix, is_axis_angle=False):
# 机械臂目标位置对应的变换矩阵
# 机械臂目标 = 机械臂初始位置 + (当前手姿 - 初始手姿)
delta_matrix = np.dot(np.linalg.inv(begin_tracker_robot_matrix), end_tracker_robot_matrix)
robot_martix = np.dot(robot_base_matrix, delta_matrix)
if is_axis_angle:
return cls.rotation_matrix_to_xyzrxryrz(robot_martix)
else:
return cls.rotation_matrix_to_xyzrpy(robot_martix)