添加手眼标定程序

This commit is contained in:
Ziwei.He 2025-05-26 17:10:13 +08:00
parent c265e99df3
commit 1d26d00175
6 changed files with 901 additions and 30 deletions

View File

@ -54,9 +54,9 @@ class AdmittanceController(BaseController):
self.state.pose_error[3:] = -rot_err_quat.as_rotvec(degrees=False)
# 期望加速度
wrench_err = self.state.external_wrench_tcp - self.state.desired_wrench
KD_vel = self.D @ (self.state.arm_desired_twist - self.state.desired_twist)
KD_pose = self.K @ self.state.pose_error
self.state.arm_desired_acc = self.M_inv @ (wrench_err - KD_vel - KD_pose)
D_vel = self.D @ (self.state.arm_desired_twist - self.state.desired_twist)
K_pose = self.K @ self.state.pose_error
self.state.arm_desired_acc = self.M_inv @ (wrench_err - D_vel - K_pose)
self.clip_command(self.state.arm_desired_acc, "acc")
## 更新速度和位姿
self.state.arm_desired_twist += self.state.arm_desired_acc * dt

View File

@ -812,33 +812,10 @@ if __name__ == "__main__":
sleep(1)
dobot.setEndEffector(i=9,tool_i="{0.0,0.0,154.071,0.0,0.0,0.0}")
dobot.chooseEndEffector(i=9)
# print(dobot.getPose())
# print(dobot.get_arm_position())
# pos,rot_quat = dobot.get_arm_position()
# print("pos",pos)
# print("rot_quat",rot_quat)
# while(True):
# print("反馈角度",dobot.feedbackData.QActual)
# print("反馈四元数",dobot.feedbackData.ActualQuaternion)
# print("反馈TCP笛卡尔实际坐标值",dobot.feedbackData.ToolVectorActual)
# sleep(0.008)
dobot.Get_feedInfo_angle()
sleep(0.008)
dobot.Get_feedInfo_pose()
sleep(0.5)
cur = dobot.Get_feedInfo_angle() # 参考关节角度
short_cur = [f"{x:.3f}" for x in cur]
target = dobot.getInverseKin([250.000045,-134.999537,344.115807,-179.999982,-0.000408,-90.000642],-1,-1,1,"{" + ",".join(map(str, short_cur)) + "}")
print(target)
# for i in range(400):
# posJ[2] += 0.04
# dobot.ServoJoint(posJ,t=0.008,aheadtime=20,gain=200)
# sleep(0.007)
print("Arm pose:",dobot.getPose())
# dobot.RunPoint_P_inPose([, 0, 154.071, 0, 0, 0])
# dobot.start_drag()
dobot.disableRobot()

View File

