具身智能专题(2)-主从臂零位校准及摄像头数据获取与检验

1. 主从臂零位校准

标定文件解读:
homing offset: 表示每个电机的零点偏移(Homing Offset),单位为步数(Steps)。它是将实际电机读数调整到标定目标位置(通常为0°或90°)所需的修正量。
drive mode:表示每个电机的驱动方向(DriveMode),0表示原始方向(无需反转),1表示反转方向。
start pos:表示标定过程中手动移到“零位”(Zero Position)时的电机读数(单位:步数),即 zero_pos。
end pos:表示标定过程中手动移到“旋转位”(Rotated Position,90°)时的电机读数(单位:步数),即 rotated_pos。
calib mode:表示每个电机的标定模式,DEGREE表示旋转关节(单位:度,范围[-180,180]),LINEAR表示线性关节(范围[0,100])。
motor names: 机械臂电机的名称列表,与其他参数一一对应。指定参数的适用对象,表示6个自由度(DOF)的机械臂:

  • shoulder pan: 肩部水平旋转
  • shoulder lift: 肩部俯仰
  • elbow flex: 肘部弯曲
  • wrist flex: 腕部俯仰
  • wrist roll: 腕部滚动
  • gripper: 夹爪

1.1 方案1:终端代码

from dynamixel_motors_bus import DynamixelMotorsBus

leader_port = "/dev/ttyACM10"
follower_port = "/dev/ttyACM11"

leader_arm = DynamixelMotorsBus(
    port=leader_port,
    motors={
        "shoulder_pan": (1, "xl330-m077"),
        "shoulder_lift": (2, "xl330-m077"),
        "elbow_flex": (3, "xl330-m077"),
        "wrist_flex": (4, "xl330-m077"),
        "wrist_roll": (5, "xl330-m077"),
        "gripper": (6, "xl330-m077")
    }
)

follower_arm = DynamixelMotorsBus(
    port=follower_port,
    motors={
        "shoulder_pan": (1, "xl430-w250"),
        "shoulder_lift": (2, "xl430-w250"),
        "elbow_flex": (3, "xl330-m288"),
        "wrist_flex": (4, "xl330-m288"),
        "wrist_roll": (5, "xl330-m288"),
        "gripper": (6, "xl330-m288")
    }
)

from lerobot.common.robot.devices.robots.manipulator import ManipulatorRobot

robot = ManipulatorRobot(
    robot_type="koch",
    leader_arms={"main": "leader_arm"},
    follower_arms={"main": "follower_arm"},
    calibration_dir=".cache/calibration/koch"
)

robot.connect()
 

1.2 方案1:脚本(推荐)

cd lerobot
vim ./lerobot/scripts/calibration.py

代码如下所示:

import os
import sys
## 添加根目录到系统路径
# current_dir = os.path.dirname(os.path.abspath(__file__))
# root_dir = os.path.abspath(os.path.join(current_dir, "..", "..", "."))
# print('root_dir:', root_dir)
# sys.path.insert(0, root_dir)


# 标定脚本。进行双臂的关节角度标定。再输出标定验证结果。
from lerobot.common.robot_devices.motors.dynamixel import DynamixelMotorsBus
from pathlib import Path


leader_port = "/dev/ttyACM10"  # 主臂端口
follower_port = "/dev/ttyACM11"  # 从臂端口

leader_arm = DynamixelMotorsBus(
    port=leader_port,
    motors={
        # name: (index, model)
        "shoulder_pan": (1, "xl330-m077"),
        "shoulder_lift": (2, "xl330-m077"),
        "elbow_flex": (3, "xl330-m077"),
        "wrist_flex": (4, "xl330-m077"),
        "wrist_roll": (5, "xl330-m077"),
        
        "gripper": (6, "xl330-m077"),
    },
)

follower_arm = DynamixelMotorsBus(
    port=follower_port,
    motors={
        # name: (index, model)
        "shoulder_pan": (1, "xl430-w250"),
        "shoulder_lift": (2, "xl430-w250"),
        "elbow_flex": (3, "xl330-m288"),
        "wrist_flex": (4, "xl330-m288"),
        "wrist_roll": (5, "xl330-m288"),
        "gripper": (6, "xl330-m288"),
    },
)

# 操作机器人
from lerobot.common.robot_devices.robots.manipulator import ManipulatorRobot

