草庐IT

欧拉角,四元数和旋转矩阵互转代码【python版】

学书才浅 2024-03-06 原文

这里写目录标题^ ^

写在前面

  • 欧拉角以Roll、Pitch、Yaw的顺序表示
  • 四元数以[ q w q_w qw, q x q_x qx, q y q_y qy, q z q_z qz]的顺序表示
  • 代码包括了欧拉角与四元数互转旋转矩阵与四元数互转欧拉角与旋转矩阵互转,输入参数均为np.array形式
  • 代码内置了角度制和弧度制😃😃当时因为这块吃了好多亏
  • 顺便测试了一下pydrake库,发现:
    1. pydrake库中是弧度制
    2. 输出结果与代码输出结果几乎一致(但pydrake精度更高)
  • 由于原理这块肯定已经有很多很完善的资料了所以不做过多赘述

思路

东拼西凑找出的这些公式,为了验证他们的正确性,采用以下方法验证:
随机生成一个欧拉角e,将其与
a. 欧拉角e -> 旋转矩阵r -> 欧拉角e
b. 欧拉角e -> 四元数q -> 欧拉角e
c. 欧拉角e -> 四元数q -> 旋转矩阵r -> 欧拉角e
d. 欧拉角e -> 旋转矩阵r -> 四元数q -> 欧拉角e
比较,看误差有多大(如图所示),并重复n轮

直接上代码!

注意⚠️

涉及到旋转矩阵的四个函数似乎有点问题【虽然自洽但是结果不对!】
如果最后导出的是旋转矩阵,请暂时不要使用本代码!!!
红豆泥私密马赛!


代码部分

utils.py

import math
import numpy as np 

# from pydrake.all import RotationMatrix, RollPitchYaw
 
# 四元数转欧拉角
# ================OKOK
def quaternion_to_euler(q, degree_mode=1):
    qw, qx, qy, qz = q

    roll = math.atan2(2 * (qw * qx + qy * qz), 1 - 2 * (qx ** 2 + qy ** 2))
    pitch = math.asin(2 * (qw * qy - qz * qx))
    yaw = math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy ** 2 + qz ** 2))
    # degree_mode=1:【输出】是角度制,否则弧度制
    if degree_mode == 1:
        roll = np.rad2deg(roll)
        pitch = np.rad2deg(pitch)
        yaw = np.rad2deg(yaw)
    euler = np.array([roll, pitch, yaw])
    return euler
 
# 欧拉角转四元数
# ================OKOK
def euler_to_quaternion(euler, degree_mode=1):
    roll, pitch, yaw = euler
    # degree_mode=1:【输入】是角度制,否则弧度制
    if degree_mode == 1:
        roll = np.deg2rad(roll)
        pitch = np.deg2rad(pitch)
        yaw = np.deg2rad(yaw)

    qx = np.sin(roll/2) * np.cos(pitch/2) * np.cos(yaw/2) - np.cos(roll/2) * np.sin(pitch/2) * np.sin(yaw/2)
    qy = np.cos(roll/2) * np.sin(pitch/2) * np.cos(yaw/2) + np.sin(roll/2) * np.cos(pitch/2) * np.sin(yaw/2)
    qz = np.cos(roll/2) * np.cos(pitch/2) * np.sin(yaw/2) - np.sin(roll/2) * np.sin(pitch/2) * np.cos(yaw/2)
    qw = np.cos(roll/2) * np.cos(pitch/2) * np.cos(yaw/2) + np.sin(roll/2) * np.sin(pitch/2) * np.sin(yaw/2)
    q = np.array([qw, qx, qy, qz])
    return q

# 四元数转旋转矩阵:
def quaternion_to_rot(q): 
    q0,q1,q2,q3=q 

    R=np.array([[1-2*(q2**2+q3**2),2*(q1*q2-q3*q0),2*(q1*q3+q2*q0)], 
                     [2*(q1*q2+q3*q0),1-2*(q1**2+q3**2),2*(q2*q3-q1*q0)], 
                     [2*(q1*q3-q2*q0),2*(q2*q3+q1*q0),1-2*(q1**2+q2**2)]]) 
    return R 
 
# 旋转矩阵转四元数:
# ================OKOK
def rot_to_quaternion(R): 
    R=R 
    
    qw=np.sqrt(1+R[0,0]+R[1,1]+R[2,2])/2 
    qx=(R[2,1]-R[1,2])/(4*qw) 
    qy=(R[0,2]-R[2,0])/(4*qw) 
    qz=(R[1,0]-R[0,1])/(4*qw) 
    q = np.array([qw, qx, qy, qz])
    return q

# 旋转矩阵转欧拉角
# ================OKOK
def rot_to_euler(R, degree_mode=1):
    sy = np.sqrt(R[0,0] * R[0,0] +  R[1,0] * R[1,0])
 
    singular = sy < 1e-6
 
    if not singular :
        roll = np.arctan2(R[2,1] , R[2,2])
        pitch = np.arctan2(-R[2,0], sy)
        yaw = np.arctan2(R[1,0], R[0,0])
    else :
        roll = np.arctan2(-R[1,2], R[1,1])
        pitch = np.arctan2(-R[2,0], sy)
        yaw = 0
    
    # degree_mode=1:【输出】是角度制,否则弧度制
    if degree_mode == 1:
        roll = np.rad2deg(roll)
        pitch = np.rad2deg(pitch)
        yaw = np.rad2deg(yaw)

    euler = np.array([roll, pitch, yaw])
    return euler

