【Python-Unity】基于Levenberg-Marquardt的UR3机械臂位置逆解

摘要: Python程序实现了基于LM法计算UR3机械臂的位置逆解,并通过插补得到机器人轨迹,发送到Unity客户端,控制机械臂运动。

ceb745f613711b3d7e41724c6be994d7.png

d2f026a93e490c29b3c465e4fd4d19e3.pngUnity场景

操作演示

e188edcf00fa6e2b65edec658ff7a9e1.png代码逻辑



源代码

一、Unity场景脚本 

1.1 客户端Client.cs

// ZeroMQ library for C#
using NetMQ;
using NetMQ.Sockets;
// System libraries
using System;
using System.Threading;
using System.Collections.Generic;
// Default Json library for work with json
using Newtonsoft.Json;
// Unity Engine
using UnityEngine;


// Set up dictonary for receive data
public class Dict
{
    [JsonProperty("dtype")]
    public string dtype { get; set; }


    [JsonProperty("shape")]
    public List<int> shape { get; set; }


    [JsonProperty("data")]
    public List<List<float>> data { get; set; }
}


public class Client : MonoBehaviour
{
    // prepare client Thread
    private Thread tcpListenerThread;
    // get joints root
    public Joint m_root;
    // bool if is data
    private static bool move = false;
    // dict class
    public static Dict dict;
    // check collision bool
    public static bool is_collision = false;
    // iteration variable
    public static int i;


    // automatically update and if not collision and data do motion
    private void FixedUpdate()
    {
        if (!is_collision && move)
        {
            try
            {
                // from root joint move
                Joint current = m_root;


                // load data from dict
                current.transform.localEulerAngles = new Vector3(0, 0, (-1) * (float)dict.data[i][0]);
                current = current.GetChild();


                current.transform.localEulerAngles = new Vector3(90, 0, (-1) * (float)dict.data[i][1]);
                current = current.GetChild();


                current.transform.localEulerAngles = new Vector3(0, 0, (-1) * (float)dict.data[i][2]);
                current = current.GetChild();


                current.transform.localEulerAngles = new Vector3(0, 0, (-1) * (float)dict.data[i][3]);
                current = current.GetChild();


                current.transform.localEulerAngles = new Vector3(90, 0, (-1) * (float)dict.data[i][4]);
                current = current.GetChild();


                current.transform.localEulerAngles = new Vector3(270, 0, (-1) * (float)dict.data[i][5]);
            }


            catch (Exception e)
            {
                Debug.Log("Exception:" + e);
            }
        }
    }
    void Start(){
        // Start TcpServer background thread     
        tcpListenerThread = new Thread(new ThreadStart(ListenForIncommingRequests));
        tcpListenerThread.IsBackground = true;
        tcpListenerThread.Start();
        Debug.Log("Start Thread");
    }


    private void ListenForIncommingRequests(){
        try
        {
            using (var client = new RequestSocket())
            {
                // connect to server
                client.Connect("tcp://127.0.0.1:5555");
                // send message
                client.SendFrame("Start Compute");
                // receive dictonary
                var msg = client.ReceiveFrameString();
                // convert to dict
                dict = JsonConvert.DeserializeObject<Dict>(msg);
                // data is in class
                move = true;
                // iterate throu main and low Thread
                for (int k = 0; k < dict.shape[0]; k++)
                {
                    i += 1;
                    Thread.Sleep(10);
                }
            }


            move = false;


        }
        catch (Exception socketException)
        {
            Debug.Log("SocketException " + socketException);
        }
    }


    // On quit application -> end tasks
    void OnApplicationQuit()
    {
        Thread.Sleep(10);
        Debug.Log("End Thread");
        // !!!! - Neccesary for quit NetMQ libary -> multiple run issue!
        NetMQConfig.Cleanup();
    }
}

1.2 Joint脚本

using UnityEngine;


