刚体就是 "刚性物体",它在运动过程中,内部各质点间的相对位置不会改变,也即 每两个质点间的距离 保持不变
假设刚体内任意两个质点,坐标分别为 和 ,则在刚体运动过程中,它们满足如下条件:
例如:影视剧《西游记》中的法宝金刚琢、玉净瓶是刚体;而幌金绳、芭蕉扇等,则不是刚体
1 刚体变换
1.1 矩阵形式
OpenCV 之 图像几何变换 中的等距变换,实际是二维刚体变换
从平面推及空间,三维刚体变换的矩阵形式为
例如:空间中一点,在相机坐标系中为 ,求其在世界坐标系中的坐标 ,则有
其中,坐标系 {W} -> {C} 就是一个刚体变换,可记为
1.2 约束分析
和 共有 12 个未知数,但 R 是标准正交矩阵,自带 6 个约束方程,则刚体变换有 12 - 6 = 6 个自由度 (和直观的感受一致)
表面上看,似乎只需 2 组空间对应点,联立 6 个方程,便可求得 6 个未知数,但这 6 个方程是有冗余的 (因为这 2 组对应点,在各自的坐标系下,两点之间的距离是相等的)
因此,第 2 组对应点,只是提供了 2 个约束方程,加上第 1 组对应点的 3 个约束,共有 5 个独立的方程
显然,还需要第 3 组对应点,提供 1 个独立的方程,才能求出 R 和 t
如图所示,两个刚体之间:1个点重合 => 3个自由度;2个点重合 => 1个自由度;3个点重合=> 0个自由度

OpenCV 中有一个函数 estimateAffine3D() 可求解刚体变换的矩阵
1.3 直观理解
一个单位立方体,可在 X-Y-Z 坐标系中自由运动,则运动前后的转换关系,可视为刚体变换
单点重合:当立方体的角点 0 和 X-Y-Z 坐标系的原点 O 重合时,立方体还能自由的旋转 (X 轴 -> Y 轴 -> Z 轴)
两点重合:除了立方体的角点 0 和坐标系的 原点 O 重合外,再令角点 4 和 X 轴上的某点重合,则此时立方体只能 绕 X 轴旋转

三点重合:除了以上两个角点 0 和 4,如果再使角点 1 和 Z 轴上的某点重合,则立方体就会和 X-Y-Z 坐标系 牢牢的连接在一起
因此,选取不共面的三组对应点,联立方程组,便可求得 R 和 T
2 旋转向量
任意的旋转,都可用一个旋转轴 (axis) 和 绕轴旋转角 (angle) 来描述,简称“轴角”

因此,可用一个方向和旋转轴一致,长度等于旋转角的向量来表示旋转,这个向量称为旋转向量 (或“旋量”)
假定旋转轴 ,旋转角 ,则旋转向量为 ;旋转向量到旋转矩阵的转换,可由罗德里格斯公式实现,如下:
反之,从旋转矩阵到旋量,公式如下:
OpenCV 中有一个 Rodrigues() 函数,可实现二者的相互转换
1 2 3 4 5 | void Rodrigues ( InputArray src, // 输入旋转向量 n(3x1 或 1x3) 或 旋转矩阵 R(3x3) OutputArray dst, // 输出旋转矩阵 R 或 旋转向量 n OutputArray jacobian = noArray() // 可选,输出 Jacobian 矩阵(3x9 或 9x3)) |
3 欧拉角
3.1 定义
假定平面内有一点 P, 旋转 角到 位置,如下图:

取 POX = ,列方程组得
转化为矩阵形式
3.2 欧拉角
将二维旋转矩阵 扩展到三维空间
1)绕 Z 轴旋转 roll,则添加 z 坐标

2)绕 Y 轴旋转 yaw,则添加 y 坐标

3)绕 X 轴旋转 pitch,则添加 x 坐标

