工业6轴机械臂运动学逆解(解析解)
通常工业机械臂采用6旋转轴串连的形式,保证了灵活性,但为其运动学逆解(即已知机械臂末端的位姿 P P P,求机械臂各个旋转轴的旋转角)带来了较大的困难,通常没有解析解。为了提高实时性,经过前辈们不懈的研究,当6轴机械臂满足pieper准则时,可以得出其运动学逆解的解析解。pieper准则如下:
- 三个相邻关节轴线交于一点,如fanuc m10系列;
- 三个相邻关节轴线相互平行,如ur5系列;
以下将通过一个简单实例,介绍机械臂在pieper第一准则的情况的运动学逆解。
机械臂运动学模型
机械臂的简化模型如图1所示。
采用DH矩阵的形式对机械臂进行建模,DH矩阵如下:
关节 | 连杆夹角 | 连杆长度 | 连杆偏距 | 初始关节角 |
---|---|---|---|---|
1 | 0 | 0 | 0.1 | 0 |
2 | 0.5 π \pi π | 0 | 0 | 0 |
3 | 0 | 0.5 | 0 | 0.5 π \pi π |
4 | 0.5 π \pi π | 0 | 0.5 | 0 |
5 | -0.5 π \pi π | 0 | 0 | 0 |
6 | 0.5 π \pi π | 0 | 0 | 0 |
从图1中可以看出,在初始状态下,机械臂的第2、3关节轴与基座坐标系的Y轴相互平行,机械臂的第4、5、6关节轴线相交于一点 P P P,满足pieper第一准则,且点 P P P与基座坐标系的XOZ平面重合。在这些条件下,虽然限制了机械臂的设计和构型,但极大地简化了逆解的过程。
运动学逆解
由于机械臂的结构比较简单,故采用几何法的方式求解机械臂的运动学逆解。
机械臂末端初始位姿 P 0 P_0 P0,运动学逆解末端的位姿 P ( x , y , z ) P(x,y,z) P(x,y,z)
求解第1关节轴的关节角
以基坐标系为参考,机械臂末端位置示意图如图2所示。
在图1中,点 P 0 P_0 P0为机械臂末端的初始状态位置(当前位置),点 P P P为机械臂末端的期望位置,由于初始状态时,机械臂末端的初始位置与 X 0 Y 0 X_0Y_0 X0Y0平面重合,故关节轴1需要旋转 θ 1 \theta_1 θ1或 θ 1 + π \theta_1 + \pi θ1+π才能使期望位置 P P P与 X 0 Y 0 X_0Y_0 X0Y0平面重合,故,关节轴1存在两个解
θ 1 = { a t a n 2 ( y , x ) a t a n 2 ( y , x ) + π (1) \theta_1 = \begin{cases} atan2(y, x)\\ atan2(y, x) + \pi\\ \end{cases} \tag1 θ1={atan2(y,x)atan2(y,x)+π(1)
求解第2、3关节轴的关节角
关节轴1经过旋转 θ 1 = a t a n 2 ( y , x ) \theta_1=atan2(y, x) θ1=atan2(y,x)角度,以第一关节轴的坐标系为参考,得示意图如图3所示。 P 1 P_1 P1为机械臂末端的当前位置, P P P为末端期望位置, O A 0 OA_0 OA0、 O A 1 OA_1 OA1为机械臂的第2连杆, A 0 P A_0P A0P、 A 1 P A_1P A1P为机械臂的第3连杆。
设 A 0 ( x 0 , 0 , z 0 ) A_0(x_0, 0, z_0) A0(x0,0,z0),可得方程:
x 0 2 + z 0 2 = D H [ 2 , 1 ] 2 ( x − x 0 ) 2 + ( z − D H [ 0 , 2 ] − z 0 ) 2 = D H [ 3 , 2 ] 2 (2) x_0^2 + z_0^2 = DH[2, 1]^2\\ (x - x_0)^2 + (z - DH[0, 2] - z_0)^2 = DH[3, 2]^2 \tag2 x02+z02=DH[2,1]2(x−x0)2+(z−DH[0,2]−z0)2=DH[3,2]2(2)
对2元2次方程组(2)进行求解,得 ( x 00 , z 00 ) (x_{00}, z_{00}) (x00,z00)、 ( x 01 , z 01 ) (x_{01}, z_{01}) (x01,z01)、 ( x 02 , z 02 ) (x_{02}, z_{02}) (x02,z02)、 ( x 03 , z 03 ) (x_{03}, z_{03}) (x03,z03)四组解,去除其中的非实数解。由此可得到关节轴2、3的旋转角。
θ 2 = a t a n 2 ( z 0 , x 0 ) θ 3 = a t a n 2 ( z − D H [ 0 , 2 ] − z 0 , x − x 0 ) − a t a n 2 ( z 0 , x 0 ) (2) \theta_2 = atan2(z_0, x_0)\\ \theta_3 = atan2(z - DH[0, 2] - z_0, x - x_0) - atan2(z_0, x_0) \tag2 θ2=atan2(z0,x0)θ3=atan2(z−DH[0,2]−z0,x−x0)−atan2(z0,x0)(2)
如图2所示,解方程(2),可得到关节角 θ 2 \theta_2 θ2、 θ 3 \theta_3 θ3的两组解。
同理,当取 θ 1 = a t a n 2 ( y , x ) + π \theta_1=atan2(y, x) + \pi θ1=atan2(y,x)+π时,亦可得到关节角 θ 2 \theta_2 θ2、 θ 3 \theta_3 θ3的两组解。
至此, θ 1 \theta_1 θ1、 θ 2 \theta_2 θ2、 θ 3 \theta_3 θ3存在8组解,可去除其中相同的解。
求解第4、5、6关节轴的关节角
经过对机械臂前3根轴的旋转,已经机械臂的末端位置与期望的末端位置相重合,由于关节轴4、5、6相交与末端位置,对此3轴的旋转不会改变末端的位置,故,单独对此3轴进行姿态解算即可得到关节角。
设 R 1 ( θ 1 ) R_1(\theta_1) R1(θ1)、 R 2 ( θ 2 ) R_2(\theta_2) R2(θ2)、 R 3 ( θ 3 ) R_3(\theta_3) R3(θ3)、 R 4 ( θ 4 ) R_4(\theta_4) R4(θ4)、 R 5 ( θ 5 ) R_5(\theta_5) R5(θ5)、 R 6 ( θ 6 ) R_6(\theta_6) R6(θ6)表示各轴的变换矩阵。将对关节轴4、5、6的旋转看成是动欧拉角ZYZ的旋转模式,其旋转矩阵为 R ( θ 4 , θ 5 , θ 6 ) R(\theta_4,\theta_5,\theta_6) R(θ4,θ5,θ6)。
R ( θ 4 , θ 5 , θ 6 ) = [ c o s θ 4 − s i n θ 4 0 s i n θ 4 c o s θ 4 0 0 0 1 ] [ c o s θ 5 0 s i n θ 5 0 1 0 − s i n θ 5 0 c o s θ 5 ] [ c o s θ 6 − s i n θ 6 0 s i n θ 6 c o s θ 6 0 0 0 1 ] (3) R(\theta_4,\theta_5,\theta_6) = \begin{bmatrix} cos\theta_4&-sin\theta_4&0\\ sin\theta_4&cos\theta_4&0\\ 0&0&1\\ \end{bmatrix} \begin{bmatrix} cos\theta_5&0&sin\theta_5\\ 0&1&0\\ -sin\theta_5&0&cos\theta_5\\ \end{bmatrix} \begin{bmatrix} cos\theta_6&-sin\theta_6&0\\ sin\theta_6&cos\theta_6&0\\ 0&0&1\\ \end{bmatrix} \tag3 R(θ4,θ5,θ6)= cosθ4sinθ40−sinθ4cosθ40001 cosθ50−sinθ5010sinθ50cosθ5 cosθ6sinθ60−sinθ6cosθ60001 (3)
具体计算得:
R ( θ 4 , θ 5 , θ 6 ) = [ c o s θ 4 c o s θ 5 c o s θ 6 − s i n θ 4 s i n θ 6 − c o s θ 4 c o s θ 5 s i n θ 6 − s i n θ 4 c o s c o s θ 6 θ 4 s i n θ 5 s i n θ 4 c o s θ 5 c o s θ 6 + c o s θ 4 s i n θ 6 − s i n θ 4 c o s θ 5 s i n θ 6 + c o s θ 4 c o s θ 6 s i n θ 4 s i n θ 5 − s i n θ 5 c o s θ 6 s i n θ 5 s i n θ 6 c o s θ 5 ] (4) R(\theta_4,\theta_5,\theta_6) = \begin{bmatrix} cos\theta_4cos\theta_5cos\theta_6-sin\theta_4sin\theta_6&-cos\theta_4cos\theta_5sin\theta_6-sin\theta_4cos&cos\theta_6\theta_4sin\theta_5\\ sin\theta_4cos\theta_5cos\theta_6+cos\theta_4sin\theta_6&-sin\theta_4cos\theta_5sin\theta_6+cos\theta_4cos\theta_6&sin\theta_4sin\theta_5\\ -sin\theta_5cos\theta_6&sin\theta_5sin\theta_6&cos\theta_5\\ \end{bmatrix} \tag4 R(θ4,θ5,θ6)= cosθ4cosθ5cosθ6−sinθ4sinθ6sinθ4cosθ5cosθ6+cosθ4sinθ6−sinθ5cosθ6−cosθ4cosθ5sinθ6−sinθ4cos−sinθ4cosθ5sinθ6+cosθ4cosθ6sinθ5sinθ6cosθ6θ4sinθ5sinθ4sinθ5cosθ5 (4)
由机械臂的正运动学可得:
R ( θ 1 ) R ( θ 2 ) R ( θ 3 ) R ( θ 4 = 0 ) R ( θ 4 , θ 5 , θ 6 ) = R P (5) R(\theta_1)R(\theta_2)R(\theta_3)R(\theta_4=0)R(\theta_4, \theta_5, \theta_6)=R_P \tag5 R(θ1)R(θ2)R(θ3)R(θ4=0)R(θ4,θ5,θ6)=RP(5)
在公式(5)中, R P R_P RP为机械臂末端点 P P P的姿态。对公式(5)进行移项得:
R ( θ 4 , θ 5 , θ 6 ) = [ R ( θ 1 ) R ( θ 2 ) R ( θ 3 ) R ( θ 4 = 0 ) ] − 1 R P = [ r 11 r 12 r 13 r 21 r 22 r 23 r 31 r 32 r 33 ] (6) R(\theta_4, \theta_5, \theta_6)=[R(\theta_1)R(\theta_2)R(\theta_3)R(\theta_4=0)]^{-1}R_P= \begin{bmatrix} r_{11}&r_{12}&r_{13}\\ r_{21}&r_{22}&r_{23}\\ r_{31}&r_{32}&r_{33}\\ \end{bmatrix} \tag6 R(θ4,θ5,θ6)=[R(θ1)R(θ2)R(θ3)R(θ4=0)]−1RP= r11r21r31r12r22r32r13r23r33 (6)
联立公式(4)(6)可得两组解:
{ θ 4 = a t a n 2 ( r 23 , r 13 ) θ 5 = a t a n 2 ( s q r t ( r 31 2 + r 32 2 ) , r 33 ) θ 6 = a t a n 2 ( r 32 , − r 31 ) (7) \begin{cases} \theta_4=atan2(r_{23}, r_{13})\\ \theta_5=atan2(sqrt(r_{31}^2+r_{32}^2), r_{33})\\ \theta_6=atan2(r_{32}, -r_{31}) \end{cases} \tag7 ⎩ ⎨ ⎧θ4=atan2(r23,r13)θ5=atan2(sqrt(r312+r322),r33)θ6=atan2(r32,−r31)(7)
{ θ 4 = a t a n 2 ( r 23 , r 13 ) + π θ 5 = − a t a n 2 ( s q r t ( r 31 2 + r 32 2 ) , r 33 ) θ 6 = a t a n 2 ( r 32 , − r 31 ) + π (8) \begin{cases} \theta_4=atan2(r_{23}, r_{13}) + \pi\\ \theta_5=-atan2(sqrt(r_{31}^2+r_{32}^2), r_{33})\\ \theta_6=atan2(r_{32}, -r_{31}) + \pi \end{cases} \tag8 ⎩ ⎨ ⎧θ4=atan2(r23,r13)+πθ5=−atan2(sqrt(r312+r322),r33)θ6=atan2(r32,−r31)+π(8)
综上,完成机械臂的运动学逆解的解析解求解过程,可能存在8个以上的解,可根据一些约束调节对求得的解进行删选,如关节限位、碰撞检测等。
示例程序
import numpy as np
import math
from pyquaternion import Quaternionnp.set_printoptions(suppress=True)# DH矩阵每列的含义:连杆夹角、连杆长度、连杆偏距、初始关节角
DH = np.mat([[ 0, 0, 0.1, 0], [ 0.5 * math.pi, 0, 0, 0], [ 0, 0.5, 0, 0.5 * math.pi], [ 0.5 * math.pi, 0, 0.5, 0], [-0.5 * math.pi, 0, 0, 0], [ 0.5 * math.pi, 0, 0, 0]])def transformToMatrix(alpha, a, d, theta):T0 = np.eye(4)T1 = np.mat([[1, 0, 0, 0], [0, math.cos(alpha), -math.sin(alpha), 0], [0, math.sin(alpha), math.cos(alpha), 0], [0, 0, 0, 1]])T2 = np.mat([[1, 0, 0, a], [0, 1, 0, 0], [0, 0, 1, d], [0, 0, 0, 1]])T3 = np.mat([[math.cos(theta), -math.sin(theta), 0, 0], [math.sin(theta), math.cos(theta), 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1]])return T1 * T2 * T3def forwardKinematic(DH, j0, j1, j2, j3, j4, j5):T0 = transformToMatrix(DH[0, 0], DH[0, 1], DH[0, 2], DH[0, 3] + j0)T1 = transformToMatrix(DH[1, 0], DH[1, 1], DH[1, 2], DH[1, 3] + j1)T2 = transformToMatrix(DH[2, 0], DH[2, 1], DH[2, 2], DH[2, 3] + j2)T3 = transformToMatrix(DH[3, 0], DH[3, 1], DH[3, 2], DH[3, 3] + j3)T4 = transformToMatrix(DH[4, 0], DH[4, 1], DH[4, 2], DH[4, 3] + j4)T5 = transformToMatrix(DH[5, 0], DH[5, 1], DH[5, 2], DH[5, 3] + j5)#print(T0 * T1 * T2 * T3 * T4 * T5)return T0 * T1 * T2 * T3 * T4 * T5def calcu3ForwardJointAngle(DH, j0, x0, y0, x, y, b, js):if abs((x0 - x) * (x0 - x) + (y0 - y) * (y0 - y) - b*b) < 0.0001:js.append([j0])js[-1].append(math.atan2(y0, x0))js[-1].append(math.atan2(y - y0, x - x0) - math.atan2(y0, x0) + 0.5 * math.pi - DH[2, 3])js.append([j0 + math.pi])js[-1].append(math.pi - math.atan2(y0, x0))js[-1].append(math.atan2(y0, x0) - math.atan2(y - y0, x - x0) + 0.5 * math.pi - DH[2, 3])if abs((x0 - x) * (x0 - x) + (-y0 - y) * (-y0 - y) - b*b) < 0.0001:js.append([j0])js[-1].append(math.atan2(-y0, x0))js[-1].append(math.atan2(y + y0, x - x0) - math.atan2(-y0, x0) + 0.5 * math.pi - DH[2, 3])js.append([j0 + math.pi])js[-1].append(math.pi - math.atan2(-y0, x0))js[-1].append(math.atan2(-y0, x0) - math.atan2(y + y0, x - x0) + 0.5 * math.pi - DH[2, 3])def quaternionToRotationMatrix(x, y, z, w):# a = math.sqrt(x*x + y*y + z*z)# if a == 0:# return np.eye(3)# v1x = 0# v1y = -z# v1z = y# b = math.sqrt(v1x*v1x + v1y*v1y + v1z*v1z)# if b == 0:# v1y = 1.0# v1z = 0.0# b = 1.0# v2 = np.cross(np.array([x, y, z]), np.array([v1x, v1y, v1z]))# #print(np.array([x, y, z]), np.array([v1x, v1y, v1z]), v2)# c = math.sqrt(v2[0]*v2[0] + v2[1]*v2[1] + v2[2]*v2[2])# R01 = np.mat([[x / a, v1x / b, v2[0] / c],# [y / a, v1y / b, v2[1] / c],# [z / a, v1z / b, v2[2] / c]])# theta = 2 * math.acos(w)# #print(R01)# R12 = np.mat([[1, 0, 0],# [0, math.cos(theta), -math.sin(theta)],# [0, math.sin(theta), math.cos(theta)]])# return R01 * R12 * np.linalg.inv(R01)a = math.sqrt(x*x + y*y + z*z + w*w)if a == 0:print('quaternion is error')return np.eye(3)x = x / ay = y / az = z / aw = w / areturn np.mat([[1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w],[ 2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w],[ 2*x*z - 2*y*w, 2*y*z + 2*x*w, 1 - 2*x*x - 2*y*y]])def rotateMatrixToQuaternion(R):R1 = [[R[0, 0], R[0, 1], R[0, 2]],[R[1, 0], R[1, 1], R[1, 2]],[R[2, 0], R[2, 1], R[2, 2]]]q = Quaternion(matrix=np.array(R1))return q.x, q.y, q.z, q.wdef inverseKinematic(DH, x, y, z, ox, oy, oz, ow):q_length = math.sqrt(ox*ox + oy*oy + oz*oz + ow*ow)if q_length == 0:print('quaternion is error')returnelse:ox = ox / q_lengthoy = oy / q_lengthoz = oz / q_lengthow = ow / q_lengthjs = []j00 = math.atan2(y, x)a = DH[2, 1]b = DH[3, 2]c = math.sqrt(x*x + y*y + (z - DH[0, 2])*(z - DH[0, 2]))a0 = 4*(x*x+y*y) + 4*(z - DH[0, 2])*(z - DH[0, 2])a1 = -4*(a*a - b*b + c*c)*math.sqrt(x*x + y*y)a2 = (a*a - b*b + c*c) * (a*a - b*b + c*c) - 4 * (z - DH[0, 2]) * (z - DH[0, 2]) * a * aif a1*a1 - 4*a0*a2 > 0:x0 = (-a1 + math.sqrt(a1*a1 - 4*a0*a2)) / (2 * a0)y0 = math.sqrt(a*a - x0*x0)#print('x0: ', x0, 'y0: ', y0)calcu3ForwardJointAngle(DH, j00, x0, y0, math.sqrt(x*x+y*y), z - DH[0, 2], b, js)x0 = (-a1 - math.sqrt(a1*a1 - 4*a0*a2)) / (2 * a0)y0 = math.sqrt(a*a - x0*x0)#print('x1: ', x0, 'y1: ', y0)calcu3ForwardJointAngle(DH, j00, x0, y0, math.sqrt(x*x+y*y), z - DH[0, 2], b, js)elif a1*a1 - 4*a0*a2 == 0:x0 = (-a1) / (2 * a0)y0 = math.sqrt(a*a - x0*x0)#print('x0: ', x0, 'y0: ', y0)calcu3ForwardJointAngle(DH, j00, x0, y0, math.sqrt(x*x+y*y), z - DH[0, 2], b, js)else:print('no solve')js = [[]]new_js = []for j in js:R = quaternionToRotationMatrix(ox, oy, oz, ow)T01 = transformToMatrix(DH[0, 0], DH[0, 1], DH[0, 2], DH[0, 3] + j[0])T12 = transformToMatrix(DH[1, 0], DH[1, 1], DH[1, 2], DH[1, 3] + j[1])T23 = transformToMatrix(DH[2, 0], DH[2, 1], DH[2, 2], DH[2, 3] + j[2])T34 = transformToMatrix(DH[3, 0], DH[3, 1], DH[3, 2], DH[3, 3])R01 = np.mat([[T01[0, 0], T01[0, 1], T01[0, 2]],[T01[1, 0], T01[1, 1], T01[1, 2]],[T01[2, 0], T01[2, 1], T01[2, 2]]])R12 = np.mat([[T12[0, 0], T12[0, 1], T12[0, 2]],[T12[1, 0], T12[1, 1], T12[1, 2]],[T12[2, 0], T12[2, 1], T12[2, 2]]])R23 = np.mat([[T23[0, 0], T23[0, 1], T23[0, 2]],[T23[1, 0], T23[1, 1], T23[1, 2]],[T23[2, 0], T23[2, 1], T23[2, 2]]])R34 = np.mat([[T34[0, 0], T34[0, 1], T34[0, 2]],[T34[1, 0], T34[1, 1], T34[1, 2]],[T34[2, 0], T34[2, 1], T34[2, 2]]])R = (R34.T) * (R23.T) * (R12.T) * (R01.T) * R#print('RRRRRRRRR: \n', R)alpha = math.atan2(R[1, 2], R[0, 2])betla = math.atan2(math.sqrt(R[2, 0]*R[2, 0] + R[2, 1]*R[2, 1]), R[2, 2])gamal = math.atan2(R[2, 1], -R[2, 0])new_js.append([])new_js[-1].append(j[0])new_js[-1].append(j[1])new_js[-1].append(j[2])new_js[-1].append(alpha)new_js[-1].append(betla)new_js[-1].append(gamal)#print(new_js[-1])new_js.append([])new_js[-1].append(j[0])new_js[-1].append(j[1])new_js[-1].append(j[2])new_js[-1].append(alpha + math.pi)new_js[-1].append(-betla)new_js[-1].append(gamal + math.pi)#print(new_js[-1])return new_jsif __name__ == '__main__':print("hello world")#print(DH)print(forwardKinematic(DH, 1, 0, 0, 0, 0, 0))js = inverseKinematic(DH, 0.5, 0.11, 0.26, 0.0, 0.0, 0.8, 0.6)print('##########################')for j in js:print('joint angle: ', j)T = forwardKinematic(DH, j[0], j[1], j[2], j[3], j[4], j[5])#print('P: ', T[0, 3], T[1, 3], T[2, 3])#print('Q: ', rotateMatrixToQuaternion(T))print('##########################')
注意实现
- 此运动学逆解求解器不具备通用性,只适用于满足以上DH矩阵格式的6轴串联机器人;
- 示例程序中,没有对所求的解进行筛选,存在超出限位、碰撞的情况;
- 在求解2元2次方程组时,注意其中的非实数解;