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

相关推荐
一次旅行1 天前
HyperTool:突破传统工具调用限制,让Agent更高效执行复杂任务
人工智能
陈天伟教授1 天前
图解人工智能(58)人工智能应用-围棋国手
人工智能·语音识别·机器翻译
闻道参看1 天前
2026年AI优质企业培训系统综合测评:合规管控/数据量化
人工智能
yaoxin5211231 天前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
老虾头1 天前
科技贴近烟火:本地化 AI,赋能各行各业日常经营
人工智能
毒爪的小新1 天前
Linux 环境极速部署 vLLM:从零搭建生产级大模型推理服务
linux·人工智能·ai·语言模型·vllm
老大白菜1 天前
25美元,DIY开源可穿戴智能AI眼镜:Arduino+乐鑫ESP32+DeepSeek项目
人工智能
鹤落晴春1 天前
RH124问答3:从命令行管理文件
linux·运维·服务器
凡人叶枫1 天前
Effective C++ 条款30:透彻了解 inlining 的里里外外
linux·开发语言·c++·嵌入式开发·effective c++