眼在手上外参标定保姆级教学---离线手眼标定(vscode + opencv)

手眼标定的本质就是记录多组数据。每组数据需记录两个关键信息:

  • 相机拍摄的标定板图像
  • 拍摄该图像时机械臂末端执行器在基坐标系下的位姿(Xt,Yt,Zt, Rx,Ry,Rz)。然后用这些大概20组数据,通过一些算法去变换出相机与末端执行器之间的变换矩阵

所以也可以不用配置那么复杂的环境,直接在实验室中拖动机械臂本体,然后记录当前位姿,然后拍摄图片就行了,然后再把这些数据送到一个简单的算法执行器求解机械臂末端与相机的变换矩阵里就行了

1. 数据采集过程(手动、离线)

  • 硬件准备
    • 相机(您的 RealSense D435)固定在机械臂末端执行器上,确保稳定。
    • 标定板(推荐 9x6 棋盘格,每格 25-30mm 方块,打印在平坦亚光纸上)。固定在工作台上,不动。
    • 机械臂(Elfin E03)连接电脑,通过教导器或控制器软件记录位姿。
  • 采集步骤
    1. 启动机械臂控制器软件(Han's Robot 教导器或 PC 软件),启用拖动模式(freedrive 或 teach mode)。
    2. 手动拖动机械臂到一个位姿,让标定板完整出现在相机视野中(倾斜 30-45 度,避免正面,覆盖不同距离和角度)。
    3. 记录位姿:用控制器软件读取末端执行器在基坐标系下的位姿 (Xt, Yt, Zt, Rx, Ry, Rz),保存到 TXT 文件或 Excel(如 pose1: 100, 200, 300, 0, 0, 90)。
    4. 立即拍摄图像:用 RealSense Viewer 或简单 Python 脚本捕获 RGB 图像,保存为 JPG(如 image1.jpg)。
    5. 重复 15-20 次:每个位姿变化显著(平移 > 50mm,旋转 > 30°),避免共面位姿(导致奇异矩阵)。
  • 数据格式
    • 图像文件夹:images/image1.jpg, image2.jpg...
    • 位姿文件:poses.txt,每行一组位姿,如 "100 200 300 0 0 90" (Xt Yt Zt Rx Ry Rz, 单位 mm/度)。

2. 计算 X 矩阵(用简单 Python 脚本)

用 OpenCV 的 cv2.calibrateHandEye 函数计算(基于 Tsai-Lenz 方法)。脚本输入:图像 + 位姿,输出 X。

  • 安装依赖 (在本地 Python 3 环境):

    Bash

    复制代码
    pip install opencv-python numpy
  • 完整 Python 脚本 (复制到文件 hand_eye_calib.py):

    Python

    python 复制代码
    import cv2
    import numpy as np
    from math import radians
    
    # 标定板参数(调整为您的)
    BOARD_SIZE = (9, 6)  # 棋盘格内角点数(宽x高)
    SQUARE_SIZE = 0.025  # 每格大小 (m)
    
    # 1. 加载图像和位姿数据
    def load_data(image_folder, pose_file):
        images = []  # 图像列表
        poses = []  # 位姿列表
        for i in range(1, 21):  # 假设 20 组
            img = cv2.imread(f"{image_folder}/image{i}.jpg")
            if img is None:
                print(f"Image {i} not found!")
                continue
            images.append(img)
        
            # 读取位姿 (Xt Yt Zt Rx Ry Rz)
            with open(pose_file, 'r') as f:
                lines = f.readlines()
                pose_line = lines[i-1].strip().split()
                t = np.array([float(pose_line[0]), float(pose_line[1]), float(pose_line[2])]) / 1000  # mm to m
                r = np.array([radians(float(pose_line[3])), radians(float(pose_line[4])), radians(float(pose_line[5]))])  # 度 to 弧度
                R = cv2.Rodrigues(r)[0]  # 罗德里格斯公式转旋转矩阵
                poses.append((R, t))
        return images, poses
    
    # 2. 从图像检测标定板角点,计算相机到标定板的位姿
    def get_camera_to_board_poses(images):
        obj_points = np.zeros((BOARD_SIZE[0] * BOARD_SIZE[1], 3), np.float32)
        obj_points[:, :2] = np.mgrid[0:BOARD_SIZE[0], 0:BOARD_SIZE[1]].T.reshape(-1, 2) * SQUARE_SIZE
        
        R_cam_to_board = []  # 旋转矩阵列表
        t_cam_to_board = []  # 平移向量列表
        
        # 相机内参(从您的自定义 YAML 或 RealSense 默认)
        camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])  # 替换 fx,fy,cx,cy
        dist_coeffs = np.array([k1, k2, p1, p2, k3])  # 畸变系数
        
        for img in images:
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            ret, corners = cv2.findChessboardCorners(gray, BOARD_SIZE, None)
            if ret:
                corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
                ret, rvec, tvec = cv2.solvePnP(obj_points, corners, camera_matrix, dist_coeffs)
                R = cv2.Rodrigues(rvec)[0]  # 旋转矩阵
                R_cam_to_board.append(R)
                t_cam_to_board.append(tvec)
            else:
                print("Chessboard not found in image!")
        return R_cam_to_board, t_cam_to_board
    
    # 3. 计算 X (相机相对末端执行器的变换)
    def compute_X(R_gripper_to_base, t_gripper_to_base, R_cam_to_board, t_cam_to_board):
        # 注意:对于 Eye-in-Hand,A = gripper_to_base, B = cam_to_board 的逆(因为 B 是 board_to_cam 的逆)
        R_board_to_cam = [R.T for R in R_cam_to_board]
        t_board_to_cam = [-np.dot(R.T, t) for R, t in zip(R_cam_to_board, t_cam_to_board)]
        
        R_X, t_X = cv2.calibrateHandEye(R_gripper_to_base, t_gripper_to_base, R_board_to_cam, t_board_to_cam, method=cv2.CALIB_HAND_EYE_TSAI)
        X = np.eye(4)
        X[:3, :3] = R_X
        X[:3, 3] = t_X.flatten()
        return X
    
    # 主函数
    if __name__ == "__main__":
        images, poses = load_data("images", "poses.txt")
        R_gripper_to_base, t_gripper_to_base = zip(*poses)
        R_cam_to_board, t_cam_to_board = get_camera_to_board_poses(images)
        
        X = compute_X(R_gripper_to_base, t_gripper_to_base, R_cam_to_board, t_cam_to_board)
        print("Camera to End-Effector Transformation Matrix X:")
        print(X)
  • 如何运行

    1. 替换脚本中的 camera_matrix 和 dist_coeffs 为您的自定义内参值。
    2. 运行 python hand_eye_calib.py。