# 实例化机器人
robot = ManipulatorRobot(
    robot_type="koch",
    leader_arms={"main": leader_arm},
    follower_arms={"main": follower_arm},
    calibration_dir=".cache/calibration/koch",
)
# 连接机器人
robot.connect()



# 注意,下面代码会读取calibration_dir文件夹下是否有main_leader.json和main_follower.json文件,如果有的则不会进行calibration
calibration_dir = ".cache/calibration/koch"
# 判断文件夹下是否有main_leader.json和main_follower.json文件
if os.path.exists(os.path.join(calibration_dir, "main_leader.json")) and os.path.exists(os.path.join(calibration_dir, "main_follower.json")):
    print("已经标定过了, 请检查标定是否成功")

    leader_pos = robot.leader_arms["main"].read("Present_Position")
    
    follower_pos = robot.follower_arms["main"].read("Present_Position")

    print(leader_pos)
    print(follower_pos)
    print("对比leader和follower的关节参数,相差不大则说明标定成功")

执行命令:

python ./lerobot/scripts/calibration.py

(1. 从臂零位
在这里插入图片描述此时需要将从臂放置到零位,夹爪和机械臂水平放置,夹爪微张,一指宽即可(如下图所示),摆放好位置后,敲击回车键:

在这里插入图片描述(2. 从臂旋转位
在这里插入图片描述这里需要将机械臂树立起来旋转90度,做到横平竖直,夹爪开口到最大,摆放好位置后,敲击回车键:
在这里插入图片描述(3. 从臂休息位
在这里插入图片描述这里需要将从臂调整到休息位,使得电机不受力,靠自身的硬件属性就可以停放。此时的夹爪微张,大概一指宽。摆放好位置后,敲击回车键

在这里插入图片描述(4. 主臂零位
在这里插入图片描述
摆放同从臂零位,敲击回车。
在这里插入图片描述(5. 主臂旋转位
在这里插入图片描述摆放同从臂旋转位,敲击回车。

在这里插入图片描述
(6. 主臂休息位
在这里插入图片描述摆放同从臂休息位,敲击回车。

在这里插入图片描述

(7. 标定结果检验
标定结束,终端会输出leader和follower的关节参数,需要人工对比,如果两者差距不是很大,则说明标定成功
在这里插入图片描述
标定成功后,项目路径下会保存标定后的数据
在这里插入图片描述

2. 主从臂跟随性测试

主从臂零位校准成功后,就需要测试一下主臂操作,从臂的跟随性。

vim ./lerobot/scripts/teleopration.py

写入代码

import os
import sys
## 添加根目录到系统路径
# current_dir = os.path.dirname(os.path.abspath(__file__))
# root_dir = os.path.abspath(os.path.join(current_dir, "..", "..", "."))
# print('root_dir:', root_dir)
# sys.path.insert(0, root_dir)
# 遥操纵机器人
from lerobot.common.robot_devices.motors.dynamixel import DynamixelMotorsBus
from lerobot.common.robot_devices.robots.manipulator import ManipulatorRobot
from lerobot.common.robot_devices.cameras.opencv import OpenCVCamera, find_cameras
from pathlib import Path
import os
import tqdm

leader_port = "/dev/ttyACM10"  # 主臂端口
follower_port = "/dev/ttyACM11"  # 从臂端口

leader_arm = DynamixelMotorsBus(
    port=leader_port,
    motors={
        # name: (index, model)
        "shoulder_pan": (1, "xl330-m077"),
        "shoulder_lift": (2, "xl330-m077"),
        "elbow_flex": (3, "xl330-m077"),
        "wrist_flex": (4, "xl330-m077"),
        "wrist_roll": (5, "xl330-m077"),
        "gripper": (6, "xl330-m077"),
    },
)

follower_arm = DynamixelMotorsBus(
    port=follower_port,
    motors={
        # name: (index, model)
        "shoulder_pan": (1, "xl430-w250"),
        "shoulder_lift": (2, "xl430-w250"),
        "elbow_flex": (3, "xl330-m288"),
        "wrist_flex": (4, "xl330-m288"),
        "wrist_roll": (5, "xl330-m288"),
        "gripper": (6, "xl330-m288"),
    },
)



# 将摄像头连接到机器人 调试获取摄像头信息
# mock=False  # 取值决定了函数在未找到摄像头时是静默返回一个空列表还是主动抛出异常
# camera_infos = find_cameras(mock=mock)  # find_cameras 查找并返回计算机上可用摄像头的索引和对应端口信息
# camera_ids = [cam["index"] for cam in camera_infos]
# print(camera_ids)

# 实例化遥控机器人 对象
robot = ManipulatorRobot(
    leader_arms={"main": leader_arm},
    follower_arms={"main": follower_arm},
    calibration_dir=".cache/calibration/koch",
    # cameras={
    #     # "front": OpenCVCamera(0, fps=30, width=640, height=480),
    #     # "side": OpenCVCamera(2, fps=30, width=640, height=480),
    #     # "top": OpenCVCamera(4, fps=30, width=640, height=480),
    #     "laptop": OpenCVCamera(2, fps=30, width=640, height=480),
    #     "phone": OpenCVCamera(4, fps=30, width=640, height=480),
    #     # "wrist":OpenCVCamera(6, fps=30, width=640, height=480),
    # },
)

robot.connect()

seconds = 100  # 运行时间
frequency = 200 # 运行频率  
for _ in tqdm.tqdm(range(seconds*frequency)):
    leader_pos = robot.leader_arms["main"].read("Present_Position")
    robot.follower_arms["main"].write("Goal_Position", leader_pos)


# for _ in tqdm.tqdm(range(seconds*frequency)):  # 每秒运行200次,运行30秒
#     robot.teleop_step()
    # leader_pos = robot.leader_arms["main"].read("Present_Position")
    # follower_pos = robot.follower_arms["main"].read("Present_Position")
    # observation, action = robot.teleop_step(record_data=True)

    # print(follower_pos)
    # print(observation)
    # print(leader_pos)
    # print(action)

robot.disconnect()
print("遥操结束!!!.")

执行程序,即可手动去操控主臂,来检查从臂的跟随性,通过旋转每个关节的电机来一一检查。

python ./lerobot/scripts/teleopration.py

在这里插入图片描述备注:
不要让机械臂长时间上力矩,需要及时执行卸力代码。

vim ./lerobot/scripts/release_torque.py

写入代码

import os
import sys
## 添加根目录到系统路径
# current_dir = os.path.dirname(os.path.abspath(__file__))
# root_dir = os.path.abspath(os.path.join(current_dir, "..", "..", "."))
# print('root_dir:', root_dir)
# sys.path.insert(0, root_dir)
# 机械臂释放力矩
from lerobot.common.robot_devices.motors.dynamixel import DynamixelMotorsBus

leader_port = "/dev/ttyACM10"  # 主臂端口
follower_port = "/dev/ttyACM11"  # 从臂端口

leader_arm = DynamixelMotorsBus(
    port=leader_port,
    motors={
        # name: (index, model)
        "shoulder_pan": (1, "xl330-m077"),
        "shoulder_lift": (2, "xl330-m077"),
        "elbow_flex": (3, "xl330-m077"),
        "wrist_flex": (4, "xl330-m077"),
        "wrist_roll": (5, "xl330-m077"),
        "gripper": (6, "xl330-m077"),
    },
)

follower_arm = DynamixelMotorsBus(
    port=follower_port,
    motors={
        # name: (index, model)
        "shoulder_pan": (1, "xl430-w250"),
        "shoulder_lift": (2, "xl430-w250"),
        "elbow_flex": (3, "xl330-m288"),
        "wrist_flex": (4, "xl330-m288"),
        "wrist_roll": (5, "xl330-m288"),
        "gripper": (6, "xl330-m288"),
    },
)


leader_arm.connect()
follower_arm.connect()

# 用于控制 Dynamixel 马达的扭矩模式
from lerobot.common.robot_devices.motors.dynamixel import TorqueMode
follower_arm.write("Torque_Enable", TorqueMode.DISABLED.value)      # TorqueMode.DISABLED:禁用扭矩(马达松开,不受主臂控)
leader_arm.write("Torque_Enable", TorqueMode.DISABLED.value)
print("Torque disabled")

执行程序,即可手动去操控主臂,来检查从臂的跟随性,通过旋转每个关节的电机来一一检查。

python ./lerobot/scripts/release_torque.py

3.摄像头数据获取以及位置校准

整个套件有两个摄像头,一个顶部摄像头,一个侧面摄像头。相机布局如图所示
在这里插入图片描述
使用下面代码调整摄像头位置,使其可以完整的拍摄画面。


'''

python lerobot/common/robot_devices/cameras/opencv.py \
--images-dir /home/cheng/Documents/workspace_hjc/data

'''

# from lerobot.common.robot_devices.cameras.opencv import OpenCVCamera
# import cv2


# # 查找所有摄像头端口号
# from lerobot.common.robot_devices.cameras.opencv import OpenCVCamera, find_cameras
# camera_infos = find_cameras(False)  
# camera_ids = [cam["index"] for cam in camera_infos]
# print(camera_ids)

# cam = cv2.VideoCapture(index=2) # 0 side 2 top
# while True:
#     ret, frame = cam.read()
#     cv2.imshow("camera", frame)
#     if cv2.waitKey(1) & 0xFF == ord('q'):
#         break
# cam.release()
# cv2.destroyAllWindows()




###################################  优化的代码  ####################################
import cv2
from lerobot.common.robot_devices.cameras.opencv import OpenCVCamera, find_cameras

def get_camera(camera_id, window_name="Camera Preview"):
    """测试并显示单个摄像头画面"""
    try:
        # 初始化摄像头
        cam = cv2.VideoCapture(camera_id)
        if not cam.isOpened():
            print(f"无法打开摄像头 {camera_id}")
            return False
        
        # 显示操作提示
        print(f"\n正在测试摄像头 {camera_id}:")
        print("  - 按 'n' 切换到下一个摄像头")
        print("  - 按 'q' 退出程序")
        
        while True:
            ret, frame = cam.read()
            if not ret:
                print(f"摄像头 {camera_id} 读取帧失败")
                break
            
            # 显示画面
            cv2.imshow(window_name, frame)
            
            # 按 'q' 退出当前摄像头,'n' 切换到下一个
            key = cv2.waitKey(1) & 0xFF
            if key == ord('q'):
                return False  # 退出整个程序
            elif key == ord('n'):
                return True  # 切换到下一个摄像头
        
        return False  # 默认退出
    
    except Exception as e:
        print(f"摄像头 {camera_id} 发生错误: {e}")
        return False
    
    finally:
        cam.release()
        cv2.destroyWindow(window_name)


def main():
    # 初始提示
    print("欢迎使用摄像头测试程序!")
    print("操作说明:请你将鼠标光标点击弹出图像然后按照下面提示切换摄像头")
    print("  - 按 'n' 切换到下一个摄像头")
    print("  - 按 'q' 随时退出程序")
    print("正在搜索可用摄像头...\n")
    
    # 查找所有可用摄像头
    try:
        camera_infos = find_cameras()
    except Exception as e:
        print(f"查找摄像头失败: {e}")
        return
    
    camera_ids = [cam["index"] for cam in camera_infos]
    print(f"找到 {len(camera_ids)} 个摄像头端口: {camera_ids}")
    
    if not camera_ids:
        print("未找到任何可用摄像头")
        return
    
    print(f"找到以下摄像头端口: {camera_ids}")
    print(f"共检测到 {len(camera_ids)} 个摄像头,开始测试...\n")
    
    # 依次测试每个摄像头
    for idx, cam_id in enumerate(camera_ids):
        window_name = f"Camera {cam_id} Preview"
        continue_testing = get_camera(cam_id, window_name)
        if not continue_testing:
            break  # 用户按 'q' 或发生错误,退出循环
    
    print("\n所有摄像头测试完成或已退出")

if __name__ == "__main__":
    main()

整体画面如下图所示:

顶部相机

在这里插入图片描述
侧边相机

在这里插入图片描述
修改配置文件
lerobot/configs/robot/koch.yaml 中摄像头的参数 camera_index 修改成对应的序号
在这里插入图片描述

<think>好的,我需要解决用户关于如何通过程序调用两台手机的摄像头并实现校准的问题。首先,我得理清用户的具体需求:他们想同时调用两台手机的摄像头,并且进行同步校准。这涉及到多个步骤,包括摄像头调用、数据同步、时间同步、标定方法等。 首先,程序调用双摄像头。可能需要使用移动开发框架,比如Android的Camera2 API或者iOS的AVFoundation。但用户提到的是双手机,所以可能需要在两台不同的设备上分别运行应用程序,然后通过网络进行通信和同步。这时候需要考虑网络协议的选择,比如TCP/UDP,或者使用更高效的WebSocket。同时,要确保两台设备的时间同步,可能需要使用NTP协议或者GPS时间戳。 接下来是数据同步的问题。摄像头采集的图像数据需要时间戳对齐,否则后续的校准会有问题。除了网络时间同步,可能还需要在应用层添加应用特定的同步机制,比如主从设备模式,其中一台设备作为主机发送同步信号,另一台作为从机进行响应。此外,硬件触发可能更可靠,比如使用外部信号触发两台摄像头同时拍摄,但这在手机上可能难以实现,所以可能只能依赖软件同步。 然后是摄像头标定。双手机校准可能涉及立体标定,需要获取各自的内参(焦距、主点、畸变系数)和外参(两个摄像头之间的旋转和平移矩阵)。用户提到的参考文章中有关于相机标定的步骤,比如离线获取内外参数和畸变系数,这可能同样适用于双手机的情况。可能需要使用标定板,如棋盘格,分别对每个摄像头进行单独标定,然后再进行联合标定获取外参。 还需要考虑如何传输和处理数据。图像数据量较大,直接传输可能延迟高,所以可能需要压缩或者降低分辨率。同时,处理双摄像头的图像数据可能需要用到OpenCV等库,进行特征点匹配、极线校正等。 另外,用户提到的IMU校准中的时间同步和滤波处理也可能有参考价值,比如在摄像头数据采集时,确保采样频率和低通滤波设置,减少噪声干扰。不过摄像头和IMU的数据处理有所不同,但时间同步的思路是类似的。 可能的挑战包括网络延迟导致的时间不同步、图像数据传输的带宽问题、双摄像头外参标定的精度等。需要分步骤解决,比如先实现基本的双摄像头调用和简单同步,再逐步优化同步机制,最后进行高精度标定。 总结下来,步骤大致是:1. 开发双手机的摄像头调用程序,支持图像采集和传输;2. 实现设备间的时间同步机制;3. 进行单摄像头的内参标定;4. 进行双摄像头的联合外参标定;5. 验证同步和标定的准确性,并进行优化。 现在需要将这些步骤结构化,给出具体的方法和建议,同时引用用户提供的参考内容中的相关部分,比如标定步骤和时间同步的设置。</think>### 双手机摄像头调用同步校准方法 #### 1. 摄像头调用数据采集 - **跨设备调用** 使用移动端开发框架(Android Camera2 API、iOS AVFoundation)分别编写摄像头控制程序。通过Wi-Fi或蓝牙建立设备间通信,使用WebSocket或MQTT协议传输控制指令和同步信号。例如: ```python # 伪代码示例:Android端通过Camera2 API捕获图像 cameraManager.openCamera(cameraId, sessionCallback, handler) captureSession.setRepeatingRequest(previewRequest, null, handler) ``` - **时间同步机制** 采用NTP协议校准设备系统时间误差,并在图像数据中添加毫秒级时间戳。参考IMU校准中时间同步的设计思路,设置低通滤波器减少时钟抖动影响[^1]。 #### 2. 双摄像头标定流程 1. **单目内参标定** 使用棋盘格标定板,通过OpenCV的`calibrateCamera()`函数分别计算各摄像头的内参矩阵$K$和畸变系数$D$: $$ K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix}, \quad D = [k_1, k_2, p_1, p_2, k_3] $$ 标定过程中需固定焦距并避免环境光突变[^1]。 2. **双目外参标定** 同步采集同一标定板的图像,通过`stereoCalibrate()`计算旋转矩阵$R$和平移向量$T$: $$ R = \begin{bmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} & r_{23} \\ r_{31} & r_{32} & r_{33} \end{bmatrix}, \quad T = [t_x, t_y, t_z]^T $$ 建议采集20组以上不同角度的图像以提高精度。 #### 3. 实时同步优化 - **硬件辅助同步** 若支持硬件触发,可通过外部信号同步摄像头曝光(如使用Arduino生成脉冲信号)。 - **软件补偿策略** 当检测到帧时间差超过阈值(如>5ms),通过插值算法补偿位移偏差,参考IMU温漂补偿的参数记忆方法。 #### 4. 验证误差修正 - **重投影误差检验** 计算标定板角点的重投影误差,要求RMS误差<0.5像素: $$ \epsilon = \sqrt{\frac{1}{N}\sum_{i=1}^{N} \|x_i - \hat{x}_i\|^2} $$ - **动态场景测试** 在运动场景下验证双目视差一致性,必要时在线更新外参。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigCabbageFy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值