因此,当按 Z-Y-X 的顺序旋转时,一个旋转矩阵就被分解成了绕不同轴的三次旋转,旋转角称为 "欧拉角"
,
注意:在使用欧拉角时,要先指明旋转的顺序,因为绕不同的轴旋转时得到的欧拉角也不同
反之,由旋转矩阵求解欧拉角,则有:
3.3 代码实现
已知绕三个轴旋转的欧拉角,要转换为旋转矩阵,直接套用公式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Mat eulerAnglesToRotationMatrix(Vec3f &theta){ // Calculate rotation about x axis Mat R_x = (Mat_<double>(3,3) << 1, 0, 0, 0, cos(theta[0]), -sin(theta[0]), 0, sin(theta[0]), cos(theta[0]) ); // Calculate rotation about y axis Mat R_y = (Mat_<double>(3,3) << cos(theta[1]), 0, sin(theta[1]), 0, 1, 0, -sin(theta[1]), 0, cos(theta[1]) ); // Calculate rotation about z axis Mat R_z = (Mat_<double>(3,3) << cos(theta[2]), -sin(theta[2]), 0, sin(theta[2]), cos(theta[2]), 0, 0,
0,
1 ); // Combined rotation matrix Mat R = R_z * R_y * R_x; return R;} |
旋转矩阵到欧拉角的转换,要指明旋转顺序 (Z-Y-X 或 X-Y-Z 等 6 种),下面代码实现了和 MATLAB 中 rotm2euler 一样的功能,只是旋转顺序不同 (X-Y-Z)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // Checks if a matrix is a valid rotation matrix.bool isRotationMatrix(Mat &R){ Mat Rt; transpose(R, Rt); Mat shouldBeIdentity = Rt * R; Mat I = Mat::eye(3,3, shouldBeIdentity.type()); return norm(I, shouldBeIdentity) < 1e-6;}// The result is the same as MATLAB except the order of the euler angles ( x and z are swapped ).Vec3f rotationMatrixToEulerAngles(Mat &R){ assert(isRotationMatrix(R)); float sy = sqrt(R.at<double>(0,0) * R.at<double>(0,0) + R.at<double>(1,0) * R.at<double>(1,0) ); bool singular = sy < 1e-6; // If float x, y, z; if (!singular) { x = atan2(R.at<double>(2,1) , R.at<double>(2,2)); y = atan2(-R.at<double>(2,0), sy); z = atan2(R.at<double>(1,0), R.at<double>(0,0)); } else { x = atan2(-R.at<double>(1,2), R.at<double>(1,1)); y = atan2(-R.at<double>(2,0), sy); z = 0; } return Vec3f(x, y, z);} |
4 四元数
4.1 定义
四元数本质是一种高阶的复数,普通复数有一个实部和一个虚部,而四元数有一个实部和三个虚部
,其中

平面中任一点的旋转,可通过“左乘” 旋转向量来表示,如下:
,
推及空间中任一点的旋转,可通过“左乘”四元数来表示,如下:
,,其中 由 组合而成
4.2 实例
例1:当向量 p 围绕 k 轴在 i-j 平面内旋转 45°,表示该旋转的四元数为
取 ,则 ,如下图 p' 确实是 p 围绕 轴旋转 45° 得到的

例2:当向量 p 围绕 q 旋转 45°,且 q 中的向量 v 在 i-k 平面内和 p 成 45° 时,表示该旋转的四元数为
取 ,则 ,可看出 中向量模长为 ,这不再是一个纯旋转的变换

但如果再“右乘” ,则 ,如下图,这又变成了一个纯旋转,但是旋转的角度是 90° 不是 45°

