眼在手上外参标定保姆级教学---离线手眼标定(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 了。

相关推荐
SmartBrain3 小时前
对比:Qwen-VL与传统的CNN在图像处理应用
图像处理·人工智能·cnn
宵时待雨3 小时前
C语言笔记归纳20:文件操作
c语言·开发语言·笔记·算法
java小吕布4 小时前
CentOS 7 服务器性能监控实战指南
linux·服务器·centos
椰子今天很可爱4 小时前
仿照muduo库实现一个高并发服务器
linux·服务器·c++
小豆子范德萨5 小时前
cursor连接远程window服务器的WSL-ubuntu
运维·服务器·ubuntu
全栈独立开发者5 小时前
架构师日记:当点餐系统遇上 AI —— 基于 Spring AI + Pgvector + DeepSeek 的架构设计思路
人工智能
谷歌开发者5 小时前
Web 开发指向标|开发者工具 AI 辅助功能的 5 大实践应用
前端·人工智能
kkai人工智能6 小时前
AI写作:从“废话”到“爆款”
开发语言·人工智能·ai·ai写作
lizz3110 小时前
C++模板编程:从入门到精通
java·开发语言·c++