如何导出机械臂位姿数据?如何导出相机的拍摄标定板的图片?

大族(Han's Robot)Elfin E03 的示教器(teach pendant)是物理硬件,通常随机器人一起发货(手持平板式设备)。

如何导出机械臂位姿数据(Xt, Yt, Zt, Rx, Ry, Rz)
  • 方法 1: 用物理示教器查看和记录(最简单,手动)
    • 连接示教器到控制柜(专用接口)。
    • 开机后,在示教器界面进入 "坐标" 或 "当前位置" 菜单。
    • 显示末端位姿(通常 XYZWPR 格式,WPR 是欧拉角)。
    • 手动拖动臂到标定位姿,记录屏幕显示的数值到笔记本或 Excel(每组一行)。

如何导出相机拍摄标定板的图片数据(RealSense D435)

最简单方式:用 Intel RealSense Viewer(免费官方工具)捕获图像。

下载 RealSense Viewer
如何捕获标定板图片
  1. 连接 D435 到电脑 USB 3.0 端口。
  2. 打开 RealSense Viewer。
  3. 左侧选择您的 D435 设备。
  4. 开启 Color 流(RGB 图像)和 Depth 流(可选)。
  5. 把标定板放进视野(完整、清晰、无反光)。
  6. 点击右上角 Snapshot 按钮(相机图标):
    • 保存 RGB 图像为 PNG/JPG。
    • 同时可保存 Depth 数据。
  7. 重复移动机械臂,拍摄 15-20 张不同位姿的标定板图像。
  8. 保存到文件夹(如 images/calib1.png)

这样就有图像 + 手动记录位姿数据,可以离线计算 X 了。

相关推荐
毕设源码-邱学长4 小时前
【开题答辩全过程】以 基于Java的学校住宿管理系统的设计与实现为例,包含答辩的问题和答案
java·开发语言
rookieﻬ°4 小时前
PHP框架漏洞
开发语言·php
GISer_Jing5 小时前
AI自动化工作流:智能驱动未来(升级研究生项目!!!)
人工智能·前端框架·自动化
草捏子5 小时前
Agent Skills:让 AI 一次学会、永远记住的能力扩展方案
人工智能
maosheng11465 小时前
RHCSA的第一次作业
linux·运维·服务器
NocoBase5 小时前
【2.0 教程】第 1 章:认识 NocoBase ,5 分钟跑起来
数据库·人工智能·开源·github·无代码
后端小肥肠5 小时前
OpenClaw实战|从识图到公众号内容自动化,我跑通了完整链路
人工智能·aigc·agent
Elastic 中国社区官方博客5 小时前
快速 vs. 准确:衡量量化向量搜索的召回率
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索
qq_381338505 小时前
【技术日报】2026-03-18 AI 领域重磅速递
大数据·人工智能
wifi chicken5 小时前
Linux 端口扫描及拓展
linux·端口扫描·网络攻击