综上所述,向量 p 围绕任一轴 旋转 ,则表示该旋转的四元数形式为
4.3 转换关系
假定四元数 ,则旋转矩阵为
或另一种形式
OpenCV 中实现的函数 quat2rot() 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // R = quat2rot(q)// q - 4x1 unit quaternion <qw, qx, qy, qz>// R - 3x3 rotation matrixstatic Mat quat2rot(const Mat& q){ CV_Assert(q.type() == CV_64FC1 && q.rows == 4 && q.cols == 1); double qw = q.at<double>(0,0); double qx = q.at<double>(1,0); double qy = q.at<double>(2,0); double qz = q.at<double>(3,0); Mat R(3, 3, CV_64FC1); R.at<double>(0, 0) = 1 - 2*qy*qy - 2*qz*qz; R.at<double>(0, 1) = 2*qx*qy - 2*qz*qw; R.at<double>(0, 2) = 2*qx*qz + 2*qy*qw; R.at<double>(1, 0) = 2*qx*qy + 2*qz*qw; R.at<double>(1, 1) = 1 - 2*qx*qx - 2*qz*qz; R.at<double>(1, 2) = 2*qy*qz - 2*qx*qw; R.at<double>(2, 0) = 2*qx*qz - 2*qy*qw; R.at<double>(2, 1) = 2*qy*qz + 2*qx*qw; R.at<double>(2, 2) = 1 - 2*qx*qx - 2*qy*qy; return R;} |
反之,由旋转矩阵转换为四元数,其实现函数 rot2quat() 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | // q = rot2quat(R)// q - 4x1 unit quaternion <qw, qx, qy, qz>// R - 3x3 rotation matrixstatic Mat rot2quat(const Mat& R){ CV_Assert(R.type() == CV_64FC1 && R.rows >= 3 && R.cols >= 3); double m00 = R.at<double>(0,0), m01 = R.at<double>(0,1), m02 = R.at<double>(0,2); double m10 = R.at<double>(1,0), m11 = R.at<double>(1,1), m12 = R.at<double>(1,2); double m20 = R.at<double>(2,0), m21 = R.at<double>(2,1), m22 = R.at<double>(2,2); double trace = m00 + m11 + m22; double qw, qx, qy, qz; if (trace > 0) { double S = sqrt(trace + 1.0) * 2; // S=4*qw qw = 0.25 * S; qx = (m21 - m12) / S; qy = (m02 - m20) / S; qz = (m10 - m01) / S; } else if (m00 > m11 && m00 > m22) { double S = sqrt(1.0 + m00 - m11 - m22) * 2; // S=4*qx qw = (m21 - m12) / S; qx = 0.25 * S; qy = (m01 + m10) / S; qz = (m02 + m20) / S; } else if (m11 > m22) { double S = sqrt(1.0 + m11 - m00 - m22) * 2; // S=4*qy qw = (m02 - m20) / S; qx = (m01 + m10) / S; qy = 0.25 * S; qz = (m12 + m21) / S; } else { double S = sqrt(1.0 + m22 - m00 - m11) * 2; // S=4*qz qw = (m10 - m01) / S; qx = (m02 + m20) / S; qy = (m12 + m21) / S; qz = 0.25 * S; } return (Mat_<double>(4,1) << qw, qx, qy, qz);} |
附 - 欧拉角可视化
一个欧拉角的可视化链接 Euler Angle Visualization Tool,输入欧拉角可实时显示位姿变化

参考资料
《An Invitaton to 3D Vision》 ch2
《Robot Vision》ch13
OpenCV - 3D rigid/afine transforamtion
Rotation Matrix To Euler Angles
An Orge compatible class for euler angles
原文链接: http://www.cnblogs.com/xinxue/
专注于机器视觉、OpenCV、C++ 编程


· .Net Core 中无处不在的 Async/Await 是如何提升性能的?
· 分布式系统改造方案 —— 老旧系统改造篇
· C# 模式匹配完全指南
· C# 异步编程由浅入深(三)细说 Awaiter
· 突破限制,CSS font-variation 可变字体的魅力
· 黑色水景金字塔上的玻璃屋,又一家独特的 Apple Store
· OPPO 联合武汉大学研发的「退烧手机壳」,散热效果好吗?
· Google要造一台能「理解」你的电脑
· 激光雷达到底是「噱头」,还是真的刚需?
· 超时、差评不再扣钱,美团新规则会对骑手更友好吗?
» 更多新闻...
2016-04-28 C++11 之 scoped enum