public class Joint : MonoBehaviour
{
    // set child joint
    public Joint m_child;


    // check on trigger collision
    void OnTriggerEnter(Collider other)
    {
        Client.is_collision = true;
        string objectName = gameObject.name;
        Debug.Log("Collision detected: " + objectName);
    }
    // get chiled joint
    public Joint GetChild()
    {
        return m_child;
    }
}

二、Python程序

2.1 服务器

import zmq
import time
from IK import IK_LM


def send_array(socket, A):
    msg = dict(
        dtype = str(A.dtype),
        shape = A.shape,
        data = A.tolist()
    )
    socket.send_json(msg)


def start_server():
    context = zmq.Context()
    socket = context.socket(zmq.REP)
    socket.bind("tcp://*:5555")


    while True:
        print("Server On")
        # wait for receive message
        msg = socket.recv()
        time.sleep(0.5)
        if (msg.decode("utf-8") == "Start Compute"):   
            # compute and send as json dict
            a = IK_LM().LM()
            send_array(socket, a)


if __name__ == "__main__":
    start_server()

2.2 IK程序

import dill
import numpy as np
from numpy import pi, cos, sin, arctan2


class IK_LM(object):
    """
    Accesible methods:
    :method 1: LM
    :method 2: demo


    """
    def __init__(self) -> None:
        """
        Load dill file with Jacobian matrix for defined robot.


        """
        self.f_new = dill.load(open("Jacobian", "rb"))
    
    # L-M法 计算位置逆解
    def LM(self) -> np.ndarray:
        """
        Compute IK depands on demo trajectory.


        :returns: array of trajectory joints
        """
        angles = [0, 0, 0, 0, 0, 0]
        
        p, r = self.demo(0)
        out, angles = self.__compute(angles, p, r) ##初始角度零位,迭代计算p,r位姿的逆解##


        for i in range(1,5):
            p, r = self.demo(i)
            a, angles = self.__compute(angles, p, r)#其他5个路径点逆解##
            out = np.r_[out, a]  #路径点堆叠


        return out


    #示例路径点
    def demo(self, target: int) -> list:
        """
        Defined demo trajectory of robot.


        :param target: switch case inpup
        :returns: point, rotation
        :raises: case out of range
        """
        match target:
            case 0:
                p1 = [0.0, -0.194, 0.69]
                r1 = [-90, 0, -180]
                return p1, r1


            case 1:
                p2 = [-0.21, -0.24, 0.2]
                r2 = [-180, 0, -180]
                return p2, r2


            case 2:
                p3 = [-0.21,-0.24, 0.326]
                r3 = [-180, 0, -180]
                return p3, r3


            case 3:
                p4 = [-0.21,-0.24, 0.326]
                r4 = [-29, 52, -140]
                return p4, r4


            case 4:
                p4 = [-0.21,-0.413, 0.326]
                r4 = [-29, 52, -140]
                return p4, r4
            
            case _:
                raise ("No exist target")
    
    ## 根据给定关节角度计算雅克比矩阵##
    def __J(self, th1, th2, th3, th4, th5, th6) -> np.ndarray:
        """
        Computation Jacobian matrix for UR3 by defined angles[rad]        
        
        :param th1-th6: joints angle
        """
       
        return self.f_new(th1, th2, th3, th4, th5, th6).astype('float64')  


    # 位置正解 #
    def __FK(self, th: np.ndarray) -> np.ndarray: 
        """
        Computation of Forward Kinematics by classics D-H tables  


        :param th: joints angle
        """
        # D-H parameters of UR3
        o = [pi/2, 0, 0, pi/2, -pi/2, 0]
        d = [0.1519, 0, 0, 0.11235, 0.08535, 0.0819]
        a = [0, -0.24365, -0.21325, 0, 0, 0]
        
        for i in range(6):
            A_x = np.array([
                [cos(th[i]),  -sin(th[i]) * cos(o[i]),     sin(th[i]) * sin(o[i]),     a[i] * cos(th[i])], 
                [sin(th[i]),   cos(th[i]) * cos(o[i]),    -cos(th[i]) * sin(o[i]),     a[i] * sin(th[i])],
                [0       ,     sin(o[i]),                  cos(o[i]),                  d[i]             ],
                [0       ,     0,                          0,                          1                ]           
            ])


            if i == 0:
                A = A_x
            
            else:
                A = A @ A_x 
        
        return A


    #根据tcp位置和欧拉角 计算期望位姿#
    def __comp_t_desired(self, target: np.ndarray) -> np.ndarray:
        """
        Computation target Forward Kinematics D-H table


        :param target: translation, rotation of target
        :returns: 4x4 d-h table of target  
        """
        x = target[0]
        y = target[1]
        z = target[2]
        
        alfa = target[3]
        beta = target[4]
        gamma = target[5]
        
        R_x = np.array([
            [1, 0,           0        ],
            [0, cos(alfa),  -sin(alfa)],
            [0, sin(alfa),   cos(alfa)] 
        ])
        
        R_y = np.array([
            [cos(beta),  0, sin(beta)],
            [0,          1, 0        ],
            [-sin(beta), 0, cos(beta)] 
        ])
        
        R_z = np.array([
            [cos(gamma), -sin(gamma),  0], 
            [sin(gamma),  cos(gamma),  0],
            [0,           0,           1]
        ])


        R = R_z @ R_y @ R_x


        return np.r_[
            np.c_[
                R, np.array([x,y,z])
            ], [np.array([0,0,0,1])]
        ]


    #计算当前位姿到期望位姿的轴角距离#
    def __Angle_Axis(self, T_desired: np.ndarray, T_current: np.ndarray) -> np.ndarray:
        """
        Computation angle-axis distance


        :param T_desired: d-h table of target
        :param T_current: d-h table current state
        :returns: 6x1 array translation, rotation  
        """
        Td = T_desired[:3,3]
        Ti = T_current[:3,3]


        Rd = T_desired[:3,:3]
        Ri = T_current[:3,:3]


        R = Rd @ Ri.T
        
        l = np.array([ 
            [R[2,1] - R[1,2]],
            [R[0,2] - R[2,0]],
            [R[1,0] - R[0,1]]
        ])
        
        l_length = np.linalg.norm(l)


        if(l_length > 0):
            a = ((arctan2(l_length, R[0,0] + R[1,1] + R[2,2] - 1 ) ) / l_length) * l


        else:  
            if(R[0,0] + R[1,1] + R[2,2] > 0):
                a = np.array([[0,0,0]]).T
            
            else:
                a = pi/2 * np.array([[
                    R[0,0] + 1,
                    R[1,1] + 1,
                    R[2,2] + 1
                ]]).T
        
        return np.r_[np.array([Td-Ti]).T, a]  #[dx;dy;dz;da;db;dc]


    #LM法计算位置逆解   给定当前关节角度,计算到目标位姿的关节角度序列
    def __compute(self, angles: list, target_position: list, target_rotation: list) -> np.ndarray:
        """
        Computation of Levenberg-Marquardt method.


        :param angles: init angles[°]  初始角度列表
        :param target_position: translation of target [x,y,z] [m]  目标位置 
        :param target_rotation: rotation of target [r,p,y] [°] Euler-angles    #目标欧拉角
        :returns: X x 6 matrix of path targets   #返回路径点对应关节角度列表
        """
        angles = np.deg2rad(angles)
        target_rotation = np.deg2rad(target_rotation)
        target = np.append(target_position, target_rotation)


        i = 0
        q = np.array([angles])


        # init d-h tables 
        T_desired = self.__comp_t_desired(target)  #期望位姿
        T_current = self.__FK(q[i,:])  #当前位姿


        # error computation
        error = np.linalg.norm(T_current - T_desired)  #位姿误差范数计算
        # do until error will be in specified accuracy  #
        
        while error > 0.001:
            J_comp = self.__J(q[i,0],q[i,1],q[i,2],q[i,3],q[i,4],q[i,5])  #雅可比矩阵
            H_k = J_comp.T @ np.eye(6,6) @ J_comp  #运算符@在矩阵运算中的功能是矩阵乘法    近似海森矩阵
            g_k = J_comp.T @ np.eye(6,6) @ self.__Angle_Axis(T_desired, T_current) # 
            
            
            if (np.linalg.det(J_comp)!=0):
                q = np.r_[q, q[i,:] + (np.linalg.inv(H_k) @ g_k).T]  # np.r_是按行增加拼接两个矩阵(效果是每列看上去延长了),就是把两矩阵上下相加,要求列数相等。#


            else:
                q = np.r_[q, q[i,:] + (np.linalg.pinv(H_k) @ g_k).T]    # np.c_是按列增加拼接两个矩阵(效果是每行看上去延长了),就是把两矩阵左右相加,要求行数相等。#


            # 计算新的误差
            T_current = self.__FK(q[-1,:])  #计算新的位姿
            error = np.linalg.norm(T_current - T_desired)  #更新误差范数


            # 将计算的关节从 360° 限制到 180° -> 防止一些自碰撞
            if(np.any(q[-1,:] > pi) or np.any(q[i+1,:] < -pi)):   #防止自碰撞
                l = np.argwhere(q[-1,:] > pi)
                k = np.argwhere(q[-1,:] < -pi)
                q[-1,l] = q[i,l]  #纠正最后一行对应元素
                q[-1,k] = q[i,k]


            i += 1  #迭代次数++


        """
        ! Check Correctness purpose
        ! Correct by UR - Polyscope


        A = self.FK(q[-1,:])
        R = A[:3, :3]


        beta = arctan2(-R[2,0], np.sqrt(R[0,0]**2 + R[1,0]**2))
        alfa = arctan2(R[1,0] / cos(beta), R[0,0] / cos(beta))
        gamma = arctan2(R[2,1] / cos(beta), R[2,2] / cos(beta))


        rpy = np.rad2deg([gamma, beta, alfa])
        xyz = A[:3,3]
        # goal = np.rad2deg(q[-1,:])
        """


        goal = q[-1,:]  #最后一个目标点


        # 生成简单路径,包含 100 个样本
        nr_pnts = 100  #100个采样点
        a = np.zeros((nr_pnts, 6))
        #关节 空间插补
        for i in range(5): #第6个关节不用管,0
            a[:,i] = np.linspace(np.rad2deg(angles[i]), np.rad2deg(goal[i]), nr_pnts)


        return a, np.rad2deg(goal)

