Matlab机器人工具箱版本9.10
在我前面的博文基于抛物线过渡(梯形加减速)的空间直线插补算法与空间圆弧插补算法、五自由度diy机械臂空间插补算法(直线和圆弧)简单测试中出现姿态奇异性问题,这主要是由于我选用的RPY角姿态表达方法在β角等于±90°时姿态解算会出现奇异现象,剩下两个自由度会退化,α角和γ角会变成一个角。为解决这个问题,我换用单位四元数来对姿态进行表达。
结果显示能够解决奇异性问题:
机器人姿态表达形式主要有旋转矩阵法、欧拉角法、RPY角法、单位四元数法等。
旋转矩阵法可以通过累乘表示坐标系的连续变化,能够清晰地表示当前坐标系相对于基座标系的姿态,是常用的姿态表达方式。但是由于旋转矩阵法需要9个元素才能描述姿态,在程序处理中需要使用大量内存空间且计算复杂,所以旋转矩阵法不适合应用与实时性较高的姿态控制和运动插补控制中。
欧拉角法和RPY角法都能非常简单地描述物体姿态。机器人末端执行器的示教姿态通常以欧拉角的形式给出,RPY通常表示船舶在航行中的姿态变化,都存在奇异性问题。
单位四元数在表达姿态时具有运算量小、便于插值等优势,而且单位四元数插补得到的中间姿态要比欧拉角法表示刚体旋转更为自然,同样的姿态变换下,欧拉角的姿态变化幅度大,因此单位四元数法要比欧拉法更加适合描述刚体姿态的变换。
关于四元数的定义以及基本知识,阅读下面著作即可:
常用的四元数知识:
q − 1 = q ∗ ∥ q ∥ 2 ( 1 ) \mathbf{q}^{-1}=\frac{\mathbf{q}^{*}}{\|\mathbf{q}\|^{2}}(1) q−1=∥q∥2q∗(1)
对于单元四元数来说, q − 1 = q ∗ \mathbf{q}^{-1}=\mathbf{q}^{*} q−1=q∗
v ′ = q v q − 1 ( 2 ) \mathbf{v}^{\prime}=\mathbf{q} \mathbf{v} \mathbf{q}^{-1}(2) v′=qvq−1(2)
R = [ q 0 2 + q 1 2 − q 2 2 − q 3 2 2 ( q 1 q 2 − q 0 q 3 ) 2 ( q 1 q 3 + q 0 q 2 ) 2 ( q 1 q 2 + q 0 q 3 ) q 0 2 − q 1 2 + q 2 2 − q 3 2 2 ( q 2 q 3 − q 0 q 1 ) 2 ( q 1 q 3 − q 0 q 2 ) 2 ( q 2 q 3 + q 0 q 1 ) q 0 2 − q 1 2 − q 2 2 + q 3 2 ] ( 3 ) R=\left[\begin{array}{ccc}{q_{0}^{2}+q_{1}^{2}-q_{2}^{2}-q_{3}^{2}} & {2\left(q_{1} q_{2}-q_{0} q_{3}\right)} & {2\left(q_{1} q_{3}+q_{0} q_{2}\right)} \\ {2\left(q_{1} q_{2}+q_{0} q_{3}\right)} & {q_{0}^{2}-q_{1}^{2}+q_{2}^{2}-q_{3}^{2}} & {2\left(q_{2} q_{3}-q_{0} q_{1}\right)} \\ {2\left(q_{1} q_{3}-q_{0} q_{2}\right)} & {2\left(q_{2} q_{3}+q_{0} q_{1}\right)} & {q_{0}^{2}-q_{1}^{2}-q_{2}^{2}+q_{3}^{2}}\end{array}\right](3) R=⎣⎡q02+q12−q22−q322(q1q2+q0q3)2(q1q3−q0q2)2(q1q2−q0q3)q02−q12+q22−q322(q2q3+q0q1)2(q1q3+q0q2)2(q2q3−q0q1)q02−q12−q22+q32⎦⎤(3)
同时机械臂末端姿态矩阵R为:
R = [ n x o x a x n y o y a y n z o z a z ] ( 4 ) \boldsymbol{R}=\left[\begin{array}{lll}{n_{x}} & {o_{x}} & {a_{x}} \\ {n_{y}} & {o_{y}} & {a_{y}} \\ {n_{z}} & {o_{z}} & {a_{z}}\end{array}\right](4) R=⎣⎡nxnynzoxoyozaxayaz⎦⎤(4)
解得对应的四元数为:
q = q 1 + q 2 i + q 3 j + q 4 k ( 5 ) q=q_{1}+q_{2} {i}+q_{3} {j}+q_{4} k(5) q=q1+q2i+q3j+q4k(5)
其中, q 1 = 1 2 n x + o y + a z + 1 q_{1}=\frac{1}{2} \sqrt{n_{x}+o_{y}+a_{z}+1} q1=21nx+oy+az+1,虽然开方有负数,考虑 θ ∈ [ − π / 2 , π / 2 ] \theta \in[-\pi / 2, \pi / 2] θ∈[−π/2,π/2],故 q 1 = c o s θ > 0 q_{1}=cos\theta >0 q1=cosθ>0,其余元素的正负号由下式决定:
{ 4 a s = o z − a y 4 b s = a x − n z 4 c s = n y − o x ( 6 ) \left\{\begin{array}{l}{4 a s=o_{z}-a_{y}} \\ {4 b s=a_{x}-n_{z}} \\ {4 c s=n_{y}-o_{x}}\end{array}\right.(6) ⎩⎨⎧4as=oz−ay4bs=ax−nz4cs=ny−ox(6)
故得到各元素为:
q 1 = 1 2 n x + o y + a z + 1 ( 7 ) q_{1}=\frac{1}{2} \sqrt{n_{x}+o_{y}+a_{z}+1}(7) q1=21nx+oy+az+1(7)
q 2 = 1 2 sign ( o z − a y ) n x − o y − a z + 1 ( 8 ) q_{2}=\frac{1}{2} \operatorname{sign}\left(o_{z}-a_{y}\right) \sqrt{n_{x}-o_{y}-a_{z}+1}(8) q2=21sign(oz−ay)nx−oy−az+1(8)
q 3 = 1 2 sign ( a x − n z ) − n x + o y − a z + 1 ( 9 ) q_{3}=\frac{1}{2} \operatorname{sign}\left(a_{x}-n_{z}\right) \sqrt{-n_{x}+o_{y}-a_{z}+1}(9) q3=21sign(ax−nz)−nx+oy−az+1(9)
q 4 = 1 2 sign ( n y − o x ) − n x − o y + a z + 1 ( 10 ) q_{4}=\frac{1}{2} \operatorname{sign}\left(n_{y}-o_{x}\right) \sqrt{-n_{x}-o_{y}+a_{z}+1}(10) q4=21sign(ny−ox)−nx−oy+az+1(10)
对于一般的线性插值来说: r = p 0 + ( p 1 − p 0 ) t r=p_{0}+\left(p_{1}-p_{0}\right) t r=p0+(p1−p0)t,其中 t ∈ [ 0 , 1 ] t \in[0,1] t∈[0,1],代表了插值矢量r末端在弦 P 0 − P 1 P_{0}-P_{1} P0−P1上的位置。如下图所示,假设的取值为1/4、2/4、3/4,也就是将P0P1弦长均分为4等份,可以看出其对应的弧长并不相等。靠近中间位置的弧长较长,而靠近两段处的弧长较短,这就意味着当t匀速变化时,代表姿态矢量的角速度变化并不均匀。
为了更好的解决非恒定旋转速度和归一化问题,需要一种球面线性插值SLERP。SLERP类似于线性插值,除了不是沿着一条直线插入而是沿着球体表面上的一个弧进行插值,如下图所示。
下面来推导一下SLERP公式。插值的一般公式可以写为:
r = a ( t ) P 0 + b ( t ) P 1 ( 11 ) r=a(t) P_{0}+b(t) P_{1}(11) r=a(t)P0+b(t)P1(11)
现在要找到合适的 a ( t ) a(t) a(t)和 b ( t ) b(t) b(t)。注意单位向量 P 0 P_{0} P0和 P 1 P_{1} P1之间的夹角为 θ \theta θ, P 0 P_{0} P0和r之间的夹角为 t θ t\theta tθ, P 1 P_{1} P1和r之间的夹角为 ( 1 − t ) θ (1-t)\theta (1−t)θ:
将(11)两边同乘 P 0 P_{0} P0可得
P 0 ⋅ r = a ( t ) P 0 ⋅ P 0 + b ( t ) P 0 ⋅ P 1 ( 12 ) P_{0} \cdot r=a(t) P_{0} \cdot P_{0}+b(t) P_{0} \cdot P_{1}(12) P0⋅r=a(t)P0⋅P0+b(t)P0⋅P1(12)
cos t θ = a ( t ) + b ( t ) cos θ ( 13 ) \cos t \theta=a(t)+b(t) \cos \theta(13) costθ=a(t)+b(t)cosθ(13)
同样对(11)两边同乘 P 1 P_{1} P1可得
cos [ ( 1 − t ) θ ] = a ( t ) cos θ + b ( t ) ( 14 ) \cos [(1-t) \theta]=a(t) \cos \theta+b(t)(14) cos[(1−t)θ]=a(t)cosθ+b(t)(14)
两个方程可以解出两个未知量 a ( t ) a(t) a(t)和 b ( t ) b(t) b(t):
a ( t ) = cos t θ − cos [ ( 1 − t ) θ ] cos θ 1 − cos 2 θ ( 15 ) a(t)=\frac{\cos t \theta-\cos [(1-t) \theta] \cos \theta}{1-\cos ^{2} \theta}(15) a(t)=1−cos2θcostθ−cos[(1−t)θ]cosθ(15)
b ( t ) = cos [ ( 1 − t ) θ ] − cos t θ cos θ 1 − cos 2 θ ( 16 ) b(t)=\frac{\cos [(1-t) \theta]-\cos t \theta \cos \theta}{1-\cos ^{2} \theta}(16) b(t)=1−cos2θcos[(1−t)θ]−costθcosθ(16)
简化后可得单位四元数的球面线性插值表达式为:
Slerp ( P 0 , P 1 , t ) = sin [ ( 1 − t ) θ ] sin θ P 0 + sin [ t θ ] sin θ P 1 ( 17 ) \operatorname{Slerp}\left(P_{0}, P_{1}, t\right)=\frac{\sin [(1-t) \theta]}{\sin \theta} P_{0}+\frac{\sin [t \theta]}{\sin \theta} P_{1}(17) Slerp(P0,P1,t)=sinθsin[(1−t)θ]P0+sinθsin[tθ]P1(17)
注意这个公式有2个问题,必须在实现过程中加以考虑:
替换掉前面博文代码中的RPY姿态插补部分,修改一下函数参数,替换代码如下:
直线插补:
.....
% 计算两个四元数之间的夹角
dot_q = Qs.s*Qd.s + Qs.v(1)*Qd.v(1) + Qs.v(2)*Qd.v(2) + Qs.v(3)*Qd.v(3);
if (dot_q < 0)
dot_q = -dot_q;
end
for i = 1: N
% 位置插补
x(i) = x1 + delta_x*lambda(i);
y(i) = y1 + delta_y*lambda(i);
z(i) = z1 + delta_z*lambda(i);
% 单位四元数球面线性姿态插补
% 插值点四元数
if (dot_q > 0.9995)
k0 = 1-lambda(i);
k1 = lambda(i);
else
sin_t = sqrt(1 - power(dot_q, 2));
omega = atan2(sin_t, dot_q);
k0 = sin((1-lambda(i)*omega)) / sin(omega);
k1 = sin(lambda(i)*omega) / sin(omega);
end
qk(i) = Qs * k0 + Qd * k1;
end
空间圆弧插补:
......
% 计算两个四元数之间的夹角
dot_q = Qs_.s*Qd_.s + Qs_.v(1)*Qd_.v(1) + Qs_.v(2)*Qd_.v(2) + Qs_.v(3)*Qd_.v(3);
if (dot_q < 0)
dot_q = -dot_q;
end
for i = 1: N
% 位置插补
x_(i) = flag * r * cos(lambda(i)*delta_ang);
y_(i) = flag * r * sin(lambda(i)*delta_ang);
P = T*[x_(i); y_(i); 0; 1];
x(i) = P(1);
y(i) = P(2);
z(i) = P(3);
% 单位四元数球面线性姿态插补
% 插值点四元数
if (dot_q > 0.9995)
k0 = 1-lambda(i);
k1 = lambda(i);
else
sin_t = sqrt(1 - power(dot_q, 2));
omega = atan2(sin_t, dot_q);
k0 = sin((1-lambda(i))*omega) / sin(omega);
k1 = sin(lambda(i)*omega) / sin(omega);
end
qk(i) = Qs_ * k0 + Qd_ * k1;
end
四元数插值与均值(姿态平滑)
四元数法及其应用
2019.11.25
发现了一处错误,计算k0的程序出错了,已改成k0 = sin((1-lambda(i))*omega) / sin(omega);