@ -0,0 +1,260 @@
import cv2
import os
from datetime import datetime
import numpy as np
import sys
from pathlib import Path
import time
from scipy.spatial.transform import Rotation as R
sys.path.append(str(Path(__file__).resolve().parent.parent))
sys.path.append('/home/jsfb/jsfb_ws/MassageRobot_aubo/Massage/MassageControl')
from hardware.remote_cam import ToolCamera
from hardware.aubo_C5 import AuboC5
np.set_printoptions(precision=8, suppress=True)
# 设置寻找亚像素角点的参数采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)
# 标定计算类
# -- 连接AuboC5
# -- 连接 ToolCamera
# -- 运动到初始位置 计算下一步运动位姿
# -- 开始手眼标定
# -- 输出结果
class Calibration:
def __init__(self, board_size, square_size, file_folder):
"""
初始化标定计算类
board_size: 标定板的格子数格式为 (width, height)
square_size: 格子的大小单位为米
"""
self.board_size = board_size
self.square_size = square_size
self.file_address = file_folder
self.objp = self._create_object_points()
self.image_points = []
self.robot_poses = []
self.cam = ToolCamera(host='127.0.0.1')
self.cam.start()
self.size = None
self.intrinsics_mtx = None
self.disorder = None
rgb_image, depth_image, camera_intrinsics = self.cam.get_latest_frame()
if rgb_image is None :
print(f'===============相机连接失败请检查并重试==============')
else:
print(f'===============相机连接成功==============')
self.size = rgb_image.shape[:2]
self.intrinsics = camera_intrinsics
self.arm = AuboC5()
self.arm.init()
self.obj_points = []
print(f'===============机械臂连接成功==============')
def _create_object_points(self):
"""创建标定板的3D点"""
objp = np.zeros((self.board_size[0] * self.board_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:self.board_size[0], 0:self.board_size[1]].T.reshape(-1, 2) * self.square_size
return objp
def collect_data(self):
#移动到初始位置
pose = [(90) * (np.pi / 180), 4.23 * (np.pi / 180), 124.33 * (np.pi / 180), 61.56 * (np.pi / 180), -89.61 * (np.pi / 180), 0 * (np.pi / 180)]
code = self.arm.move_joint(pose,4,wait=True)
if code == -1:
print("运动到拍照位置失败")
#拍照
self.check_corners()
#运动到下一个位置
# 目前只设计了44条轨迹
for i in range (0,45):
next_pose = self.get_next_pose_ugly(i)
self.arm.move_joint(next_pose, 4,wait=True)
if code == -1:
print("运动到拍照位置失败")
else:
self.check_corners()
def check_corners(self):
rgb, depth, intrinsics = self.cam.get_latest_frame()
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, self.board_size, None)
if ret:
robot_pose = self.get_pose_homomat()
self.add_sample(gray,corners,robot_pose)
print(f"robot_pose = {robot_pose}")
pic_name = self.file_address + "/"+str(len(self.image_points)) + ".jpg"
print(f"save path as: {pic_name}")
# 绘制检测到的角点 (用于可视化,可选)
cv2.drawChessboardCorners(rgb, self.board_size, corners, ret)
cv2.imwrite(pic_name, rgb)
return True
return False
def add_sample(self, gray, corners,robot_pose):
"""
添加标定样本
image: 捕获的图像
robot_pose: 机械臂的位姿格式为 [x, y, z, roll, pitch, yaw]
"""
# 检测棋盘角点
self.obj_points.append(self.objp)
corners2 = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) # 在原角点的基础上寻找亚像素角点
if [corners2]:
self.image_points.append(corners2)
else:
self.image_points.append(corners)
self.robot_poses.append(robot_pose)
def get_pose_homomat(self):
pose = self.arm.robot_rpc_client.getRobotInterface(self.arm.robot_name).getRobotState().getTcpPose()
x, y, z, rx, ry, rz = pose
r = R.from_euler('xyz', [rx, ry, rz])
rotation_matrix = r.as_matrix()
translation_vector = np.array([x, y, z]).reshape(3, 1)
homo_mat = np.eye(4) # 创建 4x4 单位矩阵
homo_mat[:3, :3] = rotation_matrix
homo_mat[:3, 3] = translation_vector.flatten()
return homo_mat
def camera_calib(self):
# print(f"size is {self.size}")
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(self.obj_points, self.image_points, (self.size[1], self.size[0]), None, None)
# print("rvecs,tvecs:",rvecs,tvecs)
if ret:
print("内参矩阵:\n", mtx) # 内参数矩阵
print("畸变系数:\n", dist) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("++++++++++相机标定完成++++++++++++++")
self.intrinsics_mtx = mtx
self.disorder = dist
else:
print("-----------相机标定失败--------------")
return rvecs, tvecs
def split_robot_pose(self):
"""
将包含 4x4 齐次矩阵的 robot_pose 列表拆分成 R_arm t_arm
Args:
robot_pose: 包含 4x4 齐次矩阵的 Python list
Returns:
R_arm: 旋转矩阵的 Python list, 每个元素是 3x3 numpy 数组
t_arm: 平移向量的 Python list, 每个元素是 3x1 numpy 数组
"""
R_arm = []
t_arm = []
for pose_matrix in self.robot_poses:
# # 1. 提取旋转矩阵 (3x3)
rotation_matrix = pose_matrix[:3, :3]
R_arm.append(rotation_matrix)
# R_arm.append(pose_matrix[:3])
# 2. 提取平移向量 (3x1)
translation_vector = pose_matrix[:3, 3] # 获取齐次矩阵的平移部分 (1x3 numpy 数组)
translation_vector = translation_vector.reshape(3, 1) # 转换为 3x1 的列向量
t_arm.append(translation_vector)
return R_arm, t_arm
def calibrate(self):
rvecs, tvecs = self.camera_calib()
r_arm, t_arm= self.split_robot_pose()
rvecs_array = np.array(rvecs)
tvecs_array = np.array(tvecs)
t_arma = np.array(t_arm)
r_arma = np.array(r_arm)
print(rvecs_array.shape)
print(tvecs_array.shape)
print(t_arma.shape)
print(r_arma.shape)
R, t = cv2.calibrateHandEye(r_arma, t_arma, rvecs_array, tvecs_array, cv2.CALIB_HAND_EYE_TSAI)
print("+++++++++++手眼标定完成+++++++++++++++")
return R, t ,self.intrinsics_mtx
#得到图片以后 根据pose判断方向
def get_next_pose_future(self):
return None
def get_next_pose_ugly(self,index):
poselist =[
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1267845650875392, -1.5116296651522887, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1791444426473692, -1.4592697875924587, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0220648099678793, -1.5116296651522887, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 0.9697049324080494, -1.4592697875924587, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 0.9173450548482196, -1.4069099100326288, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1267845650875392, -1.6163494202719486, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1791444426473692, -1.6687092978317786, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.2315043202071991, -1.7210691753916085, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.109331272567596, -1.4941763726323454, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1442378576074825, -1.424363202552572, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1791444426473692, -1.3545500324727988, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.2140510276872558, -1.2847368623930255, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.109331272567596, -1.633802712791892, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.1442378576074825, -1.7036158828716652, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0395181024878226, -1.4941763726323454, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.004611517447936, -1.424363202552572, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 0.9697049324080493, -1.3545500324727988, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 0.9347983473681627, -1.2847368623930255, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.5707963267948966, 0.06510078109938851, 2.16996785900455, 1.0395181024878226, -1.4941763726323454, 0.0],
[1.5707963267948966, 0.05637413483941686, 2.16996785900455, 1.004611517447936, -1.424363202552572, 0.0],
[1.5707963267948966, 0.047647488579445216, 2.16996785900455, 0.9697049324080493, -1.3545500324727988, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.562069680534925, 0.07382742735936015, 2.16996785900455, 1.0395181024878226, -1.4941763726323454, 0.0],
[1.5533430342749535, 0.07382742735936015, 2.16996785900455, 1.004611517447936, -1.424363202552572, 0.0],
[1.544616388014982, 0.07382742735936015, 2.16996785900455, 0.9697049324080493, -1.3545500324727988, 0.0],
[1.5358897417550104, 0.07382742735936015, 2.16996785900455, 0.9347983473681627, -1.2847368623930255, 0.0],
[1.5271630954950388, 0.07382742735936015, 2.16996785900455, 0.8998917623282761, -1.2149236923132523, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.579522973054868, 0.07382742735936015, 2.16996785900455, 1.109331272567596, -1.5988961277520053, 0.0],
[1.5882496193148397, 0.07382742735936015, 2.16996785900455, 1.1442378576074825, -1.633802712791892, 0.0],
[1.5969762655748112, 0.07382742735936015, 2.16996785900455, 1.1791444426473692, -1.6687092978317786, 0.0],
[1.6057029118347828, 0.07382742735936015, 2.16996785900455, 1.2140510276872558, -1.7036158828716652, 0.0],
[1.6144295580947543, 0.07382742735936015, 2.16996785900455, 1.2489576127271425, -1.7385224679115518, 0.0],
[1.5707963267948966, 0.07382742735936015, 2.16996785900455, 1.0744246875277093, -1.5639895427121187, 0.0],
[1.579522973054868, 0.07382742735936015, 2.16996785900455, 1.1267845650875392, -1.581442835232062, 0.0],
[1.5882496193148397, 0.07382742735936015, 2.16996785900455, 1.1791444426473692, -1.5988961277520053, 0.0],
[1.5969762655748112, 0.07382742735936015, 2.16996785900455, 1.2315043202071991, -1.6163494202719486, 0.0],
[1.6057029118347828, 0.07382742735936015, 2.16996785900455, 1.283864197767029, -1.633802712791892, 0.0],
[1.6144295580947543, 0.07382742735936015, 2.16996785900455, 1.336224075326859, -1.6512560053118353, 0.0]
]
return poselist[index]
if __name__ == '__main__':
# 标定板的width 对应的角点的个数 6
# 标定板的height 对应的角点的个数 3
calibration_board_size = [6,3]
calibration_board_square_size = 0.027 #unit - meter
systemPath = "/home/jsfb/Documents/"
now = datetime.now()
date_time_str = now.strftime("%d_%H_%M")
folder_name = f"calibration_data_{date_time_str}"
systemPath+=folder_name
os.makedirs(systemPath, exist_ok=True)
calib = Calibration(calibration_board_size,calibration_board_square_size,systemPath)
calib.collect_data()
R, t,intrin = calib.calibrate()
print("旋转矩阵:")
print(R)
print("平移向量:")
print(t)
print(f"内参: {intrin}")

