当前位置: 首页 > news >正文

基于Web的交互式坐标系变换矩阵计算工具

基于Web的交互式坐标系变换矩阵计算工具

    • 一、什么是坐标系变换矩阵?
    • 二、为什么需要这个工具?
    • 三、效果
    • 四、功能介绍
      • 1、坐标系定义
      • 2、交互控制
      • 3、变换矩阵计算
    • 五、如何使用这个工具
    • 六、完整代码
    • 七、总结

一、什么是坐标系变换矩阵?

在三维空间中,我们经常需要描述物体之间的位置和方向关系。坐标系变换矩阵就是用来表示从一个坐标系到另一个坐标系转换关系的数学工具。这种4×4矩阵包含了两部分关键信息:

  • 旋转分量:表示坐标系之间的方向关系
  • 平移分量:表示坐标系之间的位置偏移

这种变换矩阵在计算机图形学机器人学计算机视觉增强现实等领域有广泛应用。例如,当我们需要将3D模型渲染到屏幕上时,必须计算模型坐标系到相机坐标系的变换矩阵。

二、为什么需要这个工具?

手动计算三维空间中的变换矩阵麻烦:

  1. 需要理解旋转矩阵、四元数、欧拉角等
  2. 不同领域使用不同的坐标系约定(如计算机图形学通常使用Y向上,而机器人学常用Z向上)
  3. 旋转顺序对结果有重大影响(Z-Y-X旋转与X-Y-Z旋转结果不同)

这个交互式工具通过可视化界面解决了这些问题:

  • 直观展示:三维空间中的坐标系和物体关系一目了然
  • 实时反馈:调整参数时立即看到效果
  • 自动计算:复杂的数学运算在后台自动完成
  • 多表示形式:同时提供矩阵和四元数两种表示法

本工具设计之初 用于计算相机与雷达联合标定的初始外参,所以坐标系由此定义

三、效果

请添加图片描述
请添加图片描述

四、功能介绍

1、坐标系定义

