ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

机械臂手眼标定自动化实战:JAKA+D455全流程详解

机械臂手眼标定自动化实战:JAKA+D455全流程详解 手动标定拍了几百张照片、示教器按到手指发酸之后我决定把“拍照-移动-记录-求解”这串重复劳动彻底交给代码。当时手上正好有一台JAKA机械臂和一台Intel RealSense D455相机就想用Python把它们串起来做一套全自动手眼标定流程机械臂自己带着标定板跑位姿相机自动拍照代码自动记录机械臂位姿和标定板位姿最后直接求出相机坐标系到机器人基坐标系的变换矩阵。这套流程跑通之后标定时间从原来的四五十分钟压到了五分钟以内而且不用人一直盯着示教器我这个从“手动挡”换到“自动挡”的人是真香了。这篇文章就把这套方案完整拆开讲清楚从硬件通信、环境配置到手眼标定的数学原理再到自动采集和求解验证的完整代码以及我实际运行中踩过的一堆坑。适合正在做机器人视觉引导、视觉抓取或者被手眼标定精度折磨过的朋友。如果你用的是其他型号的机械臂或相机只要通信方式类似思路和代码框架都可以直接照搬。1. 手动标定拍到崩溃才决定做这套自动化流程手眼标定这件事难度本身不大真正劝退人的是手动流程里那堆机械且重复的操作。我最早做标定时用的是最传统的方式标定板固定在桌上机械臂末端装相机从示教器一段一段地挪机械臂每挪一个位姿就按一次拍照再把相机里读到的标定板位姿和机械臂控制器上显示的末端位姿手动抄下来。听起来好像也没多复杂实际做起来非常折磨。最大的问题是数据要对齐。标定了十几组数据之后一旦中间有一组漏拍、重拍或者记录串行整组数据就废了只能推倒重来。而且手动挪机械臂时位姿随机性很大经常挪到一个角度标定板在画面里只剩一半或者干脆被机械臂自身挡住拍出来的数据不能用。等你把示教器按得手酸、拍了几十张废图之后精度可能还是不理想因为采集的位姿分布不够均匀姿态变化太少标定方程解出来不稳定。后来我换了个思路既然相机是固定的标定板可以固定在机械臂末端法兰上那么标定板在相机坐标系下的位姿、机械臂末端在机器人基坐标系下的位姿这两个数据都是可以通过程序直接读到的。我把“机械臂移动、相机拍照、数据记录”这三件事交给代码去循环执行于是就有了这套全自动方案。整套流程跑下来核心逻辑只有三步提前规划好一组机械臂末端位姿保证标定板始终在相机视野里并且姿态变化足够丰富。机械臂每到达一个位姿相机自动拍一帧图检测ArUco标定板得到标定板在相机坐标系下的位姿同时从机械臂控制器读取当前末端位姿。把十几组数据拼接好用OpenCV的calibrateHandEye求解手眼矩阵再做精度验证。实际跑下来从机械臂上电到拿到可用的手眼矩阵大概就是四五分钟。相比手动标定效率高了一个量级而且数据质量稳定没有人为误操作的空间。如果你正在被手动标定折磨这套自动化思路可以直接改造成适合你自己设备的工作流。2. 硬件通信与Python环境JAKA机械臂和D455相机先打通动手写标定算法之前先把两台设备在Python里跑通。JAKA机械臂和D455相机都是比较标准的设备通信方式不算复杂但有一些容易忽略的配置细节我挨个说。2.1 硬件清单和软件库我先列一下这套方案用到的硬件JAKA Zu系列机械臂我用的是JAKA Zu 5其他Zu系列操作逻辑一样Intel RealSense D455深度相机一块硬质标定板上面打印了ArUco码。我用的是一张A3纸打印的4x4规格ArUco板每个marker边长5cm贴在平整的亚克力背板上一台运行Ubuntu 20.04的工控机Windows 10也能跑代码本身没用到系统相关API软件环境方面Python版本建议3.8以上。需要装的库有这些pip install numpy opencv-python opencv-contrib-python pyrealsense2JAKA机械臂的Python库官方一般叫jaka_api不同固件版本API名称略有差异以你手上机械臂配套的SDK为准。我用的是通过TCP/IP方式连接机械臂控制柜读取和下发指令都走网络接口。如果你只有Windows环境也可以走官方提供的Windows SDK但代码结构和网络通信思路是一样的。有一个额外提醒opencv-python和opencv-contrib-python要一起装因为ArUco相关的cv2.aruco模块在contrib包里。只装普通版opencv的话import的时候就会报错这是新手最容易踩的坑。2.2 JAKA机械臂的网络控制与位姿读取JAKA控制柜默认监听网络端口电脑和控制柜要在同一个网段下。我这边把控制柜IP设成了192.168.2.100电脑网卡IP设成192.168.2.50用网线直连。连上之后先用官方Python库做初始化。from jaka_api import JAKARobot ROBOT_IP 192.168.2.100 ROBOT_PORT 10002 robot JAKARobot(ROBOT_IP, ROBOT_PORT) robot.power_on() robot.enable_robot()注意power_on()和enable_robot()是两个独立步骤分别对应伺服上电和使能少一步机械臂都无法运行。如果在自己电脑上测试时不想让机械臂真的动可以只连接不使能只读状态。运动控制方面我主要用两个指令move_line(pose)直线运动到指定的笛卡尔位姿。move_joint(pose)关节运动到指定关节角。读取当前末端位姿使用get_tcp_position()返回的是[x, y, z, rx, ry, rz]其中rx、ry、rz是欧拉角。这个欧拉角的旋转顺序非常关键JAKA整机默认有一套自己的约定后面求解标定矩阵时如果转错结果完全不对我第6节会专门讲这个坑。2.3 D455相机初始化与图像获取RealSense D455用pyrealsense2控制初始化代码不算复杂但色彩流的格式和分辨率最好固定下来不要随便改。我用的配置是1280x72030fpsBGR格式既保证了ArUco检测的清晰度又不会让采集链路太卡。import pyrealsense2 as rs import numpy as np pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30) pipeline.start(config) def grab_color_frame(): frames pipeline.wait_for_frames() color_frame frames.get_color_frame() return np.asanyarray(color_frame.get_data())有个经验是相机刚上电的前一两分钟自动曝光还在收敛画面经常忽亮忽暗ArUco检测在这种条件下会偶发失败。我一般会提前五到十分钟把相机电源通上让它先跑着等画面稳定了再开始标定。如果现场时间紧可以在代码里加几秒的warm-up逻辑开头先连续采集几十帧扔掉也能缓解这个问题。D455出厂时已经标定过相机内参直接用sensor.get_intrinsics()或者color_frame.profile.as_video_stream_profile().get_intrinsics()读取就行不需要再用棋盘格重新标内参。这一点比很多普通USB相机省事多了。2.4 通信验证小实验设备都接好之后先用一个最简单的实验验证两台设备通信是否正常让机械臂从当前位置向前移动一小段距离再回到原位同时循环打印相机画面的分辨率。这个过程能排除大部分IP配置错误、SDK版本不兼容、相机被占用这类低级问题。import time # 读取当前位姿 current robot.get_tcp_position() print(Current TCP:, current) # 在Z方向抬升20mm target current.copy() target[2] 0.020 robot.move_line(target) time.sleep(1) # 回到原位 robot.move_line(current) time.sleep(1) print(Camera frame shape:, grab_color_frame().shape)如果机械臂能动、相机能出图硬件链路就算打通了接下来可以安心搞数学和代码。3. 手眼标定核心数学眼在手外场景为什么可以化为AXXB很多朋友写代码前没把原理搞透直接拿网上的示例套结果换了场景就不知道该把哪个数据当输入、哪个数据当输出。我这边把眼在手外的坐标关系仔细推一遍你理解了之后代码里的参数含义就一目了然。3.1 相机固定、标定板装在机械臂末端坐标关系怎么串我采用的场景是眼在手外eye-to-handD455相机固定在工作空间上方标定板固定在机械臂末端法兰上。这种场景下要标定的对象是相机坐标系到机器人基坐标系的固定变换记作Base_T_Cam。对于任意一次拍摄标定板上的某个点P_board有两条路径可以映射到机器人基坐标系经过机械臂末端P_base Base_T_End * End_T_Board * P_board其中Base_T_End是机械臂控制器读到的末端位姿End_T_Board是标定板相对机械臂末端的固定变换。经过相机P_base Base_T_Cam * Cam_T_Board * P_board其中Cam_T_Board是ArUco检测得到的标定板在相机坐标系下的位姿。两条路径描述的是同一个点所以对任意一次拍摄i有Base_T_End_i * End_T_Board Base_T_Cam * Cam_T_Board_i这里面Base_T_End_i和Cam_T_Board_i是已知量End_T_Board和Base_T_Cam是未知量。其中Base_T_Cam正是我们要求的手眼矩阵。3.2 两两联立消掉未知量AXXB就出来了把上面这个等式变换一下。取两组不同的拍摄i和j两式联立就能把End_T_Board消掉最终化简成A * X X * B其中A是两组机械臂末端位姿之间的相对变换由控制器读数计算得到。B是两组标定板位姿之间的相对变换由ArUco检测结果计算得到。X就是我们要标定的Base_T_Cam。这就是手眼标定里最经典的AXXB问题。A是机械臂的位姿变化量B是相机观测到的标定板位姿变化量求解出的X是两者之间固定的坐标变换。在代码里OpenCV的cv2.calibrateHandEye已经把这个数学求解过程封装好了。它会接收一组机械臂末端位姿R_gripper2base, t_gripper2base和一组标定板位姿R_target2cam, t_target2cam返回R_cam2gripper, t_cam2gripper。在眼在手外这个场景下由于标定板固定在机械臂末端OpenCV内部数学模型的“gripper”角色在这里对应我们求解的相机到机器人基坐标系变换。很多教程直接把这个输出解释为cam2base使用我建议拿到结果后必须做一步“验证性测试”第5节会讲确认没错再用。3.3 为什么用ArUco而不是棋盘格我一开始用的是棋盘格检测标定板位姿后来换成了ArUco。区别在于棋盘格要求所有角点都在画面内无遮挡只要有反光或者标定板边缘被裁掉一点整帧角点就会检测失败。ArUco码是编码图案即使画面里只出现部分marker只要够多也能解算出板的位姿鲁棒性好很多。ArUco的字典支持多尺度识别标定板在画面中远近变化时都能稳定工作。对于自动标定这种需要连续采集几十帧、没有人工干预的场景ArUco的高鲁棒性是刚需。我用的是cv2.aruco.DICT_6X6_250字典板子由4x4个marker组成每个marker边长在画面里占40像素以上时检测和解算都非常稳定。4. 自动采集代码设计位姿规划、触发拍照与数据记录数学原理清楚了代码骨架就出来了。整个自动采集模块分三块位姿列表生成、机械臂移动和相机拍照、数据筛选与记录。4.1 位姿列表怎么规划不是随便挪几个点就完事手眼标定求解的质量很大程度上取决于输入位姿的多样性。如果十几组位姿都只是绕着同一个轴小幅旋转A矩阵之间线性相关度过高求出的X会非常不稳定。我生成位姿的原则是姿态覆盖要广让机械臂末端分别绕X、Y、Z三个轴旋转角度范围尽可能大在机械臂可达性允许的前提下。位置变化要小标定板整体位置不要大幅游走我会尽量让标定板锁定在相机视野中央附近避免跑到画面边缘导致ArUco检测精度下降。位姿数量要够我实测下来15到20组位姿是比较合适的量级。少于10组TSAI方法容易过拟合多于30组采集时间变长精度提升却不明显。一个实用的生成思路是先手动把机械臂挪到一个“基准位姿”让标定板正对着相机、位于画面中央。然后以这个位姿为原点生成一系列固定偏移和固定旋转的位姿。代码大概这样import numpy as np import math def generate_poses(base_pose, num_angles5, radius0.06): poses [] for k in range(num_angles): theta 2 * math.pi * k / num_angles for axis in range(3): pose base_pose.copy() pose[0] radius * math.cos(theta) pose[1] radius * math.sin(theta) # 分别绕X、Y、Z加30度左右的旋转 pose[3] 0.3 * (axis 0) pose[4] 0.3 * (axis 1) pose[5] 0.3 * (axis 2) poses.append(pose) return poses注意直接往欧拉角上加偏移这种方法只适合小角度微调超过45度之后欧拉角本身的旋转顺序问题就会导致实际姿态和预期不一致。我在项目里实际使用的是固定一组姿态矩阵再转换成JAKA需要的欧拉角格式这块比较繁琐但不难核心就是保证姿态矩阵的旋转部分多样化。4.2 主循环机械臂移动、相机拍照、数据记录整个自动采集的主循环逻辑非常直接遍历位姿列表让机械臂移动到位等机械臂稳定相机拍照检测ArUco成功就记录数据失败就记录日志并跳过这个位姿。这里有一个关键的时序问题机械臂move_line指令返回时并不代表机械臂已经真的停下来。如果立即拍照机械臂末端的残余振动会让标定板位置和机械臂控制器读到的位置不一致这会让标定结果引入额外误差。我的做法是到位后强制等500ms到1s让系统稳定后再拍照。import time import cv2 import numpy as np def wait_robot_stable(delay0.8): time.sleep(delay) def move_and_collect(robot, pipeline, aruco_board, poses, out_data): camera_matrix, dist_coeffs get_camera_intrinsics(pipeline) for idx, pose in enumerate(poses): robot.move_line(pose) wait_robot_stable() color grab_color_frame() rvec, tvec, valid detect_board(color, arufo_board, camera_matrix, dist_coeffs) if not valid: print(f[skip] pose {idx}: board not detected) continue tcp robot.get_tcp_position() out_data.append((tcp, rvec, tvec)) print(f[ok] pose {idx}: collected)detect_board内部用的是cv2.aruco检测加上cv2.solvePnP解算标定板位姿。完整实现如下def detect_board(color_image, aruco_dict, board, camera_matrix, dist_coeffs): gray cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY) corners, ids, rejected cv2.aruco.detectMarkers( gray, aruco_dict, parameterscv2.aruco.DetectorParameters_create() ) if ids is None or len(ids) 1: return None, None, False valid, board_rvec, board_tvec cv2.aruco.estimatePoseBoard( corners, ids, board, camera_matrix, dist_coeffs, None, None ) if not valid: return None, None, False return board_rvec, board_tvec, True这里estimatePoseBoard要求传入之前创建好的board对象。创建ArUco板的方式如下aruco_dict cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250) board cv2.aruco.GridBoard_create( markersX4, markersY4, markerLength0.05, markerSeparation0.003, dictionaryaruco_dict )markerLength单位是米要和真实打印的标定板尺寸严格对应。这个参数一旦错了后面解算的标定板位姿尺度就错了手眼矩阵的平移部分也会跟着错。4.3 数据格式与保存为求解做准备采集完成后我会把数据整理成两个列表一个是机械臂末端位姿以旋转矩阵和平移向量形式一个是标定板在相机坐标系的位姿。后面直接喂给calibrateHandEye。JAKA的get_tcp_position()返回的是[x, y, z, rx, ry, rz]其中旋转部分是欧拉角必须先转成旋转矩阵。JAKA欧拉角的具体旋转顺序官网文档一般有说明我这边默认是绕固定轴的某种RPY顺序正确做法是先查一下你所用固件的定义。如果不想被这个坑折磨可以在示教器上把当前位姿显示模式切到“旋转矩阵”模式直接读矩阵。我写的转换函数长这样def rpy_to_rotation_matrix(rx, ry, rz, conventionZYX): # 根据JAKA文档选择对应convention # 这里以常用的ZYX为例 cx, cy, cz math.cos(rx), math.cos(ry), math.cos(rz) sx, sy, sz math.sin(rx), math.sin(ry), math.sin(rz) if convention ZYX: Rx np.array([[1, 0, 0], [0, cx, -sx], [0, sx, cx]]) Ry np.array([[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]]) Rz np.array([[cz, -sz, 0], [sz, cz, 0], [0, 0, 1]]) return Rz Ry Rx # 其他convention按需补充保存到npy或者csv都可以我习惯直接存npy因为后面numpy处理最方便。np.save(collected_data.npy, np.array(collected_list))每个数据项是17个浮点数前3个是机械臂末端平移中间9个是机械臂末端旋转矩阵后3个标定板平移最后3个是标定板旋转向量。这个格式是我自己定的你也可以按自己习惯来但建议把原始数据都留好排查问题时能回看。5. 求解手眼矩阵与精度验证别急着上机先看这三个指标数据采集好了求解本身反而很简单。真正决定这套流程能不能用到项目里的是求解完之后的精度验证。我见过太多人标定完直接把矩阵扔进抓取程序然后发现位置偏了又绕回来重标——就是因为跳过了验证这一步。5.1 calibrateHandEye求解参数和输入格式把采集到的机械臂末端位姿和标定板位姿分别整理成列表直接调用OpenCV。def solve_hand_eye(data_list): R_gripper2base_list [] t_gripper2base_list [] R_target2cam_list [] t_target2cam_list [] for item in data_list: tcp_xyz item[:3] tcp_R item[3:12].reshape(3, 3) board_tvec item[12:15] board_rvec item[15:18] board_R, _ cv2.Rodrigues(board_rvec) R_gripper2base_list.append(tcp_R) t_gripper2base_list.append(tcp_xyz) R_target2cam_list.append(board_R) t_target2cam_list.append(board_tvec) R_cam2base, t_cam2base cv2.calibrateHandEye( R_gripper2base_list, t_gripper2base_list, R_target2cam_list, t_target2cam_list, methodcv2.CALIB_HAND_EYE_TSAI ) return R_cam2base, t_cam2basemethod参数我推荐CALIB_HAND_EYE_TSAI或者CALIB_HAND_EYE_PARK。TSAI方法对数据噪声比较敏感但收敛速度快PARK方法更稳一点。实机数据略有噪声时我两个都跑一遍看哪个的重投影误差小就用哪个。如果两个方法结果差异很大说明采集的数据质量有问题先别急着用。5.2 验证指标一重投影误差重投影误差是最直接的验证方式。思路是取一组标定过程中没有参与求解的数据或者在采集时专门留出三组作为验证帧用标定得到的R_cam2base和约束关系把标定板上的角点反投影回图像计算像素误差。计算逻辑不复杂def evaluate_reprojection(data_list, R_cam2base, t_cam2base, board): errors [] for item in data_list: tcp_R item[3:12].reshape(3, 3) tcp_t item[:3] board_R ... board_t ... # 通过手眼矩阵和机械臂位姿推算标定板在相机坐标系的位姿 End_T_Board board_T_board2end # 用已知数据反推这里略 Base_T_Board compose(tcp_R, tcp_t, End_T_Board) Cam_T_Board invert(R_cam2base, t_cam2base) Base_T_Board # 投影到像素计算与检测角点之间的误差 proj_points, _ cv2.projectPoints( board_points, board_rvec, board_tvec, camera_matrix, dist_coeffs ) errors.extend(compute_pixel_errors(proj_points, detected_corners)) return np.mean(errors), np.std(errors)简化来说如果标定正确重投影误差应该在一个像素量级。我实测时均值在0.5到0.9像素之间如果超过2个像素说明标定过程中某一环出问题了需要回到第4节检查数据。5.3 验证指标二平移矩阵稳定性重投影误差能反映图像层面的标定质量但它把机械臂位姿误差也混在一起了。另一个常用验证是重复标定测试把数据随机打乱取其中12组标定一次再取另外12组标定一次比较两次得到的t_cam2base差异。如果两次平移向量差异超过10毫米说明采集的位姿多样性不够或者某些数据质量太差。我实测典型情况下两次标定的平移差异在3到5毫米左右旋转差异在1度以内这个数据在当前项目里够用。5.4 验证指标三直接用机器人去碰目标点最让工程师有安全感的验证还是让机械臂真的动一下。我在标定板旁边固定了一个尖锐针尖用相机识别针尖尖端在相机坐标系下的位置通过手眼矩阵转换到机器人基坐标系然后让机械臂按这个坐标移动过去。实际误差3毫米以内基本说明标定是成功的。如果误差超过1厘米优先排查三件事ArUco板的尺寸填错没有、JAKA欧拉角转换对不对、机械臂到位后有没有等稳定再拍照。6. 实机踩坑记录与参数调优从“标定完成”到“真能用”之间的事下面这些坑是我反复跑自动标定时真实遇到的网上教程很少专门写但对成功率影响很大。6.1 JAKA欧拉角旋转顺序这个坑能让你怀疑人生我前面提了好几次JAKA的欧拉角旋转顺序这里专门展开说。JAKA控制器返回的rx、ry、rz如果按照常规的RPY外旋顺序转成旋转矩阵手眼标定的旋转部分经常会出现几十度的偏差。我当时第一次跑通全流程求解出来的旋转矩阵怎么看怎么不对劲后来查了官方文档又读了SDK源码才发现JAKA默认的旋转顺序和我在代码里假设的不一致。解决方法是手动操作机械臂让末端分别绕X、Y、Z轴旋转已知角度然后对比控制器读出的欧拉角就能反推出它用的是哪种旋转约定。这事花不了十分钟但能帮你省下后面几天排查时间。如果你用的不是JAKA这一步同样适用不同厂家对欧拉角的定义真的不一样。6.2 ArUco检测失败和精度差多是光照和尺寸问题ArUco检测失败的原因我遇到的主要是三种反光、过曝、marker过小。D455在强光直射下拍出来的图ArUco码会呈现局部高光角点检测就会偏差甚至失败。我的处理方式是把相机的曝光时间调低锁死自动曝光sensor pipeline.get_active_profile().get_device().query_sensors()[1] sensor.set_option(rs.option.enable_auto_exposure, 0) sensor.set_option(rs.option.exposure, 80)80这个值是我现场试出来的光照环境不同需要微调。原则是图像整体不要过曝标定板上的黑白色块对比清晰。另外marker边长在画面里最好占30像素以上低于这个值位姿解算的噪点会明显变大。可以通过调整相机安装高度和标定板大小来控制。6.3 机械臂到位后等多久太短有抖动太久浪费时间我一开始为了求快到位后只等了200ms就拍照结果标定结果一直不稳定重投影误差忽高忽低。后来观察机械臂的实时状态发现200ms时末端还在肉眼可见地晃500ms时基本稳定800ms时完全稳定。最终我选了800ms整个标定流程也就多了不到十秒这个代价完全值得。如果你用的是负载能力比较小的机械臂或者标定板装得比较偏稳定时间还需要更长。稳妥起见可以在代码里加一个阈值判断连续几次读取机械臂末端位置变化小于0.1mm时再拍照。6.4 数据筛选要舍得扔掉坏帧全自动采集过程中偶尔会混进一两帧质量差的数据标定板边缘被切了一部分、ArUco检测只识别到两三个marker、标定板位置太靠近画面边缘。这种数据我不会硬着头皮用。简单算一下每帧的ArUco重投影误差超过1.5像素的直接标记为“低质量帧”不参与后续求解。我专门写了一个筛选函数def filter_bad_frames(data_list, camera_matrix, dist_coeffs, board, max_reproj1.5): clean_list [] for item in data_list: reproj_err compute_single_frame_reproj(item, camera_matrix, dist_coeffs, board) if reproj_err is not None and reproj_err max_reproj: clean_list.append(item) return clean_list标定采集的时候机械臂每到一个位姿代码会连续拍三张图分别检测标定板并计算重投影误差取误差最小的一张作为该位姿的有效数据。这样能从源头上保证数据质量不用事后诸葛亮。6.5 标定矩阵在项目中落地的扩展思路标定完成后这套自动流程还能继续复用。我现在每次更换相机安装位置或者机械臂布局后直接跑一遍脚本就能重新标定完全不用手动操作。后面我还打算把视觉抓取的流程也串进来相机识别目标物体得到物体在相机坐标系的位姿乘上标定得到的Base_T_Cam就是物体在机器人基坐标系下的位姿机械臂直接按这个坐标去抓取。自动化标定只是第一步但它解决了整个视觉引导系统里最痛苦、最容易被忽略的一环。如果你也在用JAKA或者其他国产机械臂做视觉引导项目我建议先把这套自动化标定跑通再去做抓取逻辑不然坐标系没对齐后面的一切调试都是在沙滩上盖楼。
返回列表