View File

@ -0,0 +1,550 @@
import cv2
import os
from datetime import datetime
import math
import numpy as np
import sys
import random
from pathlib import Path
import time
from scipy.spatial.transform import Rotation as R
from scipy.interpolate import CubicSpline
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
sys.path.append(str(Path(__file__).resolve().parent.parent))
sys.path.append('/home/jsfb/jsfb_ws/MassageRobot_aubo/Massage/MassageControl')
from hardware.remote_cam import ToolCamera
from hardware.aubo_C5 import AuboC5
np.set_printoptions(precision=8, suppress=True)
# 设置寻找亚像素角点的参数采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)
# 标定计算类
# -- 连接AuboC5
# -- 连接 ToolCamera
# -- 运动到初始位置 计算下一步运动位姿
# -- 开始手眼标定
# -- 输出结果
class Calibration:
def __init__(self, board_size, square_size, file_folder):
"""
初始化标定计算类
board_size: 标定板的格子数格式为 (width, height)
square_size: 格子的大小单位为米
"""
self.board_size = board_size
self.square_size = square_size
self.objp = self._create_object_points()
self.image_points = []
self.robot_poses = []
self.poselist = []
self.cam = ToolCamera(host='127.0.0.1')
self.cam.start()
self.file_address = file_folder
rgb_image, depth_image, camera_intrinsics = self.cam.get_latest_frame()
cv2.imshow("rgb1",rgb_image)
cv2.waitKey(0)
if rgb_image is None :
print(f'===============相机连接失败请检查并重试==============')
else:
print(f'===============相机连接成功==============')
self.size = rgb_image.shape[:2]
self.intrinsics = camera_intrinsics
self.arm = AuboC5()
self.arm.init()
self.obj_points = []
print(f'===============机械臂连接成功==============')
def _create_object_points(self):
"""创建标定板的3D点"""
objp = np.zeros((self.board_size[0] * self.board_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:self.board_size[0], 0:self.board_size[1]].T.reshape(-1, 2) * self.square_size
return objp
def collect_data(self):
#移动到初始位置
# move = [(90) * (np.pi / 180), 4.23 * (np.pi / 180), 124.33 * (np.pi / 180), 61.56 * (np.pi / 180), -89.61 * (np.pi / 180), 0 * (np.pi / 180)]
move = [(89.74) * (np.pi / 180), 11.21 * (np.pi / 180), 97.12 * (np.pi / 180), 29.06 * (np.pi / 180), -89.61 * (np.pi / 180), 0 * (np.pi / 180)]
code = self.arm.move_joint(move,4,wait=True)
if code == -1:
print("运动到拍照位置失败")
# time.sleep(3)
#拍照
rgb, depth, intrinsics = self.cam.get_latest_frame()
cv2.imshow("rgb2",rgb)
cv2.waitKey(0)
if self.check_corners_add_sample() is False:
print("初始位置下没有找到标定板")
rgb, depth, intrinsics = self.cam.get_latest_frame()
pose = self.arm.robot_rpc_client.getRobotInterface(self.arm.robot_name).getRobotState().getTcpPose()
cv2.imshow("not right",rgb)
cv2.waitKey(0)
rgb, depth, intrinsics = self.cam.get_latest_frame()
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, self.board_size, None)
robot_pose = self.get_pose_homomat()
print(f"EE pose:{robot_pose}")
center = self.init_chessboard_center(rgb)
print(f"current center EE: {center}")
''' Z轴抖动 '''
# EE坐标系下的点
trajectory, rpy_values = self.generate_cyclic_trajectory_above_center(center)
self.visualization(center,trajectory,rpy_values)
homo_trajectory = np.hstack((trajectory, np.ones((len(trajectory), 1))))
tcp_pose = self.get_pose_homomat()
# 把生成的trajectory 移动到 世界坐标下
trajectory = (tcp_pose @ homo_trajectory.T).T[:, :3]
rpy_angles = []
for tool_point in trajectory:
vector_center_to_tool = tool_point - center
roll, pitch, yaw = self.get_roll_pitch_yaw(vector_center_to_tool)
rpy_angles.append((roll, pitch, yaw))
rpy_angles = np.array(rpy_angles)
pose = self.concatenate_path_and_rpy(trajectory,rpy_angles )
#世界坐标系下的中心
center_homo = np.append(center, 1).reshape(4, 1)
tcp_pose = self.get_pose_homomat()
center_world_homo = tcp_pose @ center_homo # 4x4 *4x1 =4x1
center_world = center_world_homo[:3].flatten()
print(f"world center: {center_world} \n top pose:\n {pose[:5]}")
lenOfResult = self.traversal_position_direction(pose)
''' Z轴抖动 '''
assert lenOfResult > 20, "没有收集到足够多的数据来完成精确的标定"
def generate_cyclic_trajectory_above_center(self,center, num_steps=12, x_amplitude=0.12, z_amplitude=0.20, frequency=4, min_z_offset=0.60,step_z=0.01):
"""
生成所有轨迹点都在中心点上方的周期性波动轨迹
"""
trajectory = []
rpy_values = []
y_constant = center[1]
x_values = np.linspace(-x_amplitude, x_amplitude, num_steps)
y_values = [center[1]+0.05,center[1]+0.15]
# y_values = [center[1]+0.05,center[1]+0.15,center[1]+0.30]
# y_values = [center[1]+0.15,center[1]+0.03,center[1]+0.06,center[1]+0.10,center[1]+0.15]
# y_values = [center[1],center[1],center[1]]
for i in range(len(y_values)):
y = y_values[i]
for x in x_values:
# 使用绝对值正弦函数确保 Z 始终在 center[2] 之上
z = z_amplitude * np.abs(np.sin(frequency * x * np.pi)) + center[2] + min_z_offset + step_z*i
tool_point = np.array([x + center[0], y, z])
trajectory.append(tool_point)
# 计算从中心点射向轨迹点的向量
vector_center_to_tool = tool_point - center
roll, pitch, yaw = self.get_roll_pitch_yaw(vector_center_to_tool)
rpy_values.append((roll, pitch, yaw))
print(f"above part center:{center}\n {trajectory[:5]}")
return np.array(trajectory), np.array(rpy_values)
def get_roll_pitch_yaw(self,vector):
"""
计算从原点指向给定向量的 roll, pitch, yaw (弧度).
假设 Z 轴向前Y 轴向上X 轴向右
"""
x, y, z = vector
roll = math.atan2(y, z)
pitch = math.atan2(-x, math.sqrt(y**2 + z**2))
yaw = 0 # 假设指向 tool 的方向没有偏航,或者根据实际需求调整
return roll, pitch, yaw
def concatenate_path_and_rpy(self,path, rpy_angles):
"""
Concatenates a list of positions [x, y, z] with a corresponding list of RPY angles [roll, pitch, yaw].
Args:
path (list of lists): A list where each inner list contains [x, y, z].
rpy_angles (list of lists or tuples): A list where each inner list or tuple contains [roll, pitch, yaw].
Returns:
list of lists: A new list where each inner list is a concatenation of the
corresponding position and RPY angles: [x, y, z, roll, pitch, yaw].
"""
if len(path) != len(rpy_angles):
raise ValueError("The lengths of the path and rpy_angles lists must be the same.")
combined_list = []
for i in range(len(path)):
position = path[i]
orientation = rpy_angles[i]
# combined = np.concatenate((position,orientation))
combined = np.hstack((position.flatten(), orientation))
combined_list.append(combined)
print(f"combined xyz|rpy :{combined}")
return combined_list
def check_corners_add_sample(self):
rgb, depth, intrinsics = self.cam.get_latest_frame()
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, self.board_size)
pic_name = self.file_address + "/"+str(len(self.image_points)) + ".jpg"
if ret:
robot_pose = self.get_pose_homomat()
self.add_sample(gray,corners,robot_pose)
print(f"save path as: {pic_name}")
# 绘制检测到的角点 (用于可视化,可选)
# cv2.drawChessboardCorners(rgb, self.board_size, corners, ret)
cv2.imwrite(pic_name, rgb)
return True
cv2.imwrite(pic_name, rgb)
return False
def add_sample(self, gray, corners,robot_pose):
"""
添加标定样本
image: 捕获的图像
robot_pose: 机械臂的位姿格式为 [x, y, z, roll, pitch, yaw]
"""
# 检测棋盘角点
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 在原角点的基础上寻找亚像素角点
# if self._validate_corners(corners2):
if [corners2]:
self.obj_points.append(self.objp)
self.image_points.append(corners2)
self.robot_poses.append(robot_pose)
print(f"insert {robot_pose}")
def get_pose_homomat(self):
pose = self.arm.robot_rpc_client.getRobotInterface(self.arm.robot_name).getRobotState().getTcpPose()
x, y, z, rx, ry, rz = pose
r = R.from_euler('xyz', [rx, ry, rz])
rotation_matrix = r.as_matrix()
translation_vector = np.array([x, y, z]).reshape(3, 1)
homo_mat = np.eye(4) # 创建 4x4 单位矩阵
homo_mat[:3, :3] = rotation_matrix
homo_mat[:3, 3] = translation_vector.flatten()
return homo_mat
def camera_calib(self):
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(self.obj_points, self.image_points, (self.size[1],self.size[0]) , None, None)
if ret:
print("内参矩阵:\n", mtx) # 内参数矩阵
print("畸变系数:\n", dist) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("++++++++++相机标定完成++++++++++++++")
else:
print("-----------相机标定失败--------------")
return rvecs, tvecs
def split_robot_pose(self):
"""
将包含 4x4 齐次矩阵的 robot_pose 列表拆分成 R_arm t_arm
Args:
robot_pose: 包含 4x4 齐次矩阵的 Python list
Returns:
R_arm: 旋转矩阵的 Python list, 每个元素是 3x3 numpy 数组
t_arm: 平移向量的 Python list, 每个元素是 3x1 numpy 数组
"""
R_arm = []
t_arm = []
for pose_matrix in self.robot_poses:
# 1. 提取旋转矩阵 (3x3)
rotation_matrix = pose_matrix[:3, :3]
R_arm.append(rotation_matrix)
# 2. 提取平移向量 (3x1)
translation_vector = pose_matrix[:3, 3] # 获取齐次矩阵的平移部分 (1x3 numpy 数组)
translation_vector = translation_vector.reshape(3, 1) # 转换为 3x1 的列向量
t_arm.append(translation_vector)
return R_arm, t_arm
def calibrate(self):
rvecs, tvecs = self.camera_calib()
r_arm, t_arm= self.split_robot_pose()
rvecs_array = np.array(rvecs)
tvecs_array = np.array(tvecs)
t_arma = np.array(t_arm)
r_arma = np.array(r_arm)
print(rvecs_array.shape)
print(tvecs_array.shape)
print(t_arma.shape)
print(r_arma.shape)
R, t = cv2.calibrateHandEye(r_arma, t_arma, rvecs_array, tvecs_array, cv2.CALIB_HAND_EYE_HORAUD)
print("+++++++++++手眼标定完成+++++++++++++++")
return R, t
def init_chessboard_center(self,image):
'''
返回的是EE坐标下的中点
'''
intrinsics = self.intrinsics
# 检测标定板获取初始位置
ret, corners = cv2.findChessboardCorners(image, self.board_size)
if not ret: raise ValueError("标定板未检测到")
# 计算标定板中心在相机坐标系中的位置
obj_pts = np.array([[i*self.square_size, j*self.square_size, 0] for i in range(self.board_size[0]) for j in range(self.board_size[1])], dtype=np.float32)
print(intrinsics)
camera_matrix = np.array([
[intrinsics['fx'], 0, intrinsics['cx']],
[0, intrinsics['fy'], intrinsics['cy']],
[0, 0, 1]
], dtype=np.float32)
dist_coeffs = np.array([
intrinsics['distortion_coeffs']['k1'],
intrinsics['distortion_coeffs']['k2'],
intrinsics['distortion_coeffs']['p1'],
intrinsics['distortion_coeffs']['p2'],
intrinsics['distortion_coeffs']['k3']
], dtype=np.float32) # 同样确保数据类型
print("obj_pts shape:", obj_pts.shape)
print("corners shape:", corners.shape)
ret, rvec, tvec = cv2.solvePnP(obj_pts, corners, camera_matrix, dist_coeffs)
# 这里我们假设标定板的初始位置是原点,没有旋转和平移
rvec_initial = np.zeros((3, 1), dtype=np.float32) # 初始旋转向量
tvec_initial = np.zeros((3, 1), dtype=np.float32) # 初始平移向量
# 初始变换(标定板到初始坐标系)
initial_transform = np.eye(4)
initial_transform[:3, :3], _ = cv2.Rodrigues(rvec_initial)
initial_transform[:3, 3] = tvec_initial.flatten()
print(f"board itselt {initial_transform}")
# PnP 结果(初始坐标系到相机)
cam2initial = np.eye(4)
cam2initial[:3, :3], _ = cv2.Rodrigues(rvec)
cam2initial[:3, 3] = tvec.flatten()
# 合成最终变换:标定板 -> 初始坐标系 -> 相机
board2cam = initial_transform @ cam2initial
print(f"board2cam\n{board2cam}")
# 手眼标定矩阵的逆相机到TCP的固定变换
cam2tool = np.eye(4)
cam2tool[:3, :3] = np.array([[0.0, 1.0 , 0.0],
[1.0 , 0.0 , 0.0],
[0.0, 0.0 , -1.0]])
cam2tool[:3, 3] = np.array([0.00209053, 0.01021561, -0.16877656]).flatten()
###找到棋盘格中心
camera_center = None
chessboard_center_object = self.get_chessboard_center_object_points()
if chessboard_center_object is not None:
# Convert to homogeneous coordinates
chessboard_center_homogeneous = np.append(chessboard_center_object, 1)
# Transform the center to camera coordinates
camera_center_homogeneous = cam2tool@ board2cam @ chessboard_center_homogeneous
camera_center = camera_center_homogeneous[:3]
return camera_center
def get_chessboard_center_object_points(self):
rows, cols = self.board_size
obj_pts = self.objp
if obj_pts is None or len(obj_pts) != rows * cols:
return None
# Get the range of x and y indices
x_indices = obj_pts[:, 0].flatten()
y_indices = obj_pts[:, 1].flatten()
center_x_index = (np.max(x_indices) + np.min(x_indices)) / 2.0
center_y_index = (np.max(y_indices) + np.min(y_indices)) / 2.0
center_z = 0 # Assuming the chessboard is on the Z=0 plane in its frame
return np.array([center_x_index, center_y_index, center_z])
def generate_perturbed_joint_angles_rad(self,original_angle):
"""
Args:
original_angle (list or tuple): 包含6个角度值的列表或元组 (单位).
Returns:
numpy.ndarray: 一个形状为 (6, 6) numpy 数组包含6组6个角度值单位弧度
每组数据的含义如下
- 第1组: 原始角度转换为弧度
- 第2组: 第五个关节角度 + rand5转换为弧度
- 第3组: 第五个关节角度 - rand5转换为弧度
- 第4组: 第四个关节角度 + rand4转换为弧度
- 第5组: 第四个关节角度 - rand4转换为弧度
- 第6组: 第四个关节角度 + rand4第五个关节角度 + rand5转换为弧度
"""
original_angle = np.array(original_angle)
if original_angle.shape != (6,):
raise ValueError("输入的角度列表必须包含6个元素。")
original_angle[5] = 0.0
# 生成随机数
rand5 = random.uniform(-20*np.pi/180, 20*np.pi/180) # 第五个关节的随机数,范围可以调整
rand4 = random.uniform(-10*np.pi/180, 10*np.pi/180) # 第四个关节的随机数,范围可以调整
angle_array = []
# 1. 原始角度
angle_array.append(original_angle.copy())
# 4. += rand4
angle_c = original_angle.copy()
angle_c[3] -= rand4
angle_c[4] -= rand5
angle_array.append(angle_c)
# 5. -= rand4
angle_d = original_angle.copy()
angle_d[3] += rand4
angle_d[4] += rand5
angle_array.append(angle_d)
# 6. +=rand4-= rand5
angle_e = original_angle.copy()
angle_e[3] += rand4
angle_e[4] -= rand5
angle_array.append(angle_e)
# 6. -= rand4第五个关节 += rand5
angle_f = original_angle.copy()
angle_f[3] -= rand4
angle_f[4] += rand5
angle_array.append(angle_f)
# 6. -= rand4第五个关节 += rand5
angle_g = original_angle.copy()
angle_g[3] -= rand4/2
angle_g[4] += rand5/2
angle_array.append(angle_g)
angle_h = original_angle.copy()
angle_h[3] += rand4/2
angle_h[4] -= rand5/2
angle_array.append(angle_h)
# 将所有角度从度转换为弧度
# modified_angles_rad = np.array([np.deg2rad(angles) for angles in angle_array])
# import pdb
# pdb.set_trace()
return angle_array
def traversal_position_direction(self,path):
for pose in path:
current_pose = self.arm.robot_rpc_client.getRobotInterface(self.arm.robot_name).getRobotState().getTcpPose()
print(f" elipse path is {pose}\n current pose is {current_pose}" )
code = self.arm.move_line( pose )
if code == -1:
print(f"运动到拍照位置失败:{pose}")
else:
joint = self.arm.robot_rpc_client.getRobotInterface(self.arm.robot_name).getRobotState().getJointPositions()
angle_list = self.generate_perturbed_joint_angles_rad(joint)
print(f"\n {angle_list}\n Angles Position ")
for joint in angle_list:
print(f"current joint: {joint}")
code = self.arm.move_joint(joint,4,wait=True)
if code != -1:
print(f"move success")
self.check_corners_add_sample()
return len(self.robot_poses)
def visualization(self,center,trajectory,rpy_values):
# 可视化
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 绘制中心点
ax.scatter(*center, color='r', marker='o', s=100, label='Center Point')
# 绘制轨迹
ax.plot(trajectory[:, 0], trajectory[:, 1], trajectory[:, 2], color='b', label='Tool Trajectory')
# 可视化 RPY 值 (每隔几个点显示一个)
num_samples_rpy = 20
indices = np.linspace(0, len(rpy_values) - 1, num_samples_rpy, dtype=int)
for i in indices:
tool_point = trajectory[i]
roll, pitch, yaw = rpy_values[i]
vector = tool_point - center
# 将箭头的起始点移动到 tool_point
ax.quiver(tool_point[0], tool_point[1], tool_point[2],
vector[0], vector[1], vector[2],
length=0.2, color='g', alpha=0.7, label='Vector Center to Tool' if i == indices[0] else "")
# 将文本标签的位置移动到 tool_point 附近
ax.text(tool_point[0] + 0.05, tool_point[1] + 0.05, tool_point[2] + 0.05,
f'RPY:({math.degrees(roll):.2f},{math.degrees(pitch):.2f},{math.degrees(yaw):.2f})',
color='g', fontsize=8)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Cyclic Trajectory Above Center Point')
ax.legend()
ax.view_init(azim=-120, elev=30)
plt.grid(True)
plt.show()
# 打印 RPY 值 (示例)
# print("\nRoll, Pitch, Yaw values (degrees) along the trajectory:")
# for i, (r, p, y) in enumerate(rpy_values):
# if i % 10 == 0: # Print every 10th value
# print(f"Step {i}: Roll: {math.degrees(r):.2f}, Pitch: {math.degrees(p):.2f}, Yaw: {math.degrees(y):.2f}")
if __name__ == '__main__':
# 标定板的width 对应的角点的个数 6
# 标定板的height 对应的角点的个数 3
calibration_board_size = [9,6]
calibration_board_square_size = 0.020 #unit - meter
systemPath = "/home/jsfb/Documents/"
now = datetime.now()
date_time_str = now.strftime("%d_%H_%M")
folder_name = f"calibration_data_{date_time_str}"
systemPath+=folder_name
os.makedirs(systemPath, exist_ok=True)
calib = Calibration(calibration_board_size,calibration_board_square_size,systemPath)
calib.collect_data()
r, t = calib.calibrate()
print("旋转矩阵:")
print(r)
print("平移向量:")
print(t)
# arm = AuboC5()
# arm.init()
# aubo_algorithm = arm.robot_rpc_client.getRobotInterface(arm.robot_name).getRobotAlgorithm().inverseKinematics()
# # 查看 rpc_client 对象的所有属性和方法
# print(dir(aubo_algorithm))