2.3 雅可比计算

import dill
import sympy as sym
import numpy as np
from numpy import pi
from sympy import sin, cos, Matrix, lambdify


# Script generates 6-dof robot Jacobian matrix
# D-H parameters are from Universal Robots UR3 robot
# Script can be easily modifed by add/remove d-h table and 
# computation of Forward Kinematics 


th1, th2, th3, th4, th5, th6 = sym.symbols('th1, th2, th3, th4, th5, th6')


o1 = pi/2
d1 = 0.1519
a1 = 0


o2 = 0
d2 = 0
a2 = -0.24365


o3 = 0
d3 = 0
a3 = -0.21325


o4 = pi/2
d4 = 0.11235
a4 = 0


o5 = -pi/2
d5 = 0.08535
a5 = 0


o6 = 0
d6 = 0.0819
a6 = 0


A1 = np.array([
    [cos(th1),  -sin(th1) * cos(o1),     sin(th1) * sin(o1),     a1 * cos(th1)], 
    [sin(th1),   cos(th1) * cos(o1),    -cos(th1) * sin(o1),     a1 * sin(th1)],
    [0       ,   sin(o1),               cos(o1),                 d1           ],
    [0       ,   0,                     0,                       1            ]           
])


A2 = np.array([
    [cos(th2),  -sin(th2) * cos(o2),     sin(th2) * sin(o2),     a2 * cos(th2)], 
    [sin(th2),   cos(th2) * cos(o2),    -cos(th2) * sin(o2),     a2 * sin(th2)],
    [0       ,   sin(o2),               cos(o2),                 d2           ],
    [0       ,   0,                     0,                       1            ]           
])


