栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

手眼标定(opencv实现)

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

手眼标定(opencv实现)

0.背景介绍

在机械臂与摄像头进行协同工作时,为了使得相机(亦即机器人的眼)与机器人(亦即机器人的手)坐标系之间建立关系就必须要对机器人与相机坐标系进行标定,该标定过程也就叫做手眼标定。

由于机器人是运动的,因此标定的主要是相对不变的量,大概分为以下两种:

    对于eye-to-hand情况,机器人手眼标定即标定得到机器人基座与相机之间的坐标变换关系。对于eye-in-hand情况,机器人手眼标定即标定得到机器人末端与相机之间的坐标变换关系;

1.坐标系变换

如图,空间中有一个点P,以及三个坐标系A,B,C,P在A,B,C坐标系下点对应的坐标分别是a,b,c。那么有如下运算关系:

    ,M是变换矩阵,R是3x3旋转矩阵,T是3x1平移矩阵,。因此有。通过这个公式可以看出,要反过来的话,只需要求一个逆即可

 2.手眼标定公式推导

对机械臂来说一共有四个坐标系。基底(base)、末端(end)、相机(camera)、标定板(board) 眼在手内

眼在手外

总结

无论是眼在手上还是眼在手外,最终都可以化解成 AX=XB,的形式。拍摄N张图片则可以得到N-1个方程。(一般要n>10效果好点)问题变转化成先去求N-1个A,B,再通过A,B联立解得X两种的方程区别不大,A都是类似的,不过要主要的是B。一个是end to base,一个是base to end。不过问题也不大,参考前面的推导,其实也就是求一个逆而已同理,求得的X进行求逆,就可以得到坐标系变换的逆变换

3.X求解

cv.calibrateHandEye()是核心的函数,该函数很适合眼在手上的标定,之后的项目都是眼在手上的标定,因此以下都是眼在手上的说明:

输入参数一:R_gripper2base:   输入参数二: R_target2cam:

输出:R_cam2gripper:

 因此,只需要把N对参数作为输入,则可以得到标定

求解参数一

、可以由机械臂内部直接得到。(位姿一般为6个参数,。通过这六个参数便能得到)

求解参数二

objectpoints:以棋盘格左上角为原点建立,由于棋盘格大小已知,因此可以建立

imagepoints:用cv2.findchessboardCorner函数找到

(真实点与图像点是一一对应的关系)

相机内参、相机畸变:这个由说明手册提供或者自己进行相机标定

代码实现
import cv2
import numpy as np
import glob
from math import *
import pandas as pd
import os
import json

K = np.array([[1373.5709486833512, 0.0, 976.1956272790611],
              [0.0, 1384.2380687124548, 670.6014863714781],
              [0, 0, 1]], dtype=np.float64)  # 内参

dist = np.array(
    [0.00968424149204635, 0.025415217557397604, 0.0009639920437816544, -0.00046553191699270647, -0.01217641000574228])

chess_board_x_num = 11  # 棋盘格x方向格子数
chess_board_y_num = 8  # 棋盘格y方向格子数
chess_board_len = 30  # 单位棋盘格长度,mm

global retval


# 用于根据欧拉角计算旋转矩阵
def myRPY2R_robot(x, y, z):
    Rx = np.array([[1, 0, 0], [0, cos(x), -sin(x)], [0, sin(x), cos(x)]])
    Ry = np.array([[cos(y), 0, sin(y)], [0, 1, 0], [-sin(y), 0, cos(y)]])
    Rz = np.array([[cos(z), -sin(z), 0], [sin(z), cos(z), 0], [0, 0, 1]])
    R = Rz @ Ry @ Rx
    return R


# 用于根据位姿计算变换矩阵(end to base)
def pose_robot(x, y, z, Tx, Ty, Tz):
    thetaX = x / 180 * pi
    thetaY = y / 180 * pi
    thetaZ = z / 180 * pi
    R = myRPY2R_robot(thetaX, thetaY, thetaZ)
    t = np.array([[Tx], [Ty], [Tz]])
    RT1 = np.column_stack([R, t])  # 列合并
    RT1 = np.row_stack((RT1, np.array([0, 0, 0, 1])))
    # RT1=np.linalg.inv(RT1)
    return RT1