# 长方体顶点定义以匹配标准相机坐标系 (X右, Y下, Z前)
def create_box_vertices(length=0.5, width=0.3, height=0.1):vertices = np.array([# 后部 (z = -half_h)[-half_l, -half_w, -half_h],  # 后左下[ half_l, -half_w, -half_h],  # 后右下...])return vertices
  • 基坐标系:X前、Y左、Z上(常用于机器人学)
  • 相机坐标系:X右、Y下、Z前(计算机视觉标准)

2、交互控制

dash_app.layout = html.Div([dcc.RadioItems(id='operation-mode',options=[{'label': '移动/旋转整个空间', 'value': 'space'},{'label': '移动/旋转相机(长方体)', 'value': 'camera'}],value='camera'),# 位置输入dcc.Input(id='camera-x-input', type='number', value=0.0),dcc.Input(id='camera-y-input', type='number', value=0.0),dcc.Input(id='camera-z-input', type='number', value=0.0),# 旋转输入dcc.Input(id='camera-yaw-input', type='number', value=0.0),dcc.Input(id='camera-pitch-input', type='number', value=0.0),dcc.Input(id='camera-roll-input', type='number', value=0.0),# 计算按钮html.Button("计算变换矩阵", id="calculate-btn")
])
  1. 操作模式选择:移动相机或移动整个空间
  2. 位置控制:输入X/Y/Z坐标值
  3. 旋转控制:按Z-Y-X顺序输入欧拉角(Yaw、Pitch、Roll)
  4. 计算按钮:触发变换矩阵计算

3、变换矩阵计算

def calculate_transformation(n_clicks, x, y, z, roll, pitch, yaw):# 创建旋转矩阵(按Z-Y-X顺序)rotation = R.from_euler('zyx', [yaw, pitch, roll], degrees=True)rot_matrix = rotation.as_matrix()# 创建4x4变换矩阵transformation_matrix = np.eye(4)transformation_matrix[:3, :3] = rot_matrix  # 旋转部分transformation_matrix[:3, 3] = [x, y, z]    # 平移部分# 获取四元数表示quaternion = rotation.as_quat()  # [x, y, z, w]return formatted_matrix, formatted_quaternion

核心计算步骤:

  1. 将欧拉角转换为旋转矩阵(按Z-Y-X顺序)
  2. 组合旋转矩阵和平移向量为4×4变换矩阵
  3. 同时计算四元数表示(更紧凑的旋转表示)

五、如何使用这个工具

  1. 调整位置参数

    • 在X/Y/Z输入框中输入数值(单位:米)
    • 正值表示沿该轴正方向移动
  2. 调整方向参数

    • Yaw(偏航角):绕Z轴旋转(左右转头)
    • Pitch(俯仰角):绕Y轴旋转(抬头低头)
    • Roll(滚转角):绕X轴旋转(左右倾斜)
  3. 选择操作模式

    • “移动/旋转相机”:改变相机位置/方向
    • “移动/旋转空间”:保持相机不动,移动整个世界
  4. 计算结果

    • 点击"计算变换矩阵"按钮
    • 查看4×4变换矩阵和四元数表示

六、完整代码

  • app.py
import dash
from dash import dcc, html, Input, Output, State, no_update
import dash_daq as daq
import plotly.graph_objects as go
import numpy as np
from scipy.spatial.transform import Rotation as R
import re# 初始化应用
dash_app = dash.Dash(__name__, title='坐标系变换矩阵计算工具')
server = dash_app.server# 长方体顶点定义以匹配标准相机坐标系 (X右, Y下, Z前)
def create_box_vertices(length=0.5, width=0.3, height=0.1):half_l = length / 2half_w = width / 2half_h = height / 2# 定义标准相机坐标系顶点:# X: 右为正# Y: 下为正# Z: 前为正vertices = np.array([# 后部 (z = -half_h)[-half_l, -half_w, -half_h],  # 后左下 (x左, y上, z后)[ half_l, -half_w, -half_h],  # 后右下 (x右, y上, z后)[ half_l,  half_w, -half_h],  # 后右上 (x右, y下, z后)[-half_l,  half_w, -half_h],  # 后左上 (x左, y下, z后)# 前部 (z = half_h)[-half_l, -half_w,  half_h],  # 前左下 (x左, y上, z前)[ half_l, -half_w,  half_h],  # 前右下 (x右, y上, z前)[ half_l,  half_w,  half_h],  # 前右上 (x右, y下, z前)[-half_l,  half_w,  half_h]   # 前左上 (x左, y下, z前)])return vertices# 创建长方体网格
def create_box_mesh(vertices):faces = [[0, 1, 2, 3],  # 底部[4, 5, 6, 7],  # 顶部[0, 1, 5, 4],  # 前面[2, 3, 7, 6],  # 后面[1, 2, 6, 5],  # 右面[0, 3, 7, 4]   # 左面]x, y, z = vertices[:, 0], vertices[:, 1], vertices[:, 2]i, j, k = [], [], []for face in faces:i.extend([face[0], face[0], face[1], face[1], face[2], face[2], face[3]])j.extend([face[1], face[3], face[0], face[2], face[1], face[3], face[0]])k.extend([face[2], face[1], face[3], face[0], face[3], face[0], face[1]])return go.Mesh3d(x=x, y=y, z=z,i=i, j=j, k=k,color='#1f77b4',opacity=0.8,flatshading=True,name='相机(长方体)')# 创建坐标系
def create_coordinate_frame(origin=[0, 0, 0], scale=1.0, name="基坐标系"):# 坐标系: X前(红), Y左(绿), Z上(蓝)x_axis = go.Scatter3d(x=[origin[0], origin[0] + scale],y=[origin[1], origin[1]],z=[origin[2], origin[2]],mode='lines+text',line=dict(width=5, color='red'),text=['', 'X'],textposition="top center",name=f'{name} X轴')y_axis = go.Scatter3d(x=[origin[0], origin[0]],y=[origin[1], origin[1] + scale],z=[origin[2], origin[2]],mode='lines+text',line=dict(width=5, color='lime'),text=['', 'Y'],textposition="middle right",name=f'{name} Y轴')z_axis = go.Scatter3d(x=[origin[0], origin[0]],y=[origin[1], origin[1]],z=[origin[2], origin[2] + scale],mode='lines+text',line=dict(width=5, color='blue'),text=['', 'Z'],textposition="top center",name=f'{name} Z轴')return [x_axis, y_axis, z_axis]# 创建初始3D场景
def create_initial_scene():# 创建基坐标系axes = create_coordinate_frame(scale=1.5, name="基坐标系")# 创建长方体(相机) - 使用修复后的顶点定义box_vertices = create_box_vertices()box_mesh = create_box_mesh(box_vertices)# 添加相机位置标记camera_pos = go.Scatter3d(x=[0], y=[0], z=[0],mode='markers',marker=dict(size=5, color='gold'),name='相机中心')# 组合所有图形元素data = axes + [box_mesh, camera_pos]# 创建3D场景布局layout = go.Layout(scene=dict(xaxis=dict(title='X (前)', range=[-2, 2]),yaxis=dict(title='Y (左)', range=[-2, 2]),zaxis=dict(title='Z (上)', range=[-2, 2]),aspectmode='cube',camera=dict(eye=dict(x=1.5, y=1.5, z=1.5))),margin=dict(l=0, r=0, b=0, t=0),showlegend=True,legend=dict(x=0, y=0))return go.Figure(data=data, layout=layout)# 应用布局
dash_app.layout = html.Div([html.Div([html.H1("坐标系变换矩阵计算工具", className="header"),html.P("交互式计算相机(长方体)到基坐标系的变换矩阵", className="subtitle")], className="banner"),html.Div([dcc.Graph(id='3d-scene',figure=create_initial_scene(),style={'height': '80vh'},config={'scrollZoom': True})], className="graph-container"),    html.Div([html.Div([html.Label("操作模式:", className="control-label"),dcc.RadioItems(id='operation-mode',options=[{'label': '移动/旋转整个空间', 'value': 'space'},{'label': '移动/旋转相机(长方体)', 'value': 'camera'}],value='camera',className="radio-group"),html.Div([html.Div([html.Label("相机位置 (X, Y, Z):", className="control-label"),html.Div([dcc.Input(id='camera-x-input',type='number',value=0.0,step=0.1,className="number-input"),dcc.Input(id='camera-y-input',type='number',value=0.0,step=0.1,className="number-input"),dcc.Input(id='camera-z-input',type='number',value=0.0,step=0.1,className="number-input")], className="input-group")]),], className="slider-group"),html.Div([html.Div([html.Label("相机请按Yaw[Z]->Pitch(Y)->Roll(X)的顺序旋转):", className="control-label"),html.Div([dcc.Input(id='camera-yaw-input',type='number',value=0.0,step=1,className="number-input"),       dcc.Input(id='camera-pitch-input',type='number',value=0.0,step=1,className="number-input"),                        dcc.Input(id='camera-roll-input',type='number',value=0.0,step=1,className="number-input")], className="input-group")]),], className="slider-group"),html.Button("计算变换矩阵", id="calculate-btn", n_clicks=0,className="calculate-btn"),], className="controls"),html.Div([html.H3("变换矩阵结果", className="result-title"),html.Div(id='transformation-matrix', className="matrix-display"),html.Div(id='quaternion-display', className="quaternion-display")], className="results")], className="container")
])# 回调函数:更新3D场景
@dash_app.callback(Output('3d-scene', 'figure'),[Input('camera-x-input', 'value'),Input('camera-y-input', 'value'),Input('camera-z-input', 'value'),Input('camera-roll-input', 'value'),Input('camera-pitch-input', 'value'),Input('camera-yaw-input', 'value')]
)
def update_scene(x_input, y_input, z_input, roll_input, pitch_input, yaw_input):ctx = dash.callback_context# 判断哪个输入触发了回调if not ctx.triggered:trigger_id = Noneelse:trigger_id = ctx.triggered[0]['prop_id'].split('.')[0]x = float(x_input) if x_input is not None else 0y = float(y_input) if y_input is not None else 0z = float(z_input) if z_input is not None else 0roll = float(roll_input) if roll_input is not None else 0pitch = float(pitch_input) if pitch_input is not None else 0yaw = float(yaw_input) if yaw_input is not None else 0# 创建基坐标系axes = create_coordinate_frame(scale=1.5, name="基坐标系")# 创建初始长方体 - 使用修复后的顶点定义box_vertices = create_box_vertices()# 修复2: 使用正确的旋转顺序 'zyx' (yaw->pitch->roll)# 并按照 [yaw, pitch, roll] 顺序传递角度值rotation = R.from_euler('zyx', [yaw, pitch, roll], degrees=True)rotated_vertices = rotation.apply(box_vertices)# 应用平移translated_vertices = rotated_vertices + np.array([x, y, z])# 创建变换后的长方体网格box_mesh = create_box_mesh(translated_vertices)# 添加相机位置标记camera_pos = go.Scatter3d(x=[x], y=[y], z=[z],mode='markers',marker=dict(size=5, color='gold'),name='相机中心')# 添加相机坐标系camera_axes = create_coordinate_frame(origin=[x, y, z], scale=0.7, name="相机坐标系")# 应用旋转到相机坐标系for axis in camera_axes:# 获取原始点orig_x = [axis.x[0], axis.x[1]]orig_y = [axis.y[0], axis.y[1]]orig_z = [axis.z[0], axis.z[1]]# 旋转点points = np.array([[orig_x[0], orig_y[0], orig_z[0]], [orig_x[1], orig_y[1], orig_z[1]]])rotated_points = rotation.apply(points - [x, y, z]) + [x, y, z]# 更新坐标axis.x = rotated_points[:, 0]axis.y = rotated_points[:, 1]axis.z = rotated_points[:, 2]# 组合所有图形元素data = axes + camera_axes + [box_mesh, camera_pos]# 创建3D场景布局layout = go.Layout(scene=dict(xaxis=dict(title='X (前)', range=[-2, 2]),yaxis=dict(title='Y (左)', range=[-2, 2]),zaxis=dict(title='Z (上)', range=[-2, 2]),aspectmode='cube',camera=dict(eye=dict(x=1.5, y=1.5, z=1.5))),margin=dict(l=0, r=0, b=0, t=0),showlegend=True,legend=dict(x=0, y=1))return go.Figure(data=data, layout=layout)def sync_inputs(x_input, y_input, z_input, roll_input, pitch_input, yaw_input):# 此函数未使用,但为了完整性保留return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]# 回调函数:计算变换矩阵
@dash_app.callback([Output('transformation-matrix', 'children'),Output('quaternion-display', 'children')],[Input('calculate-btn', 'n_clicks')],[State('camera-x-input', 'value'),State('camera-y-input', 'value'),State('camera-z-input', 'value'),State('camera-roll-input', 'value'),State('camera-pitch-input', 'value'),State('camera-yaw-input', 'value')]
)
def calculate_transformation(n_clicks, x, y, z, roll, pitch, yaw):if n_clicks == 0:return "", ""# 使用正确的旋转顺序 'zyx' (yaw->pitch->roll)# 并按照 [yaw, pitch, roll] 顺序传递角度值rotation = R.from_euler('zyx', [yaw, pitch, roll], degrees=True)rot_matrix = rotation.as_matrix()# 创建变换矩阵 (4x4)transformation_matrix = np.eye(4)transformation_matrix[:3, :3] = rot_matrixtransformation_matrix[:3, 3] = [x, y, z]# 获取四元数表示quaternion = rotation.as_quat()  # [x, y, z, w]# 格式化矩阵显示matrix_html = html.Div([html.H4("4x4 变换矩阵:"),html.Table([html.Tr([html.Td(f"{transformation_matrix[0, 0]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[0, 1]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[0, 2]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[0, 3]:.4f}", className="matrix-cell")], className="matrix-row"),html.Tr([html.Td(f"{transformation_matrix[1, 0]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[1, 1]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[1, 2]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[1, 3]:.4f}", className="matrix-cell")], className="matrix-row"),html.Tr([html.Td(f"{transformation_matrix[2, 0]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[2, 1]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[2, 2]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[2, 3]:.4f}", className="matrix-cell")], className="matrix-row"),html.Tr([html.Td(f"{transformation_matrix[3, 0]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[3, 1]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[3, 2]:.4f}", className="matrix-cell"),html.Td(f"{transformation_matrix[3, 3]:.4f}", className="matrix-cell")], className="matrix-row")], className="matrix-table"),html.P("该矩阵将点从相机坐标系转换到基坐标系")])# 格式化四元数显示quaternion_html = html.Div([html.H4("四元数表示 (旋转部分):"),html.P(f"q = [{quaternion[3]:.6f}, {quaternion[0]:.6f}, {quaternion[1]:.6f}, {quaternion[2]:.6f}]", className="quaternion-formula"),html.P("(w, x, y, z) 格式"),html.Div([html.P("平移向量:"),html.P(f"t = [{x:.4f}, {y:.4f}, {z:.4f}]")], className="translation-display"),html.H4("应用说明:"),html.Ul([html.Li("四元数表示相机的旋转"),html.Li("平移向量表示相机在基坐标系中的位置"),html.Li("变换矩阵 = 旋转矩阵 × 平移矩阵")])])return matrix_html, quaternion_html# 添加CSS样式
dash_app.css.append_css({'external_url': ('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap','https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css')
})dash_app.css.append_css({'inline':   open("stype.css").read()
})if __name__ == '__main__':dash_app.run(debug=False)
  • stype.css
:root {--primary-color: #1f77b4;--secondary-color: #ff7f0e;--dark-color: #2c3e50;--light-color: #ecf0f1;--success-color: #2ecc71;--danger-color: #e74c3c;}body {font-family: 'Roboto', sans-serif;margin: 0;padding: 0;background-color: #f5f7fa;color: #333;
}.banner {background: linear-gradient(135deg, var(--primary-color), #4a90e2);color: white;padding: 1.5rem;text-align: center;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}.header {margin: 0;font-weight: 500;
}.subtitle {margin: 0.5rem 0 0;opacity: 0.9;
}.container {display: flex;padding: 20px;max-width: 1800px;margin: 0 auto;
}.graph-container {flex: 3;background: white;border-radius: 8px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);overflow: hidden;margin-right: 20px;
}.controls {flex: 1;background: white;padding: 20px;border-radius: 8px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}.results {flex: 1;background: white;padding: 20px;border-radius: 8px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);margin-left: 20px;
}.control-label {display: block;margin: 15px 0 8px;font-weight: 500;color: var(--dark-color);
}.radio-group {padding: 10px 0;border-bottom: 1px solid #eee;
}.slider-group {margin-bottom: 20px;padding-bottom: 15px;border-bottom: 1px solid #eee;
}.slider {margin: 15px 0;width: 100%;
}.input-group {display: flex;justify-content: space-between;margin-bottom: 10px;
}.number-input {width: 30%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;text-align: center;font-size: 14px;
}.number-input:focus {border-color: var(--primary-color);outline: none;box-shadow: 0 0 0 2px rgba(31, 119, 180, 0.2);
}.calculate-btn {background-color: var(--primary-color);color: white;border: none;padding: 12px 20px;border-radius: 4px;cursor: pointer;font-size: 16px;font-weight: 500;width: 100%;margin-top: 10px;transition: background-color 0.3s;
}.calculate-btn:hover {background-color: #1668a6;
}.result-title {color: var(--primary-color);margin-top: 0;border-bottom: 2px solid #eee;padding-bottom: 10px;
}.matrix-display, .quaternion-display {background-color: #f9f9f9;padding: 15px;border-radius: 4px;margin: 15px 0;font-family: monospace;
}.matrix-table {width: 100%;border-collapse: collapse;margin: 10px 0;
}.matrix-row {border: none;
}.matrix-cell {border: 1px solid #ddd;padding: 10px;text-align: center;background-color: white;width: 25%;
}.quaternion-formula {font-size: 16px;background-color: #f0f8ff;padding: 10px;border-radius: 4px;font-family: monospace;
}.translation-display {margin-top: 15px;padding: 10px;background-color: #f0f8ff;border-radius: 4px;font-family: monospace;
}@media (max-width: 1200px) {.container {flex-direction: column;}.graph-container {margin-right: 0;margin-bottom: 20px;}.results {margin-left: 0;margin-top: 20px;}
}

七、总结

这个交互式坐标系变换矩阵计算工具通过可视化界面简化了三维空间变换的计算

http://www.dtcms.com/a/321251.html

相关文章:

  • 时间复杂度计算(以for循环为例)
  • BBH详解:面向大模型的高阶推理评估基准与数据集分析
  • 轻松实现浏览器自动化——AI浏览器自动化框架Stagehand
  • 力扣 hot100 Day69
  • 使用 PicGo 与 GitHub 搭建高效图床,并结合 Local Images Plus 备份原图
  • 杂谈 001 · VScode / Copilot 25.08 更新
  • 供电架构之供电构型分类
  • 浪漫沙迦2|浪漫沙加2 七英雄的复仇 送修改器(Romancing SaGa 2)免安装中文版
  • 机器视觉任务(目标检测、实例分割、姿态估计、多目标跟踪、单目标跟踪、图像分类、单目深度估计)常用算法及公开数据集分享
  • excel 导出
  • 【vue】Vue 重要基础知识清单
  • Numpy科学计算与数据分析:Numpy广播机制入门与实践
  • 使用FinTSB框架进行金融时间序列预测的完整指南
  • 算法提升之-启发式并查集
  • 剪映里面导入多张照片,p图后如何再导出多张照片?
  • VScode 文件标签栏多行显示
  • QML中显示二级界面的三种方式
  • 【Git】企业级使用
  • electron自定义国内镜像
  • 静电释放场景误报率↓78%!陌讯多模态融合算法在工业检测的落地优化
  • 【unity实战】用unity实现一个简易的战斗飞机控制器
  • BUG调试案例十七:ENC424J600以太网掉线问题案例
  • uniapp瀑布流最简单的实现方法
  • SonarQube 扫描多个微服务模块
  • 【51单片机2个按键控制流水灯转向】2022-10-25
  • 移动端开发中类似腾讯Bugly的产品推荐与比较-5款APP异常最终产品推荐-卓伊凡|bigniu
  • springBoot集成minio并实现文件的上传下载
  • 华为网路设备学习-28(BGP协议 三)路由策略
  • 怎么实现对三菱PLC的远程调试和PLC远程维护?
  • 【世纪龙科技】数智重构车身实训-汽车车身测量虚拟实训软件