A3 = np.array([
    [cos(th3),  -sin(th3) * cos(o3),     sin(th3) * sin(o3),     a3 * cos(th3)], 
    [sin(th3),   cos(th3) * cos(o3),    -cos(th3) * sin(o3),     a3 * sin(th3)],
    [0       ,   sin(o3),               cos(o3),                 d3           ],
    [0       ,   0,                     0,                       1            ]           
])


A4 = np.array([
    [cos(th4),  -sin(th4) * cos(o4),     sin(th4) * sin(o4),     a4 * cos(th4)], 
    [sin(th4),   cos(th4) * cos(o4),    -cos(th4) * sin(o4),     a4 * sin(th4)],
    [0       ,   sin(o4),               cos(o4),                 d4           ],
    [0       ,   0,                     0,                       1            ]           
])


A5 = np.array([
    [cos(th5),  -sin(th5) * cos(o5),     sin(th5) * sin(o5),     a5 * cos(th5)], 
    [sin(th5),   cos(th5) * cos(o5),    -cos(th5) * sin(o5),     a5 * sin(th5)],
    [0       ,   sin(o5),               cos(o5),                 d5           ],
    [0       ,   0,                     0,                       1            ]           
])


A6 = np.array([
    [cos(th6),  -sin(th6) * cos(o6),     sin(th6) * sin(o6),     a6 * cos(th6)], 
    [sin(th6),   cos(th6) * cos(o6),    -cos(th6) * sin(o6),     a6 * sin(th6)],
    [0       ,   sin(o6),               cos(o6),                 d6           ],
    [0       ,   0,                     0,                       1            ]           
])




