当前位置:   article > 正文

欧拉角与四元数互转,及四元数slerp球面线性插值算法

欧拉角与四元数互转,及四元数slerp球面线性插值算法

1. 欧拉角与四元数是什么?

roll:翻滚角,pitch:俯仰角,heading:航向角

roll、pitch、heading,这3个角又称为欧拉角,欧拉角是弧度。弧度与度°可以通过公式转换;

四元数:w,x,y,z,有 xx+yy+zz+ww = 1

欧拉角与四元数可以互转,四元数插值完在转回欧拉角,对于航向角突变的情况会更准确;

  • Math.toDegrees(eulerAngles.roll); // 弧度转角度
  • Math.toRadians(roll); // 角度转弧度
  • roll 范围 [-180°~180°]
  • pitch 范围 [-180°~180°]
  • heading 范围 [0°~360°]

2. 源码

2.1 欧拉角类

  1. package test;
  2. /****************************
  3. * Class Name: EulerAngles
  4. * Description: <欧拉角类>
  5. * @Author: seminar
  6. * @create: 2021/05/21
  7. * @since: 1.0.0
  8. ***************************/
  9. public class EulerAngles {
  10. /**
  11. * Math.toRadians(roll) 角度转弧度
  12. * Math.toDegrees(roll) 弧度转角度
  13. *
  14. * 翻滚角(roll) 弧度
  15. */
  16. public double roll;
  17. /**
  18. * 俯仰角(pitch) 弧度
  19. */
  20. public double pitch;
  21. /**
  22. * yaw 即heading(航向角) 弧度
  23. */
  24. public double yaw;
  25. public EulerAngles(float pitch, float yaw, float roll) {
  26. this.pitch = pitch;
  27. this.yaw = yaw;
  28. this.roll = roll;
  29. }
  30. public EulerAngles(float w, float x, float y, float z) {
  31. // roll (x-axis rotation)
  32. float sinr_cosp = 2 * (w * x + y * z);
  33. float cosr_cosp = 1 - 2 * (x * x + y * y);
  34. this.roll = (float) Math.atan2(sinr_cosp, cosr_cosp);
  35. // pitch (y-axis rotation)
  36. float sinp = 2 * (w * y - z * x);
  37. if (Math.abs(sinp) >= 1) {
  38. this.pitch = Math.copySign(1.57075f, sinp); // use 90 degrees if out of range
  39. } else {
  40. this.pitch = (float) Math.asin(sinp);
  41. }
  42. // yaw (z-axis rotation)
  43. float siny_cosp = 2 * (w * z + x * y);
  44. float cosy_cosp = 1 - 2 * (y * y + z * z);
  45. this.yaw = (float) Math.atan2(siny_cosp, cosy_cosp);
  46. }
  47. public Quaternion toQuaternion() {
  48. //欧拉角转四元数,角度减半是因为四元数旋转计算时需要旋转两次,具体原理请查看四元数原理
  49. float cy = (float) Math.cos(yaw * 0.5f);
  50. float sy = (float) Math.sin(yaw * 0.5f);
  51. float cp = (float) Math.cos(pitch * 0.5f);
  52. float sp = (float) Math.sin(pitch * 0.5f);
  53. float cr = (float) Math.cos(roll * 0.5f);
  54. float sr = (float) Math.sin(roll * 0.5f);
  55. Quaternion q = new Quaternion();
  56. q.w = cy * cp * cr + sy * sp * sr;
  57. q.x = cy * cp * sr - sy * sp * cr;
  58. q.y = sy * cp * sr + cy * sp * cr;
  59. q.z = sy * cp * cr - cy * sp * sr;
  60. return q;
  61. }
  62. }

