0


机器人动起来1:机械臂手眼标定、像素-世界坐标系转换

    机械臂要想到达期望的位置,必须将其感知系统和机械臂运动产生联系,这关键的两步就是手眼标定和坐标系转换。按我所讲的步骤进行调试一定可以成功。

1.手眼标定

    机械臂手眼标定目的是为了求得三个参数:机械臂末端位姿矩阵、末端与相机的变换矩阵以及相机到标定板的变换矩阵。其中,末端与相机的变换矩阵是求解的关键。机械臂的末端位姿矩阵可通过ROS订阅话题得出,相机到标定板的变换矩阵可通过外参标定得出,末端与相机的变换矩阵可通过AX=XB模型求出。

1.1 相机标定

    相机标定是手眼标定的最先应进行的工作,目的是为了获取相机的内外参数,畸变矩阵。相机标定不仅可以用于机械臂手眼标定,还可以用于多个相机间的校准、对齐,实现多模态图像配准。

    1.将标定板放置在距离机械臂一定距离处,距离不应太远。值得注意的是,标定板的横向格数一定不能等于纵向格数,否则不同图像中的同一个角点坐标并不对应,如下图为**错误案例**。

     2.将realsense相机稳定固定在机械臂末端,打开realsense-viewer,通过施教模式控制机械臂到达某个位置,依次在终端输入下面命令可订阅机械臂的位姿,记录此时的机械臂四元数矩阵和realsense拍摄的画面。
rosrun moveit_commander moveit_commander_cmdline.py
use <group name>
current
    3.将四元数转换为机械臂旋转矩阵,如下代码
from scipy.spatial.transform import Rotation as R
 
Rq=[-0.756325124972, 0.269649470864,-0.54434743131, 0.24279073752] # 四元数
Rm = R.from_quat(Rq) 
rotation_matrix = Rm.as_matrix() # 旋转矩阵
print('rotation:\n',rotation_matrix)
    4.将旋转矩阵和第2步得到的平移量记录为四行四列的齐次矩阵,并记录旋转矩阵和平移矩阵于RobotToolPose.csv中。

1.2 手眼标定

    原理讲解(不想看的可以跳过):

A:机器人末端在机械臂坐标系下的位姿,这其实就是机器人运动学正解的问题。(已知)。

B:相机在机器人末端坐标系下的位姿,这个变换是固定的,只要知道这个变换,我们就可以随时计算相机的实际位置,所以这就是我们想求的东西。(未知,待求)

C:相机在标定板坐标系下的位姿,这个其实就是求解相机的外参(已知)


    话不多说,直接上代码
# coding=utf-8
# copied by ysh in 2021/12/08
"""
用于相机标定和相机的手眼标定
A2^{-1}*A1*X=X*B2*B1^{−1}
"""

import os.path
import cv2
import numpy as np
np.set_printoptions(precision=8,suppress=True)
import glob

path = "D:/hand_eye_image/"
# 角点的个数以及棋盘格间距
XX = 8
YY = 6
L = 0.03 # 格子大小

# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)

# 获取标定板角点的位置
objp = np.zeros((XX * YY, 3), np.float32)
objp[:, :2] = np.mgrid[0:XX, 0:YY].T.reshape(-1, 2)     # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
objp = L*objp

obj_points = []     # 存储3D点
img_points = []     # 存储2D点

images = glob.glob('{}/*.png'.format(path))
print(images)
i = 0
for fname in images:
    print(fname)
    img = cv2.imread(fname)
    # cv2.imshow('img',img)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    size = gray.shape[::-1]
    ret, corners = cv2.findChessboardCorners(gray, (XX, YY), None)
    print(ret)

    if ret:

        obj_points.append(objp)

        corners2 = cv2.cornerSubPix(gray, corners, (3, 3), (-1, -1), criteria)  # 在原角点的基础上寻找亚像素角点
        #print(corners2)
        if [corners2]:
            img_points.append(corners2)
        else:
            img_points.append(corners)

        cv2.drawChessboardCorners(img, (XX, YY), corners, ret)
        # 红色为第一个点,蓝色为最后一个点,先X轴再Y轴
        cv2.imwrite('{}/figure_save/{}.png'.format(path,i), img)
        i = i+1
        # cv2.imshow('img', img)
        # cv2.waitKey(2000)

N = len(img_points)
print(f'图像个数:{N}')
# cv2.destroyAllWindows()

# 标定,得到图案在相机坐标系下的位姿
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, size, None, None)

# print("ret:", ret)
print("内参矩阵:\n", mtx) # 内参数矩阵
print("畸变系数:\n", dist)  # 畸变系数   distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("rvecs:\n", rvecs)  # 旋转向量  # 外参数
print("tvecs:\n", tvecs ) # 平移向量  # 外参数

print("-----------------------------------------------------")

# img2 = cv2.imread(f'{path}/figure/*.jpg')
i = 0
for fname in images:
    figure = cv2.imread(fname)
    h,  w = figure.shape[:2]
    newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),0,(w,h)) # 自由比例参数,用于去除畸变矫正后图像四周黑色的区域。alpha=0,则去除所有黑色区域,alpha=1,则保留所有原始图像像素,其他值则得到介于两者之间的效果。
    dst = cv2.undistort(figure, mtx, dist, None, newcameramtx)
    cv2.imwrite('{}/figure_undist/{}.png'.format(path,i), dst)
    i = i + 1

# 机器人末端在基座标系下的位姿
tool_pose = np.loadtxt('{}/RobotToolPose.csv'.format(path),delimiter=',',encoding='utf-8-sig')
R_tool = []
t_tool = []
for i in range(int(N)):
    R_tool.append(tool_pose[0:3,4*i:4*i+3])
    t_tool.append(tool_pose[0:3,4*i+3])