# 欧拉角转旋转矩阵
# # ================OKOK
def euler_to_rot(euler, degree_mode=1):
    roll, pitch, yaw = euler
    # degree_mode=1:【输入】是角度制,否则弧度制
    if degree_mode == 1:
        roll = np.deg2rad(roll)
        pitch = np.deg2rad(pitch)
        yaw = np.deg2rad(yaw)

    R_x = np.array([
    [1,     0,              0              ],
    [0,     math.cos(roll), -math.sin(roll)],
    [0,     math.sin(roll), math.cos(roll) ]
    ])
 
    R_y = np.array([
    [math.cos(pitch),  0,   math.sin(pitch) ],
    [0,                1,   0               ],
    [-math.sin(pitch), 0,   math.cos(pitch) ]
    ])
 
    R_z = np.array([
    [math.cos(yaw), -math.sin(yaw),  0],
    [math.sin(yaw), math.cos(yaw),   0],
    [0,             0,               1]
    ])
    
    R = np.dot(R_z, np.dot( R_y, R_x ))
    return R


if __name__ == "__main__":
    # 测试思路:随机生成几组欧拉角,用各种转换后看是否与原数据相同
    # 备注:np.sin(),math.sin()的返回值都是弧度制
    # np.atan2(), math.asin()这些也是弧度制
    print(np.sin(90), np.sin(45))
    print(np.sin(3.14), np.sin(1.57))
    print(math.sin(90), math.sin(45))
    print(math.sin(3.14), math.sin(1.57))
    mode = 0  # 当这个值等于1时, 输入和输出的欧拉角均为 <角度制>
    for i in range(10):
        print('=' * 32, "index:", i, '=' * 35)
        euler = np.random.randint(0, 360, 3) - 180
        # euler = np.array([60, 30, 45])

        if mode != 1:
            # 限制最多小数点后三位
            euler = np.array([round(t * math.pi / 180, 3) for t in euler])

        print("euler = ", euler)
        e2q2e = quaternion_to_euler(euler_to_quaternion(euler, mode), mode)
        e2r2e = rot_to_euler(euler_to_rot(euler, mode), mode)
        e2r2q2e = quaternion_to_euler(rot_to_quaternion(euler_to_rot(euler, mode)), mode)
        e2q2r2e = rot_to_euler(quaternion_to_rot(euler_to_quaternion(euler, mode)), mode)
        
        # 限制这些值为[-180,179)
        if mode == 1:
            e2q2e = [(int((180 * 3 + t)) % 360) - 180 for t in e2q2e]
            e2r2e = [(int((180 * 3 + t)) % 360) - 180 for t in e2r2e]
            e2r2q2e = [(int((180 * 3 + t)) % 360) - 180 for t in e2r2q2e]
            e2q2r2e = [(int((180 * 3 + t)) % 360) - 180 for t in e2q2r2e]
        else:  # 限制最多小数点后三位
            e2q2e = [round((int((math.pi * 3 * 1000 + t * 1000)) % 6283 - 3142) / 1000, 3)\
                 for t in e2q2e]    
            e2r2e = [round((int((math.pi * 3 * 1000 + t * 1000)) % 6283 - 3142) / 1000, 3)\
                 for t in e2r2e]   
            e2r2q2e = [round((int((math.pi * 3 * 1000 + t * 1000)) % 6283 - 3142) / 1000, 3)\
                 for t in e2r2q2e]   
            e2q2r2e = [round((int((math.pi * 3 * 1000 + t * 1000)) % 6283 - 3142) / 1000, 3)\
                 for t in e2q2r2e]     

        print("e -> q -> e = ", e2q2e, "norm of delta = ", np.linalg.norm(e2q2e - euler))
        print("e -> r -> e = ", e2r2e, "norm of delta = ", np.linalg.norm(e2r2e - euler))
        print("e -> r -> q -> e = ", e2r2q2e, "norm of delta = ", np.linalg.norm(e2r2q2e - euler))
        print("e -> q -> r -> e = ", e2q2r2e, "norm of delta = ", np.linalg.norm(e2q2r2e - euler))

        # if mode == 0:   
        #     # pydrake中的旋转角是弧度制
        #     print("drake actual rot = ", RotationMatrix(RollPitchYaw(euler[0],euler[1],euler[2])))
        #     print("rot =", euler_to_rot(euler, mode))
        #     print("e -> q -> rot =", quaternion_to_rot(euler_to_quaternion(euler, mode)))

那串看起来非常笨重的限制我感觉我玷污了这个语言的美、、

一个问题

  • 使用随机数,会发现部分随机的欧拉角会变成另一个角,这两个角的效果应该是等效的

    (使用弧度制也是一样)

  • 不过个人感觉在实际应用中应该也不会出现这种"错误的角度",因为角度都是通过各种正确值之间的转换得来的

补充

这里感谢下这个博主——
下面这几个网站都是从他的这篇文章转过来的👇

工具网站推荐 - 欧拉角四元数在线可视化转化网站/三维在线旋转变换网站

1 欧拉角四元数在线可视化转换网站

内容:四元数与欧拉角的可视化
特点:直观,明显
缺点:没有旋转矩阵
网站地址:https://quaternions.online/

2 三维在线旋转变换网站

内容:四元数与欧拉角与旋转矩阵的转换
特点:全面,丰富,可以调欧拉角形式
缺点:有一些并不常用的参数,容易搞混懵圈
网站地址:https://www.andre-gaschler.com/rotationconverter/

补充一点关于上面的问题

  • 输入这个"错误欧拉角"在两个网站里也会得到一个不同的欧拉角,见上图和下图,所以应该不影响,也就是说正常使用时不会出现这种"错误欧拉角"

有关欧拉角,四元数和旋转矩阵互转代码【python版】的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  4. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  5. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  6. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  7. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  8. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  9. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  10. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

随机推荐