2.2 四元数类

  1. package test;
  2. import lombok.extern.slf4j.Slf4j;
  3. /****************************
  4. * Class Name: Quaternion
  5. * Description: <四元数类>
  6. * @Author: seminar
  7. * @create: 2021/05/21
  8. * @since: 1.0.0
  9. ***************************/
  10. @Slf4j
  11. public class Quaternion {
  12. public float w;
  13. public float x;
  14. public float y;
  15. public float z;
  16. public Quaternion() {
  17. }
  18. public Quaternion(Quaternion b) {
  19. this.w = b.w;
  20. this.x = b.x;
  21. this.y = b.y;
  22. this.z = b.z;
  23. }
  24. public Quaternion(float w, float x, float y, float z) {
  25. this.w = w;
  26. this.x = x;
  27. this.y = y;
  28. this.z = z;
  29. }
  30. //向量旋转
  31. static void VectorRotation(float[] vector, Quaternion q) {
  32. Quaternion qv = new Quaternion(0, vector[0], vector[1], vector[2]);
  33. //四元数旋转公式q0*qv*(q0逆)s
  34. qv = Quaternion.Multiplication(Quaternion.Multiplication(q, qv), q.Inverse());
  35. vector[0] = qv.x;
  36. vector[1] = qv.y;
  37. vector[2] = qv.z;
  38. }
  39. //返回欧拉角
  40. public EulerAngles toEulerAngles() {
  41. // roll (x-axis rotation)
  42. return new EulerAngles(this.w, this.x, this.y, this.z);
  43. }
  44. //四元数相乘
  45. static Quaternion Multiplication(Quaternion q0, Quaternion q1) {
  46. Quaternion ret = new Quaternion();
  47. ret.w = q0.w * q1.w - q0.x * q1.x - q0.y * q1.y - q0.z * q1.z;
  48. ret.x = q0.w * q1.x + q0.x * q1.w + q0.y * q1.z - q0.z * q1.y;
  49. ret.y = q0.w * q1.y + q0.y * q1.w + q0.z * q1.x - q0.x * q1.z;
  50. ret.z = q0.w * q1.z + q0.z * q1.w + q0.x * q1.y - q0.y * q1.x;
  51. return ret;
  52. }
  53. //四元数求逆
  54. public Quaternion Inverse() {
  55. Quaternion ret;
  56. ret = this;
  57. ret.x *= -1;
  58. ret.y *= -1;
  59. ret.z *= -1;
  60. return ret;
  61. }
  62. }