T2 = A1 @ A2
T3 = A1 @ A2 @ A3
T4 = A1 @ A2 @ A3 @ A4
T5 = A1 @ A2 @ A3 @ A4 @ A5
T6 = A1 @ A2 @ A3 @ A4 @ A5 @ A6


z0= np.array([0,0,1])
z1 = A1[:3,2]
z2 = T2[:3,2] 
z3 = T3[:3,2] 
z4 = T4[:3,2] 
z5 = T5[:3,2]


p0 = np.array([0,0,0])
p1 = A1[:3,3]
p2 = T2[:3,3]
p3 = T3[:3,3]
p4 = T4[:3,3]
p5 = T5[:3,3]


P = T6[:3,3]
#雅可比矩阵
J = Matrix([
    np.append(np.cross(z0, P - p0), z0),
    np.append(np.cross(z1, P - p1), z1),
    np.append(np.cross(z2, P - p2), z2),
    np.append(np.cross(z3, P - p3), z3),
    np.append(np.cross(z4, P - p4), z4),
    np.append(np.cross(z5, P - p5), z5),
]).T


f = lambdify('th1, th2, th3, th4, th5, th6', J, 'numpy')


dill.settings['recurse'] = True 
dill.dump(f, open("testik", "wb"))

三、Levenberg-Marquardt 迭代算法文献

63bbbae77c369071a918783a0d4f2c0a.jpeg

9cf1e835ca70e8255125aabfaa393566.jpeg

f2c6f410a9a2138e9ee966ec35d4c3bf.jpeg

a8dc110e2d640afe79a2cf7ffcfeb307.jpeg

6f718ff3f4bbe297cd6289ab6f4622e3.jpeg

21a795b3c06bbcb70a880009df01e4bf.jpeg

The End

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值