R, t = cv2.calibrateHandEye(R_tool, t_tool, rvecs, tvecs, cv2.CALIB_HAND_EYE_TSAI) # R_tool, t_tool手爪相对于机器人基坐标系的旋转矩阵与平移向量;rvecs, tvecs标定板相对于双目相机的齐次矩阵
T_tool_camera = np.hstack((R, t)) # R,T就是相机到机械臂末端的旋转偏移矩阵
T_tool_camera = np.vstack((T_tool_camera, np.array([0,0,0,1])))
print(f'相机在机器人末端坐标系的位姿:\n{T_tool_camera}')

with open('{}/camera.txt'.format(path), 'w') as f:
    f.write(f'{mtx}\n') # 内参矩阵
    f.write(f'{dist}\n') # 畸变矩阵
    f.write(f'{T_tool_camera}') # 相机到机械臂的外参矩阵
    需要自己调整的参数有:标定板分别在XX,YY角点数量,标定板单位格的宽(高)L,存储地址path,机器人位姿RobotToolPose.csv。最后可以得到相机内参矩阵mtx,相机到机械臂的外参矩阵T_tool_camera。

    附:手眼标定原理讲解http://t.csdn.cn/KoFM9

2. 像素-世界坐标系转换

    完成手眼标定后,我们手中就有了相机内参矩阵mtx,相机到机械臂的外参矩阵T_tool_camera。接下来就是最后一步:坐标系转换。

    原理讲解(不想看的可以跳过):

    (1)顺序变换

** 世界坐标系-相机坐标系**

            ![](https://img-blog.csdnimg.cn/2294248d5a9e4a40878b6d9c7f7fe17b.png)(1)

** 相机坐标系-像素坐标系**

            ![](https://img-blog.csdnimg.cn/0cf5d2b1991e4b1890a34ad250452dd5.png)                                    (2)

            KI是内参矩阵,xc,yc,zc为相机坐标系坐标,u,v是像素坐标系坐标

** 像素坐标系到世界坐标系**

    ![](https://img-blog.csdnimg.cn/3550c44349f24ec3a98ebef49df8b002.png)(3)

            u,v是像素坐标系坐标,xw yw zw是世界坐标系坐标,等式右第一个矩阵是内参矩阵,第二个矩阵是外参矩阵。但是不能直接乘,因为涉及到求得3*4矩阵无法求逆矩阵,所以不能直接用![](https://img-blog.csdnimg.cn/659f8644e4444ec3b743e047bd4b2896.png),而是要用![](https://img-blog.csdnimg.cn/bb64180037524bac9c255385620482ab.png)。

               将(1)变形:

            ![](https://img-blog.csdnimg.cn/3b6ab5095cea42af93cdbeede9d8c0b2.png)                                                                (4)

                将(2)带入(4):

            ![](https://img-blog.csdnimg.cn/7b239484d5ca426f8cdea92ebadb468e.png)

** (2)直接变换**

    其中,XwYwZw为世界坐标系坐标,R为外参中的旋转矩阵,KI为内参矩阵,Zc为相机坐标系中的深度值,u,v为像素坐标

            ![](https://img-blog.csdnimg.cn/7b239484d5ca426f8cdea92ebadb468e.png)

话不多说,直接上代码

import numpy as np
matrixHand2Camera = np.array([[ 0.56186877, -0.82718502, -0.00827197, 0.00665606],
                            [ 0.82722097,0.56180082, 0.00923615, -0.0420225],
                            [-0.00299281 , -0.01203225, 0.99992313, 0.02549419],
                            [ 0,          0,          0,          1         ]]) # 手眼矩阵THC

matrixBase2Hand = np.array([[0.26195007,    -0.14356031, 0.95434407, 0.319418241631],
                            [-0.67221037, -0.73668364, 0.07369148,    -0.010993728332],
                            [0.69247049, -0.66082346, -0.28947706, 0.689715275419],
                            [0,           0,          0,          1         ]]) # 末端姿态TBH

matrixCamera2Pixel = np.array([[922.78685321,   0,            657.06183115],
                             [  0,             923.92329954, 366.06665478],
                             [  0,             0,              1         ]]) # 内参

matrixBase2Camera = np.dot(matrixBase2Hand,matrixHand2Camera)
matrixCamera2Base = np.linalg.inv(matrixBase2Camera)

zc = 0.495
u = 801
v = 452

# 直接变换
outputBase2 = np.dot(np.linalg.inv(matrixCamera2Base[0:3,0:3]),zc*np.dot(np.linalg.inv(matrixCamera2Pixel),np.array([u,v,1]).reshape(3,1))-matrixCamera2Base[:3,3].reshape(3,1))
print("直接变换",outputBase2)

** 根据上述代码,即可求得像素坐标系一点在机械臂基坐标系下的位置,到此就完成了视觉系统感知+机械臂运动一体化流程。**

** 值得注意的是,**matrixBase2Camera要先求逆才可带入中,因为公式中的R和T实际为基坐标相对于相机的位置,而matrixBase2Camera为相机相对于基坐标位姿,根据,求得基坐标相对于相机的位置matrixCamera2Base。

    附:坐标系转换http://t.csdn.cn/r4Pwq

本文转载自: https://blog.csdn.net/road_of_god/article/details/126891153
版权归原作者 road_of_god 所有, 如有侵权,请联系我们删除。

“机器人动起来1:机械臂手眼标定、像素-世界坐标系转换”的评论:

还没有评论