2.3 欧拉角与四元数互转及球面线性插值算法

  1. package test;
  2. import test.EulerAngles;
  3. import test.Quaternion;
  4. import lombok.extern.slf4j.Slf4j;
  5. import static java.lang.Math.abs;
  6. /*************************************
  7. *Class Name: EulerAngle2QuatUtil
  8. *Description: <四元数与欧拉角互转>
  9. *@author: seminar
  10. *@create: 2021/5/24
  11. *@since 1.0.0
  12. *************************************/
  13. @Slf4j
  14. public class EulerAngle2QuatUtil {
  15. /**
  16. * 归一化
  17. *
  18. * @param x
  19. * @param y
  20. * @param z
  21. * @param w
  22. * @return
  23. */
  24. public Quaternion normalizeQuaternion(float w, float x, float y, float z) {
  25. double lengthD = 1.0f / (w * w + x * x + y * y + z * z);
  26. w *= lengthD;
  27. x *= lengthD;
  28. y *= lengthD;
  29. z *= lengthD;
  30. return new Quaternion(w, x, y, z);
  31. }
  32. /**
  33. * Slerp球面线性插值(Spherical Linear Interpolation)
  34. *
  35. * @param a 原始数据a
  36. * @param b 原始数据b
  37. * @param t 要插值的比例(中间插一个值1/2)
  38. * @return
  39. */
  40. public Quaternion makeInterpolated(Quaternion a, Quaternion b, double t) {
  41. Quaternion out = new Quaternion();
  42. double cosHalfTheta = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
  43. if (cosHalfTheta < 0.0F) {
  44. b = new Quaternion(b);
  45. cosHalfTheta = -cosHalfTheta;
  46. b.x = -b.x;
  47. b.y = -b.y;
  48. b.z = -b.z;
  49. b.w = -b.w;
  50. }
  51. double halfTheta = (double) Math.acos((double) cosHalfTheta);
  52. double sinHalfTheta = (double) Math.sqrt((double) (1.0F - cosHalfTheta * cosHalfTheta));
  53. double ratioA;
  54. double ratioB;
  55. if ((double) abs(sinHalfTheta) > 0.001D) {
  56. double oneOverSinHalfTheta = 1.0F / sinHalfTheta;
  57. ratioA = (double) Math.sin((double) ((1.0F - t) * halfTheta)) * oneOverSinHalfTheta;
  58. ratioB = (double) Math.sin((double) (t * halfTheta)) * oneOverSinHalfTheta;
  59. } else {
  60. ratioA = 1.0F - t;
  61. ratioB = t;
  62. }
  63. out.x = (float) (ratioA * a.x + ratioB * b.x);
  64. out.y = (float) (ratioA * a.y + ratioB * b.y);
  65. out.z = (float) (ratioA * a.z + ratioB * b.z);
  66. out.w = (float) (ratioA * a.w + ratioB * b.w);
  67. out = normalizeQuaternion(out.w, out.x, out.y, out.z);
  68. return out;
  69. }
  70. /**
  71. * 欧拉角(弧度)转四元数
  72. *
  73. * @param pitch
  74. * @param yaw
  75. * @param roll
  76. * @return
  77. */
  78. public Quaternion toQuaternion(double pitch, double yaw, double roll) {
  79. EulerAngles eu = new EulerAngles((float) Math.toRadians(pitch), (float) Math.toRadians(yaw), (float) Math.toRadians(roll)); // 角度转弧度
  80. return eu.toQuaternion();
  81. }
  82. /**
  83. * 四元数转欧拉角(弧度)
  84. *
  85. * @param quaternion
  86. * @return
  87. */
  88. public EulerAngles toEulerAngles(Quaternion quaternion) {
  89. return quaternion.toEulerAngles();
  90. }
  91. /**
  92. * 姿态角——即欧拉角转四元数,对俩个四元数进行球面插值,四元数转回欧拉角并返回
  93. *
  94. * @param pitch 位置一俯仰角 -180~180
  95. * @param yaw 位置一航向角 0~360
  96. * @param roll 位置一翻滚角 -180~180
  97. * @param pitch1 位置二俯仰角 -180~180
  98. * @param yaw1 位置二俯仰角 0~360°
  99. * @param roll1 位置二翻滚角 -180~180
  100. * @param t 位置一时间
  101. * @param t1 位置二时间
  102. * @param t_insert 要计算姿态角的位置对应时间
  103. * @return
  104. */
  105. public EulerAngles slerpInsert(float pitch, float yaw, float roll, float pitch1, float yaw1, float roll1, long t, long t1, long t_insert) {
  106. // 位置1 欧拉角转四元数
  107. // 位置2 欧拉角转四元数
  108. Quaternion p = toQuaternion(pitch, yaw, roll);
  109. Quaternion q = toQuaternion(pitch1, yaw1, roll1);
  110. // 计算插入的scale
  111. float scale = (float) ((t_insert - t) / ((t1 - t) * 1.0));
  112. // Slerp球面线性插值
  113. Quaternion r = makeInterpolated(q, p, scale);
  114. // 四元数转欧拉角
  115. EulerAngles eulerAngles = r.toEulerAngles();
  116. return eulerAngles;
  117. }
  118. public static void main(String[] args) {
  119. // 示例,中间1615609866585L的插值不太对
  120. // Roll Pitch Heading
  121. // 1615609866544L -0.9 -0.405 358.809
  122. // 1615609866585L -0.942 -0.362 314.489
  123. // 1615609866625L -0.956 -0.331 0.178
  124. // 正确结果
  125. // Roll Pitch Heading
  126. // 1615609866544L -0.9, -0.405, 358.809
  127. // 1615609866585L -0.929, -0.368359.502
  128. // 1615609866625L -0.956, -0.331, 0.178
  129. // 调用EulerAngle2QuatUtil实现姿态角插值的获取
  130. float roll = -0.9f, pitch = -0.405f, yaw = 358.809f;
  131. EulerAngle2QuatUtil eq = new EulerAngle2QuatUtil();
  132. Quaternion p = eq.toQuaternion(pitch, yaw, roll);
  133. log.info("p: {} {} {} {}", p.w, p.x, p.y, p.z);
  134. float roll1 = -0.956f, pitch1 = -0.331f, yaw1 = 0.178f;
  135. Quaternion q = eq.toQuaternion(pitch1, yaw1, roll1);
  136. log.info("q: {} {} {} {}", q.w, q.x, q.y, q.z);
  137. long t = 1615609866544L;
  138. long t1 = 1615609866625L;
  139. long t_insert = 1615609866585L;
  140. float scale = (float) ((t_insert - t) / ((t1 - t) * 1.0));
  141. // Slerp球面线性插值
  142. Quaternion r = eq.makeInterpolated(q, p, scale);
  143. EulerAngles eulerAngles = r.toEulerAngles();
  144. float roll2 = (float) Math.toDegrees(eulerAngles.roll); // 弧度转回角度
  145. float pitch2 = (float) Math.toDegrees(eulerAngles.pitch); // 弧度转回角度
  146. float heading2 = (float) (Math.toDegrees(eulerAngles.yaw) > 0 ? Math.toDegrees(eulerAngles.yaw) : Math.toDegrees(eulerAngles.yaw) + 360); // 弧度转回角度(航向角0~360°)
  147. log.info("{} {} {}", Double.parseDouble(String.format("%.3f", roll2)), Double.parseDouble(String.format("%.3f", pitch2)), Double.parseDouble(String.format("%.3f", heading2)));
  148. testSlerpInsert(pitch, yaw, roll, pitch1, yaw1, roll1, t, t1, t_insert);
  149. // 0.000 -8.523 0.000
  150. // 0.000 -0.432 93.112
  151. testSlerpInsert(-8.523f, 0.00f, 0.00f, -0.432f, 93.112f, 0.00f, t, t1, t_insert);
  152. // 0.000 1.054 66.847
  153. // 1.237 -1.956 62.336
  154. testSlerpInsert(1.054f, 66.847f, 0.00f, -1.956f, 62.336f, 1.237f, t, t1, t_insert);
  155. // 0.411 5.393 338.058
  156. // 0.402 5.395 338.063
  157. testSlerpInsert(5.393f, 338.058f, 0.411f, 5.395f, 338.063f, 0.402f, t, t1, t_insert);
  158. }
  159. private static void testSlerpInsert(float pitch, float yaw, float roll, float pitch1, float yaw1, float roll1, long t, long t1, long t_insert) {
  160. log.info("==================testSlerpInsert start===============");
  161. EulerAngle2QuatUtil eq = new EulerAngle2QuatUtil();
  162. EulerAngles eulerAngles = eq.slerpInsert(pitch, yaw, roll, pitch1, yaw1, roll1, t, t1, t_insert);
  163. float roll2 = (float) Math.toDegrees(eulerAngles.roll); // 弧度转回角度
  164. float pitch2 = (float) Math.toDegrees(eulerAngles.pitch); // 弧度转回角度
  165. float heading2 = (float) (Math.toDegrees(eulerAngles.yaw) > 0 ? Math.toDegrees(eulerAngles.yaw) : Math.toDegrees(eulerAngles.yaw) + 360); // 弧度转回角度(航向角0~360°)
  166. log.info("slerpInsert {} {} {}", Double.parseDouble(String.format("%.3f", roll2)), Double.parseDouble(String.format("%.3f", pitch2)), Double.parseDouble(String.format("%.3f", heading2)));
  167. log.info("==================testSlerpInsert end=================");
  168. }
  169. private static Quaternion getQuaternion(float roll, float pitch, float yaw) {
  170. EulerAngle2QuatUtil eq = new EulerAngle2QuatUtil();
  171. EulerAngles eu = new EulerAngles((float) Math.toRadians(pitch), (float) Math.toRadians(yaw), (float) Math.toRadians(roll));
  172. Quaternion quaternion = eu.toQuaternion();
  173. EulerAngles eulerAngles = quaternion.toEulerAngles();
  174. float roll2 = (float) Math.toDegrees(eulerAngles.roll); // 弧度转回角度
  175. float pitch2 = (float) Math.toDegrees(eulerAngles.pitch); // 弧度转回角度
  176. float heading2 = (float) (Math.toDegrees(eulerAngles.yaw) > 0 ? Math.toDegrees(eulerAngles.yaw) : Math.toDegrees(eulerAngles.yaw) + 360); // 弧度转回角度(航向角0~360°)
  177. log.info("toDegree: {} {} {}", Double.parseDouble(String.format("%.3f", roll2)), Double.parseDouble(String.format("%.3f", pitch2)), Double.parseDouble(String.format("%.3f", heading2)));
  178. return quaternion;
  179. }
  180. }

参考

  • https://blog.csdn.net/xiaoma_bk/article/details/79082629?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_v2~rank_aggregation-6-79082629.pc_agg_rank_aggregation&utm_term=%E5%9B%9B%E5%85%83%E6%95%B0%E6%AC%A7%E6%8B%89%E8%A7%92%E8%BD%AC%E6%8D%A2%E5%85%AC%E5%BC%8F&spm=1000.2123.3001.4430
  • 在线转换工具
  • 四元数插值
  • 四元数插值2
  • 四元数与欧拉角互转

转载自:欧拉角与四元数互转,及四元数slerp球面线性插值算法 - it610.com

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/422751
推荐阅读
相关标签
  

闽ICP备14008679号