# 用来从棋盘格图片得到相机外参(board to camera)
def get_RT_from_chessboard(img_path, chess_board_x_num, chess_board_y_num, K, chess_board_len,dist):
    global tvec
    '''
    :param img_path: 读取图片路径
    :param chess_board_x_num: 棋盘格x方向格子数
    :param chess_board_y_num: 棋盘格y方向格子数
    :param K: 相机内参
    :param chess_board_len: 单位棋盘格长度,mm
    :return: 相机外参
    '''
    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    size = gray.shape[::-1]
    ret, corners = cv2.findChessboardCorners(gray, (chess_board_x_num, chess_board_y_num), None)

    corner_points = np.zeros((2, corners.shape[0]), dtype=np.float64)
    for i in range(corners.shape[0]):
        corner_points[:, i] = corners[i, 0, :]
    object_points = np.zeros((3, chess_board_x_num * chess_board_y_num), dtype=np.float64)
    flag = 0
    for i in range(chess_board_y_num):
        for j in range(chess_board_x_num):
            object_points[:2, flag] = np.array([(11 - j - 1) * chess_board_len, (8 - i - 1) * chess_board_len])
            flag += 1
    retval, rvec, tvec = cv2.solvePnP(object_points.T, corner_points.T, K, distCoeffs=dist)
    RT = np.column_stack(((cv2.Rodrigues(rvec))[0], tvec))
    RT = np.row_stack((RT, np.array([0, 0, 0, 1])))
    return RT


def set_setting_file(path):
    try:
        with open(path) as json_file:
            setting_dict_string = json_file.readline()[:-2]
        return json.loads(setting_dict_string)
    except:
        return {}


def read_xyz_Rxyz(good_picture, folder, ele_data_dict={}):
    """用于读取每个图片对应的json文件中,对应的"""

    for i in good_picture:
        ele_data_dict[i] = set_setting_file(folder + '/' + str(i) + '.json')

    return ele_data_dict


folder = r"save_cail_data"  # 棋盘格图片存放文件夹
'''
这个地方很奇怪的特点,有些棋盘格点检测得出来,有些检测不了,可以通过函数get_RT_from_chessboard的运行时间来判断
'''
good_picture = [3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14]  # 存放可以检测出棋盘格角点的图片
ele_data_dict = read_xyz_Rxyz(good_picture, folder)

file_num = len(good_picture)

# 计算board to cam 变换矩阵
R_all_chess_to_cam_1 = []
T_all_chess_to_cam_1 = []
for i in good_picture:
    image_path = folder + '/' + str(i) + '.bmp'
    RT = get_RT_from_chessboard(image_path, chess_board_x_num, chess_board_y_num, K, chess_board_len, dist)

    # RT=np.linalg.inv(RT)
    R_all_chess_to_cam_1.append(RT[:3, :3])
    T_all_chess_to_cam_1.append(RT[:3, 3].reshape((3, 1)))


# 计算end to base变换矩阵
R_all_end_to_base_1 = []
T_all_end_to_base_1 = []
for i in good_picture:
    RT = pose_robot(ele_data_dict[i]["x"], ele_data_dict[i]["y"], ele_data_dict[i]["z"],
                    ele_data_dict[i]["Rx"], ele_data_dict[i]["Ry"], ele_data_dict[i]["Rz"])
    R_all_end_to_base_1.append(RT[:3, :3])
    T_all_end_to_base_1.append(RT[:3, 3].reshape((3, 1)))

print("n")
R, T = cv2.calibrateHandEye(R_all_end_to_base_1, T_all_end_to_base_1, R_all_chess_to_cam_1,
                            T_all_chess_to_cam_1)  # 手眼标定
print("手眼矩阵分解得到的旋转矩阵")
print(R)
print("n")

print("手眼矩阵分解得到的平移矩阵")
print(T)

RT = np.column_stack((R, T))
RT = np.row_stack((RT, np.array([0, 0, 0, 1])))  # 即为cam to end变换矩阵
print("n")
print('相机相对于末端的变换矩阵为(cam to end):')
print(RT)

# # 结果验证,原则上来说,每次结果相差较小
# for i in range(len(good_picture)):
#     # 得到机械手末端到基座的变换矩阵,通过机械手末端到基座的旋转矩阵与平移向量先按列合并,然后按行合并形成变换矩阵格式
#     RT_end_to_base = np.column_stack((R_all_end_to_base_1[i], T_all_end_to_base_1[i]))
#     RT_end_to_base = np.row_stack((RT_end_to_base, np.array([0, 0, 0, 1])))
#     # print(RT_end_to_base)
#
#     # 标定版相对于相机的齐次矩阵
#     RT_chess_to_cam = np.column_stack((R_all_chess_to_cam_1[i], T_all_chess_to_cam_1[i]))
#     RT_chess_to_cam = np.row_stack((RT_chess_to_cam, np.array([0, 0, 0, 1])))
#     # print(RT_chess_to_cam)
#
#     # 手眼标定变换矩阵
#     RT_cam_to_end = np.column_stack((R, T))
#     RT_cam_to_end = np.row_stack((RT_cam_to_end, np.array([0, 0, 0, 1])))
#     # print(RT_cam_to_end)
#
#     # 即为固定的棋盘格相对于机器人基坐标系位姿
#
#     RT_chess_to_base = RT_end_to_base @ RT_cam_to_end @ RT_chess_to_cam
#     print('第', i, '次')
#     print(RT_chess_to_base)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/755000.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号