View File

@ -0,0 +1,84 @@
2025-05-26 15:02:07,413 - 测试日志 - INFO - log.py:106 - 切换到finger_head按摩头
2025-05-26 15:02:10,922 - 测试日志 - INFO - log.py:106 - 机械臂测量线程启动
2025-05-26 15:02:10,923 - 测试日志 - INFO - log.py:106 - position command: [ 0.25000011 -0.13499942 0.3442838 ]
2025-05-26 15:02:10,925 - 测试日志 - INFO - log.py:106 - orientation command: [ 1.79999939e+02 1.09863257e-04 -9.00000000e+01]
2025-05-26 15:02:10,927 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000011 -0.13499942 0.3443389 ]
2025-05-26 15:02:10,928 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159159e+00 1.91747555e-06 -1.57079633e+00]
2025-05-26 15:02:10,929 - 测试日志 - INFO - log.py:106 - position command: [ 0.25000011 -0.13499942 0.34423872]
2025-05-26 15:02:10,930 - 测试日志 - INFO - log.py:106 - orientation command: [ 1.79999939e+02 1.09626792e-04 -9.00001567e+01]
2025-05-26 15:02:10,931 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999965 -0.13499989 0.3443392 ]
2025-05-26 15:02:10,932 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159265e+00 1.43810701e-06 -1.57079872e+00]
2025-05-26 15:02:10,947 - 测试日志 - INFO - log.py:106 - position command: [ 0.24999248 -0.13499964 0.3442024 ]
2025-05-26 15:02:10,948 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79998049e+02 5.63537890e-05 -9.00001768e+01]
2025-05-26 15:02:10,949 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000008 -0.13499965 0.34433948]
2025-05-26 15:02:10,951 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159159e+00 9.58738475e-07 -1.57079633e+00]
2025-05-26 15:02:10,965 - 测试日志 - INFO - log.py:106 - position command: [ 0.24998647 -0.13499941 0.34417218]
2025-05-26 15:02:10,966 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79996532e+02 1.10826636e-04 -9.00001933e+01]
2025-05-26 15:02:10,967 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000006 -0.13499954 0.34433917]
2025-05-26 15:02:10,968 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 1.91747644e-06 -1.57079726e+00]
2025-05-26 15:02:10,984 - 测试日志 - INFO - log.py:106 - position command: [ 0.24997959 -0.13499632 0.34414674]
2025-05-26 15:02:10,985 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79995110e+02 -7.71629391e-04 -9.00002723e+01]
2025-05-26 15:02:10,988 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999971 -0.13499977 0.34433887]
2025-05-26 15:02:10,990 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159159e+00 9.58737078e-07 -1.57079726e+00]
2025-05-26 15:02:11,003 - 测试日志 - INFO - log.py:106 - position command: [ 0.24997405 -0.1349941 0.34412699]
2025-05-26 15:02:11,004 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79993630e+02 -1.21707493e-03 -9.00001773e+01]
2025-05-26 15:02:11,005 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999979 -0.13499953 0.34433914]
2025-05-26 15:02:11,010 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 1.43810727e-06 -1.57079633e+00]
2025-05-26 15:02:11,022 - 测试日志 - INFO - log.py:106 - position command: [ 0.24996921 -0.13499329 0.34411049]
2025-05-26 15:02:11,023 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79992392e+02 -1.41061365e-03 -9.00001617e+01]
2025-05-26 15:02:11,027 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999976 -0.13499954 0.34433859]
2025-05-26 15:02:11,028 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159132e+00 1.91747466e-06 -1.57079726e+00]
2025-05-26 15:02:11,040 - 测试日志 - INFO - log.py:106 - position command: [ 0.24996431 -0.13499314 0.344097 ]
2025-05-26 15:02:11,042 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79991336e+02 -1.42291447e-03 -9.00001738e+01]
2025-05-26 15:02:11,044 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000012 -0.13499965 0.34433951]
2025-05-26 15:02:11,044 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159132e+00 1.43810905e-06 -1.57079779e+00]
2025-05-26 15:02:11,059 - 测试日志 - INFO - log.py:106 - position command: [ 0.24996024 -0.13499153 0.34408569]
2025-05-26 15:02:11,060 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79990418e+02 -1.83792583e-03 -9.00001532e+01]
2025-05-26 15:02:11,061 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000006 -0.13499954 0.34433936]
2025-05-26 15:02:11,062 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 1.91747644e-06 -1.57079726e+00]
2025-05-26 15:02:11,077 - 测试日志 - INFO - log.py:106 - position command: [ 0.24995717 -0.13499 0.34407685]
2025-05-26 15:02:11,078 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79989529e+02 -2.20403839e-03 -9.00001363e+01]
2025-05-26 15:02:11,079 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000012 -0.13499953 0.34433917]
2025-05-26 15:02:11,080 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 1.43810740e-06 -1.57079686e+00]
2025-05-26 15:02:11,094 - 测试日志 - INFO - log.py:106 - position command: [ 0.249954 -0.13498974 0.34406968]
2025-05-26 15:02:11,095 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79988802e+02 -2.26643396e-03 -9.00001428e+01]
2025-05-26 15:02:11,098 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999983 -0.13499965 0.34433942]
2025-05-26 15:02:11,099 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 1.91747619e-06 -1.57079726e+00]
2025-05-26 15:02:11,112 - 测试日志 - INFO - log.py:106 - position command: [ 0.2499512 -0.13498704 0.34406287]
2025-05-26 15:02:11,114 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79988304e+02 -2.84823642e-03 -9.00001578e+01]
2025-05-26 15:02:11,115 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999988 -0.13499977 0.34433896]
2025-05-26 15:02:11,118 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159265e+00 1.43810701e-06 -1.57079819e+00]
2025-05-26 15:02:11,130 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994961 -0.13498602 0.34405822]
2025-05-26 15:02:11,131 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79987684e+02 -3.11853240e-03 -9.00000925e+01]
2025-05-26 15:02:11,132 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999988 -0.13499989 0.34433929]
2025-05-26 15:02:11,133 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159265e+00 9.58737967e-07 -1.57079726e+00]
2025-05-26 15:02:11,149 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994811 -0.13498334 0.34405508]
2025-05-26 15:02:11,150 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79987145e+02 -3.78674921e-03 -9.00001559e+01]
2025-05-26 15:02:11,150 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000006 -0.13499989 0.34433951]
2025-05-26 15:02:11,151 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159132e+00 9.58739364e-07 -1.57079779e+00]
2025-05-26 15:02:11,168 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994679 -0.13498323 0.3440513 ]
2025-05-26 15:02:11,171 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79986832e+02 -3.73867209e-03 -9.00000707e+01]
2025-05-26 15:02:11,173 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999988 -0.13499989 0.34433929]
2025-05-26 15:02:11,175 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159159e+00 1.43810841e-06 -1.57079872e+00]
2025-05-26 15:02:11,189 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994505 -0.1349838 0.3440481 ]
2025-05-26 15:02:11,190 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79986625e+02 -3.72589441e-03 -9.00001151e+01]
2025-05-26 15:02:11,192 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999983 -0.13499965 0.34433942]
2025-05-26 15:02:11,193 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 1.91747670e-06 -1.57079819e+00]
2025-05-26 15:02:11,204 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994443 -0.1349842 0.34404624]
2025-05-26 15:02:11,208 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79986397e+02 -3.43709370e-03 -9.00001074e+01]
2025-05-26 15:02:11,209 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999983 -0.13499965 0.34433908]
2025-05-26 15:02:11,211 - 测试日志 - INFO - log.py:106 - orientation current: [ 3.14159212e+00 1.43810651e-06 -1.57079726e+00]
2025-05-26 15:02:11,223 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994332 -0.13498559 0.34404457]
2025-05-26 15:02:11,226 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79986153e+02 -3.16623999e-03 -9.00001230e+01]
2025-05-26 15:02:11,228 - 测试日志 - INFO - log.py:106 - position current:[ 0.24999966 -0.13499989 0.34433914]
2025-05-26 15:02:11,228 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159212e+00 9.58738475e-07 -1.57079779e+00]
2025-05-26 15:02:11,242 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994286 -0.13498704 0.34404302]
2025-05-26 15:02:11,242 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79986006e+02 -2.85488773e-03 -9.00000897e+01]
2025-05-26 15:02:11,243 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000018 -0.13499977 0.34433942]
2025-05-26 15:02:11,244 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159159e+00 9.58738475e-07 -1.57079686e+00]
2025-05-26 15:02:11,259 - 测试日志 - INFO - log.py:106 - position command: [ 0.24994203 -0.13498865 0.34404123]
2025-05-26 15:02:11,260 - 测试日志 - INFO - log.py:106 - orientation command: [-1.79985975e+02 -2.50918022e-03 -9.00001638e+01]
2025-05-26 15:02:11,261 - 测试日志 - INFO - log.py:106 - position current:[ 0.25000014 -0.13499953 0.34433948]
2025-05-26 15:02:11,262 - 测试日志 - INFO - log.py:106 - orientation current: [-3.14159159e+00 9.58738920e-07 -1.57079633e+00]
2025-05-26 15:02:11,277 - 测试日志 - INFO - log.py:106 - MassageRobot启动
2025-05-26 15:02:11,277 - 测试日志 - INFO - log.py:106 - 机械臂控制线程启动