云台非正交时,坐标转换代码python
·
我简单测试了一下,计算结果正确。
import numpy as np
import math
class NonOrthogonalGimbal:
"""
非正交云台坐标转换类
支持两种非正交类型:
1. 方位轴与俯仰轴不垂直(存在安装偏差角)
2. 双轴非正交(两轴实际轴线不垂直)
"""
def __init__(self, alpha=0, beta=0, gimbal_type='azimuth_tilt'):
"""
初始化云台非正交参数
参数:
----------
alpha : float
非正交角(弧度),表示两轴实际夹角与90°的偏差
beta : float
第二非正交角(弧度),某些模型需要
gimbal_type : str
'azimuth_tilt' : 方位轴倾斜(常见于两轴云台)
'dual_axis' : 双轴非正交(更一般模型)
"""
self.alpha = alpha
self.beta = beta
self.type = gimbal_type
def angles_to_direction(self, azimuth, elevation):
"""
将云台角度(非正交坐标系)转换为笛卡尔方向向量
参数:
----------
azimuth : float
方位角(弧度),绕Z轴
elevation : float
俯仰角(弧度),绕X或Y轴(取决于云台结构)
返回:
----------
numpy.ndarray
归一化方向向量 [x, y, z] 在东北天坐标系下
"""
if self.type == 'azimuth_tilt':
# 模型1: 俯仰轴相对于方位轴有非正交偏差
# 实际俯仰轴方向 = 理想俯仰轴 + 绕方位轴方向的微小旋转
# 理想坐标系下的初始方向(假设俯仰轴为Y轴,方位轴为Z轴)
# 先计算理想方向(正交情况)
cos_el = math.cos(elevation)
x_ideal = cos_el * math.cos(azimuth)
y_ideal = cos_el * math.sin(azimuth)
z_ideal = math.sin(elevation)
# 应用非正交修正:俯仰轴实际绕X轴有alpha角偏差
# 实际方向 = R_x(alpha) * 理想方向
R_nonorth = np.array([
[1, 0, 0],
[0, math.cos(self.alpha), -math.sin(self.alpha)],
[0, math.sin(self.alpha), math.cos(self.alpha)]
])
dir_ideal = np.array([x_ideal, y_ideal, z_ideal])
dir_actual = R_nonorth @ dir_ideal
elif self.type == 'dual_axis':
# 模型2: 双轴非正交,使用更一般的旋转矩阵
# 方位轴绕Z轴,俯仰轴实际绕轴 u = [sin(beta), cos(beta), 0]
# 先绕俯仰轴旋转,再绕方位轴旋转
# 俯仰轴单位向量(存在非正交)
tilt_axis = np.array([math.sin(self.beta), math.cos(self.beta), 0])
tilt_axis = tilt_axis / np.linalg.norm(tilt_axis)
# 绕任意轴旋转的罗德里格斯公式
def rodrigues_rot(v, k, theta):
"""绕单位向量k旋转theta角"""
cos_t = math.cos(theta)
sin_t = math.sin(theta)
return v * cos_t + np.cross(k, v) * sin_t + k * np.dot(k, v) * (1 - cos_t)
# 初始方向(沿X轴)
init_dir = np.array([1, 0, 0])
# 先绕俯仰轴(非正交)旋转elevation角
dir_tilted = rodrigues_rot(init_dir, tilt_axis, elevation)
# 再绕Z轴旋转azimuth角
Rz = np.array([
[math.cos(azimuth), -math.sin(azimuth), 0],
[math.sin(azimuth), math.cos(azimuth), 0],
[0, 0, 1]
])
dir_actual = Rz @ dir_tilted
else:
raise ValueError(f"Unknown gimbal type: {self.type}")
# 归一化
dir_actual = dir_actual / np.linalg.norm(dir_actual)
return dir_actual
def direction_to_angles(self, direction, initial_guess=None):
"""
将笛卡尔方向向量转换为云台角度(非正交坐标系)
使用数值优化方法求解逆运动学
参数:
----------
direction : numpy.ndarray
目标方向向量 [x, y, z](归一化或非归一化)
initial_guess : tuple or None
初始猜测角度 (azimuth, elevation),默认None则自动计算
返回:
----------
tuple
(azimuth, elevation) 云台所需角度(弧度)
"""
# 归一化输入向量
direction = np.array(direction)
direction = direction / np.linalg.norm(direction)
# 如果未提供初始猜测,使用理想正交解作为起点
if initial_guess is None:
# 理想情况下的角度
ideal_az = math.atan2(direction[1], direction[0])
ideal_el = math.asin(direction[2])
initial_guess = (ideal_az, ideal_el)
# 使用scipy优化(如果可用),否则使用简单梯度下降
try:
from scipy.optimize import minimize
def error_func(angles):
az, el = angles
dir_pred = self.angles_to_direction(az, el)
# 计算角度误差(点积反余弦)
cos_theta = np.clip(np.dot(dir_pred, direction), -1, 1)
return np.arccos(cos_theta) ** 2
# 优化
result = minimize(error_func, initial_guess,
method='L-BFGS-B',
bounds=[(-np.pi, np.pi), (-np.pi/2, np.pi/2)])
if result.success:
return result.x[0], result.x[1]
else:
# 优化失败,返回最接近的离散搜索解
return self._grid_search_angles(direction)
except ImportError:
# 没有scipy时使用简单网格搜索
return self._grid_search_angles(direction)
def _grid_search_angles(self, direction, resolution=100):
"""网格搜索方法(备用)"""
best_error = float('inf')
best_angles = (0, 0)
for i in range(resolution):
az = -np.pi + 2 * np.pi * i / resolution
for j in range(resolution//2 + 1):
el = -np.pi/2 + np.pi * j / resolution
dir_pred = self.angles_to_direction(az, el)
cos_theta = np.clip(np.dot(dir_pred, direction), -1, 1)
error = np.arccos(cos_theta)
if error < best_error:
best_error = error
best_angles = (az, el)
if error < 1e-6: # 找到精确解
return best_angles
return best_angles
def transform_points(self, points, azimuth, elevation):
"""
将点云从云台坐标系转换到世界坐标系
参数:
----------
points : numpy.ndarray
Nx3的点云数组
azimuth, elevation : float
云台当前角度
返回:
----------
numpy.ndarray
转换后的点云
"""
# 获取方向向量(从云台到目标)
direction = self.angles_to_direction(azimuth, elevation)
# 构造旋转矩阵:使云台Z轴指向direction
# 简化版本:假设云台坐标系Z轴为视线方向
z_axis = direction
# 选择一个临时参考向量
tmp = np.array([0, 0, 1]) if abs(z_axis[2]) < 0.999 else np.array([1, 0, 0])
x_axis = np.cross(tmp, z_axis)
x_axis = x_axis / np.linalg.norm(x_axis)
y_axis = np.cross(z_axis, x_axis)
R = np.array([x_axis, y_axis, z_axis]).T
# 应用旋转
points_transformed = points @ R.T
return points_transformed
def example1():
# 示例1: 基本使用 - 方位轴倾斜模型
print("=== 示例1: 方位轴倾斜模型 ===")
# 转换到实际指向方向
direction = gimbal1.angles_to_direction(az, el)
print(f"云台角度: az={np.degrees(az):.1f}°, el={np.degrees(el):.1f}°")
print(f"实际指向方向: {direction}")
# 反向转换:给定方向,求云台角度
target_dir = np.array([0.5, 0.5, 0.7071])
az_recovered, el_recovered = gimbal1.direction_to_angles(target_dir)
print(f"目标方向 {target_dir} 对应的云台角度: "
f"az={np.degrees(az_recovered):.1f}°, el={np.degrees(el_recovered):.1f}°")
def example2():
print("\n=== 示例2: 双轴非正交模型 ===")
# 双轴非正交,beta=0.1弧度
gimbal2 = NonOrthogonalGimbal(alpha=0, beta=0.1, gimbal_type='dual_axis')
direction2 = gimbal2.angles_to_direction(az, el)
print(f"双轴非正交模型指向: {direction2}")
# 对比理想正交情况
ideal_dir = np.array([
math.cos(el) * math.cos(az),
math.cos(el) * math.sin(az),
math.sin(el)
])
print(f"理想正交指向: {ideal_dir}")
print(f"角度偏差: {np.degrees(np.arccos(np.dot(direction2, ideal_dir))):.3f}°")
def example3():
print("\n=== 示例3: 点云转换 ===")
# 生成一个简单的点云(立方体8个顶点)
points = np.array([
[0,0,0], [1,0,0], [0,1,0], [0,0,1],
[1,1,0], [1,0,1], [0,1,1], [1,1,1]
])
# 云台指向 (az=30°, el=20°)
transformed = gimbal1.transform_points(points, np.radians(30), np.radians(20))
print(f"原始点云形状: {points.shape}")
print(f"转换后点云前3个点:\n{transformed[:3]}")
# ========== 使用示例 ==========
if __name__ == "__main__":
# 假设俯仰轴相对于正交位置有3度的偏差
alpha = 3
# 给定云台角度
az = 45
el = 30
print(np.radians(2.86))
gimbal1 = NonOrthogonalGimbal(alpha=np.radians(alpha), gimbal_type='azimuth_tilt')
az = np.radians(az) # 45度方位角
el = np.radians(el) # 30度俯仰角
example1()
example2()
example3()
更多推荐



所有评论(0)