赞
踩
介绍之前进行变量定义说明:对于 Eye-in-hand 手眼标定方式,需要求解工业机器人的末端坐标系与相机坐标系之间的坐标转换关系。 Eye-in-hand 手眼标定的原理示意图如图 1所示。这其中有几个坐标系, 基础坐标系(用 base 表示) 是机器臂的基底坐标系,末端坐标系(用 end 表示) 是机器臂的末端坐标系, 相机坐标系(用 cam 表示) 是固定在机器臂上面的相机自身坐标系,标定物坐标系(用 cal 表示)是标定板所在的坐标系。任意移动两次机器臂,由于标定板和机器臂的基底是不动的,因此对于某个世界点,其在 base 坐标系和 cal 坐标系下的坐标值不变,在 end 坐标系和 cam 坐标系下的坐标值随着机器臂的运动而改变。根据这一关系,可以求解出end坐标系和 cam 坐标系之间的转换矩阵。具体求解过程如下:
{b}: base基坐标系
{g}: end夹具坐标系
{t}: cal标定板坐标系
{c}: camera相机坐标系
calibrateHandeye()
参数描述如下:R_gripper2base,t_gripper2base是机械臂抓手相对于机器人基坐标系的旋转矩阵与平移向量,需要通过机器人运动控制器或示教器读取相关参数转换计算得到;R_target2cam, t_target2cam 是标定板相对于相机的齐次矩阵,在进行相机标定时可以求取得到(calibrateCamera()得到),也可以通过solvePnP()单独求取相机外参数获得 ; R_cam2gripper ,t_cam2gripper是求解的手眼矩阵分解得到的旋转矩阵与平移矩阵;OpenCV实现了5种方法求取手眼矩阵 , Tsai两步法速度最快。
cd ~
mkdir paizhao/src
cd paizhao/src
运行:
ros2 pkg create python_img --build-type ament_python --dependencies rclpy cv_bridge sensor_msgs vision_msgs cv2 --license Apache-2.0 --node-name img_huoqu
会出现以上的功能包python_img
cd ~/paizhao/src/python_img/python_img
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
class ImageListener(Node):
def __init__(self):
super().__init__('image_listener')
self.subscription = self.create_subscription(
Image,
'camera/color/image_raw',
self.listener_callback,
10)
self.subscription # prevent unused variable warning
self.bridge = CvBridge()
self.count = 0
def listener_callback(self, data):
self.get_logger().info('Receiving video frame')
cv_image = self.bridge.imgmsg_to_cv2(data, desired_encoding='bgr8')
# 显示图像
cv2.imshow("Image Window", cv_image)
# 按下“t”键保存图像
if cv2.waitKey(1) & 0xFF == ord('t'):
self.count += 1
filename = f'image_{self.count}.bmp'
cv2.imwrite(filename, cv_image)
self.get_logger().info(f'Image saved: {filename}')
def main(args=None):
rclpy.init(args=args)
image_listener = ImageListener()
rclpy.spin(image_listener)
# 销毁窗口
cv2.destroyAllWindows()
# 关闭节点
image_listener.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
cd ~/paizhao
colcon build
cd ~/paizhao
. ./install/setup.bash
ros2 run python_img img_huoqu
import cv2
import numpy as np
def find_chessboard_corners(image, pattern_size):
"""
在图像中查找棋盘格的角点。
:param image: 要检测的棋盘格图像。
:param pattern_size: 棋盘格的内部角点数量,格式为(row, col)。
:return: 是否找到角点,角点列表。
"""
# 将图像转换为灰度
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray',gray)
cv2.waitKey(0)
# 寻找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
corners_subpixel = None
# 如果找到了足够的角点,将其细化并绘制
if ret:
# 获取亚像素精度的角点位置
corners_subpixel = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1))
# 绘制角点
cv2.drawChessboardCorners(image, pattern_size, corners_subpixel, ret)
return ret, corners_subpixel
# 使用示例
if __name__ == '__main__':
# 加载图像
image = cv2.imread('/home/zy/temp/image_3.bmp')
# cv2.imshow('first', image)
# cv2.waitKey(0)
# 检查图像是否成功加载
if image is None:
print("Could not open or find the image.")
exit()
# 设置棋盘格的内部角点数量,例如一个10x7的棋盘格
pattern_size = (7, 10)
# 查找并绘制角点
found, corners = find_chessboard_corners(image, pattern_size)
if found:
print("Chessboard corners found.")
print(corners.shape)
cv2.imshow('Chessboard Corners', image)
key = cv2.waitKey(0)
if key & 0xff == ord('q'):
cv2.destroyAllWindows()
else:
print("Chessboard corners not found.")
# cv2.destroyAllWindows()
import cv2
import numpy as np
import glob
from math import *
import pandas as pd
import os
K = np.array([[360.2007751464844, 0, 323.737548828125],
[0, 360.2007751464844, 176.65692138671875],
[0, 0, 1]], dtype=np.float64) #大恒相机内参
chess_board_x_num = 7 #棋盘格x方向格子数
chess_board_y_num = 10 #棋盘格y方向格子数
chess_board_len = 10 #单位棋盘格长度,mm
#用于根据欧拉角计算旋转矩阵
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
#用于根据位姿计算变换矩阵
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
#用来从棋盘格图片得到相机外参
def get_RT_from_chessboard(img_path, chess_board_x_num, chess_board_y_num, K, chess_board_len):
'''
: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)
# print(corners)
corner_points = np.zeros((2, corners.shape[0]), dtype=np.float64)
for i in range(corners.shape[0]):
corner_points[:, i] = corners[i, 0, :]
# print(corner_points)
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
# print(object_points)
retval, rvec, tvec = cv2.solvePnP(object_points.T, corner_points.T, K, distCoeffs=None)
# print(rvec.reshape((1,3)))
# RT=np.column_stack((rvec,tvec))
RT = np.column_stack(((cv2.Rodrigues(rvec))[0], tvec))
RT = np.row_stack((RT, np.array([0, 0, 0, 1])))
# RT=pose(rvec[0,0],rvec[1,0],rvec[2,0],tvec[0,0],tvec[1,0],tvec[2,0])
# print(RT)
# print(retval, rvec, tvec)
# print(RT)
# print('')
return RT
folder = "/home/zy/temp" #棋盘格图片存放文件夹
# files = os.listdir(folder)
# file_num=len(files)
# RT_all=np.zeros((4,4,file_num))
# print(get_RT_from_chessboard('calib/2.bmp', chess_board_x_num, chess_board_y_num, K, chess_board_len))
'''
这个地方很奇怪的特点,有些棋盘格点检测得出来,有些检测不了,可以通过函数get_RT_from_chessboard的运行时间来判断
'''
good_picture = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12] #存放可以检测出棋盘格角点的图片
# good_picture=[1,3,10,11,12]
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:
# print(i)
image_path = folder + '/image_' + str(i) + '.bmp'
RT = get_RT_from_chessboard(image_path, chess_board_x_num, chess_board_y_num, K, chess_board_len)
# 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)))
# print(T_all_chess_to_cam.shape)
#计算end to base变换矩阵
file_address = '/home/zy/temp/A.xlsx' #从记录文件读取机器人六个位姿
sheet_1 = pd.read_excel(file_address)
R_all_end_to_base_1 = []
T_all_end_to_base_1 = []
# print(sheet_1.iloc[0]['ax'])
good_picture_1=[0,1,2,3,4,5,6,7,8,9,]
for i in good_picture_1:
# print(sheet_1.iloc[i-1]['ax'],sheet_1.iloc[i-1]['ay'],sheet_1.iloc[i-1]['az'],sheet_1.iloc[i-1]['dx'],
# sheet_1.iloc[i-1]['dy'],sheet_1.iloc[i-1]['dz'])
RT = pose_robot(sheet_1.iloc[i - 1]['ax'], sheet_1.iloc[i - 1]['ay'], sheet_1.iloc[i - 1]['az'],
sheet_1.iloc[i - 1]['dx'],
sheet_1.iloc[i - 1]['dy'], sheet_1.iloc[i - 1]['dz'])
# RT=np.column_stack(((cv2.Rodrigues(np.array([[sheet_1.iloc[i-1]['ax']],[sheet_1.iloc[i-1]['ay']],[sheet_1.iloc[i-1]['az']]])))[0],
# np.array([[sheet_1.iloc[i-1]['dx']],
# [sheet_1.iloc[i-1]['dy']],[sheet_1.iloc[i-1]['dz']]])))
# RT = np.row_stack((RT, np.array([0, 0, 0, 1])))
# RT = np.linalg.inv(RT)
R_all_end_to_base_1.append(RT[:3, :3])
T_all_end_to_base_1.append(RT[:3, 3].reshape((3, 1)))
# print(R_all_end_to_base_1)
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) #手眼标定
RT = np.column_stack((R, T))
RT = np.row_stack((RT, np.array([0, 0, 0, 1]))) #即为cam to end变换矩阵
print('相机相对于末端的变换矩阵为:')
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 #即为固定的棋盘格相对于机器人基坐标系位姿
RT_chess_to_base = np.linalg.inv(RT_chess_to_base)
print('第', i, '次')
print(RT_chess_to_base[:3, :])
print('')
chess_board_x_num = 7 #棋盘格x方向格子数
chess_board_y_num = 10 #棋盘格y方向格子数
chess_board_len = 10 #单位棋盘格长度,mm
K = np.array([[360.2007751464844, 0, 323.737548828125],
[0, 360.2007751464844, 176.65692138671875],
[0, 0, 1]], dtype=np.float64) #相机内参
标定纸的规格(8*11)每个小单位大小是10mm之前说过
chess_board_x_num = 7 #棋盘格x方向格子数
chess_board_y_num = 10 #棋盘格y方向格子数
chess_board_len = 10 #单位棋盘格长度,mm
good_picture_1=[0,1,2,3,4,5,6,7,8,9,] #是你有多少行的excel的数据
/home/zy/anaconda3/envs/lableimg/bin/python3.8 /home/zy/ZY/Python/Temp/rt.py
相机相对于末端的变换矩阵为:
[[ 0.63862265 0.08529824 0.76477796 51.26101178]
[ 0.14605989 0.96233497 -0.22929875 -23.83712011]
[ -0.75553135 0.25813876 0.60211025 -16.68651879]
[ 0. 0. 0. 1. ]]
第 0 次
[[ 8.09165804e-01 5.32251040e-01 -2.48916719e-01 -1.28822056e+02]
[-6.77023672e-02 5.05263185e-01 8.60305471e-01 -9.99946717e+01]
[ 5.83666936e-01 -6.79277516e-01 4.44876347e-01 -3.93351031e+02]]
第 1 次
[[ 1.17769749e-01 7.87851505e-01 -6.04500035e-01 -8.46698165e+00]
[ 2.94972379e-01 5.53507106e-01 7.78858895e-01 -1.00539764e+02]
[ 9.48220218e-01 -2.70036830e-01 -1.67208040e-01 -3.94339956e+02]]
第 2 次
[[-5.71028866e-01 8.07200351e-01 -1.49511297e-01 5.86255553e+01]
[ 4.58459627e-01 4.64642871e-01 7.57576249e-01 -1.38566749e+02]
[ 6.80985172e-01 3.64053013e-01 -6.35393264e-01 -2.34357678e+02]]
第 3 次
[[-4.23638255e-01 5.12154207e-01 -7.47147038e-01 -5.31925717e+01]
[ 7.06147180e-01 7.03332427e-01 8.17291639e-02 1.14924972e+02]
[ 5.67350675e-01 -4.92972174e-01 -6.59614772e-01 -1.09600469e+02]]
第 4 次
[[ 4.95857214e-01 6.47857770e-01 -5.78278422e-01 1.24190440e+02]
[ 4.38150677e-01 3.88290772e-01 8.10712193e-01 2.54024309e+01]
[ 7.49766369e-01 -6.55370572e-01 -9.13225401e-02 -9.88927414e+01]]
第 5 次
[[ 8.12065360e-01 4.66192129e-01 -3.51019587e-01 1.59286158e+02]
[-5.91239090e-03 6.08049184e-01 7.93877341e-01 -3.24281884e+01]
[ 5.83536542e-01 -6.42604924e-01 4.96531990e-01 -1.48777560e+02]]
第 6 次
[[ 8.10598990e-01 2.97702419e-01 -5.04284193e-01 -1.21642319e+02]
[ 4.85286487e-01 1.40483574e-01 8.62995592e-01 -1.52217153e+02]
[ 3.27759521e-01 -9.44265659e-01 -3.05951176e-02 -2.64163592e+02]]
第 7 次
[[ 8.43703499e-01 -2.48462721e-01 -4.75847330e-01 -1.49385842e+02]
[ 5.25409896e-01 2.00506185e-01 8.26886758e-01 -1.63486422e+02]
[-1.10040202e-01 -9.47662148e-01 2.99712542e-01 -2.11411259e+02]]
第 8 次
[[ 9.04377761e-01 -1.29452604e-01 -4.06623767e-01 -1.64552215e+02]
[ 4.26356534e-01 3.14112838e-01 8.48264835e-01 -1.24995087e+02]
[ 1.79156536e-02 -9.40518553e-01 3.39269629e-01 -2.73727354e+02]]
第 9 次
[[ 7.68795493e-01 4.03889006e-01 -4.95809601e-01 -1.05512710e+02]
[ 2.02538477e-01 5.81619289e-01 7.87843365e-01 -7.70707092e+01]
[ 6.06573701e-01 -7.06110950e-01 3.65343224e-01 -3.61701280e+02]]
进程已结束,退出代码为 0
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。