大象机器人水星MercuryX1轮式人形机器人基于物体标记建模的键盘点按操作!

news2024/11/14 14:04:55

引言

在现代科技的推动下,机器人在日常生活和工作场景中的应用越来越广泛。本文将介绍MercuryX1,这款先进的机器人如何通过其手臂末端的摄像头识别并确定键盘的键位,从而进行精确的打字操作。通过这一案例,我们将展示MercuryX1在自动化办公领域的潜力,以及其在提升效率和减少人为错误方面的显著优势。

接下里跟随我们的脚步,我们先简单的介绍一下使用到的产品。

Product

Mercury X1

水星Mercury X1是一款轮式人形机器人,整体由水星Mercury B1和高性能移动底座组合而成,拥有19个自由度。其单臂为7自由度的类人手臂结构机械臂。整机配备了英伟达Jetson Xavier主控。

移动底座具备丰富的感知能力,包括高性能激光雷达、超声波传感器和2D视觉传感器。其直驱电机驱动系统使其最大运行速度可达1.2m/s,最大爬坡高度为2CM,最大爬坡角度为15度。整机最大续航时间高达8小时。

此外,Mercury X1支持包括ROS、Moveit、Gazebo和Mujoco等主流仿真软件,提升了机器人智能的自主学习和快速迭代能力。

myCobot Pro Adaptive Gripper

是Mercury X1适配的自适应夹爪,提供较大的加持力,和标准的M8航空插头接口。

Camera Flange

适配于Mercury X1的摄像头模组可以安装在机械臂双臂的末端,并搭配夹爪使用。通过摄像头,实际获取物体的相关信息,并利用这些数据进行机器视觉识别算法处理。通过USB接口,数据被传输到Jetson Nano主控进行进一步处理。

技术要点

接下来介绍在项目中使用到的技术点。

pymycobot

pymycobot是Elephant Robotics专为其机械臂产品设计的控制库。通过该库,用户可以方便快捷地调用API,以实现对机器人的精确控制和操作。pymycobot提供了丰富的功能接口,简化了编程流程,使开发者能够专注于应用开发。

pymycobot · PyPI

OpenCV

OpenCV是一个开源的计算机视觉库。通过该库,用户可以方便快捷地调用各种图像处理和计算机视觉的API,以实现图像识别、对象检测和图像转换等操作。OpenCV提供了丰富的功能接口,简化了开发流程,使开发者能够专注于应用实现。

OpenCV - Open Computer Vision Library

Stag

STag是一种稳定的标记码,广泛应用于计算机视觉和机器人定位领域。通过STag,用户可以实现可靠的物体识别和追踪。STag提供了稳健的性能和易于集成的接口,简化了开发者在定位和识别任务中的工作。

GitHub - bbenligiray/stag: STag: A Stable Fiducial Marker System

Project

整个项目最主要的功能就机械臂运动控制,机械臂的手眼标定(坐标系的转化)和机器的视觉识别。我们先来介绍最重要的机器视觉识别。

机器视觉识别

想要让X1进行打字,那么它肯定得认识键盘,机器人咋可能自己就认识键盘呢,所以我们要教他认识键盘,并且告诉他那个键在哪个位置。这就用到了STag和OpenCV,STag的标记码能够确定键盘的位置,并且反馈坐标参数。

下面这段代码实现是,刷新相机界面获取实时画面,来检测STag码的位置

def stag_identify_loop(self):
        while True:
            self.camera.update_frame()  # 刷新相机界面
            frame = self.camera.color_frame()  # 获取当前帧
            (corners, ids, rejected_corners) = stag.detectMarkers(frame, 11)  # 获取画面中二维码的角度和id
            marker_pos_pack = self.calc_markers_base_position(corners, ids)  # 获取物的坐标(相机系)
            print("Camera coords = ", marker_pos_pack)
            cv2.imshow("按下键盘任意键退出", frame)
            # cv2.waitKey(1)
            # 按下键盘任意键退出
            if cv2.waitKey(1) & 0xFF != 255:
                break

两个STag码是为了确定位置,在用draw在图中画出键盘的位置,在这里需要考虑两个手的情况,当然如果单臂也是能完成的但是我们是要模拟人打字的,左手负责左边的区域,右手负责右边的区域。

def draw(frame, arm):
    global per_right_corners, per_left_corners
    # 将图片灰度
    imGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 获取图片中二维码的角点
    (corners, ids, rejected_corners) = stag.detectMarkers(imGray, 11)
    # 通过角点获取,二维码相对于相机的位移、旋转向量
    marker_pos_pack = calc_markers_base_position(corners, ids, marker_size, mtx, dist)
    if arm == "left":
        # 获取当前机械臂末端坐标
        stag_cur_coords = np.array(ml.get_base_coords())
        stag_cur_bcl = stag_cur_coords.copy()
        stag_cur_bcl[-3:] *= (np.pi / 180)
        # 通过手眼矩阵获取二维码相对于机械臂基座的坐标
        stag_fact_bcl = Eyes_in_hand(stag_cur_bcl, marker_pos_pack, "left")
        stag_coord = stag_cur_coords.copy()
        stag_coord[0] = stag_fact_bcl[0]
        stag_coord[1] = stag_fact_bcl[1]
        stag_coord[2] = stag_fact_bcl[2]
        # 存入二维码的三维坐标
        keyboard_coords[","] = stag_coord
    else:
        # 获取当前机械臂末端坐标
        stag_cur_coords = np.array(mr.get_base_coords())
        stag_cur_bcl = stag_cur_coords.copy()
        stag_cur_bcl[-3:] *= (np.pi / 180)
        # 通过手眼矩阵获取二维码相对于机械臂基座的坐标
        stag_fact_bcl = Eyes_in_hand(stag_cur_bcl, marker_pos_pack, "right")
        stag_coord = stag_cur_coords.copy()
        stag_coord[0] = stag_fact_bcl[0]
        stag_coord[1] = stag_fact_bcl[1]
        stag_coord[2] = stag_fact_bcl[2]
        # 存入二维码的三维坐标
        keyboard_coords["."] = stag_coord
    # 通过角点获取,二维码相对于相机的位移、旋转向量
    rvecs, tvecs = solve_marker_pnp(corners, marker_size, mtx, dist)
    # 画出坐标系
    cv2.drawFrameAxes(frame, mtx, dist, rvecs, tvecs, 50)
    draw_img = frame

    x1 = corners[0][0][0][0]
    y1 = corners[0][0][0][1]
    x2 = corners[0][0][1][0]
    y2 = corners[0][0][1][1]

    x = x1 - x2
    y = y1 - y2
    # 根据两个交点之间连线的角度获取偏转角
    r = np.arctan(y / x)
    r = abs(r)
    # 获取两个角点之间的距离
    size = abs(x / np.cos(r))
    # 左臂摄像头两个按键x轴之间的距离
    left_x_dis = size * 1.3
    # 左臂摄像头两个按键y轴之间的距离
    left_y_dis = size * 1.35
    # 右臂摄像头两个按键x轴之间的距离
    right_x_dis = size * 1.3
    # 右臂摄像头两个按键y轴之间的距离
    right_y_dis = size * 1.35
    # 按键框的半径
    rad = int(size / 2)
    # 左臂摄像头x轴与第一个字母的偏移距离
    left_add_x = [size * 1.25]
    # 左臂摄像头y轴各行与第一个字母的偏移距离
    left_add_y = [-size * 2, -size * 2.3, -size * 3]
    # 右臂摄像头x轴与第一个字母的偏移距离
    right_add_x = [size * 1.3]
    # 右臂摄像头y轴各行与第一个字母的偏移距离
    right_add_y = [size * 4.1, size * 2.1, size * 1]
    # 获取按键框的中心点
    tray_frame = Path(corners[0][0])
    tray_frame_center_plot = tray_frame.vertices.mean(axis=0)

随后就是通过算法来确定每个键位的坐标如何计算的问题了。将圈选出来的键位存入数组当中,规定左手负责的区域,规定右手负责的区域

    # 左臂键盘布局
    left_keyboard_txt = [
        ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
        ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
        ["z", "x", "c", "v", "b", "n", "m"]
    ]
    # 右臂键盘布局
    right_keyboard_txt = [
        ["m", "n", "b", "v", "c", "x", "z"],
        ["l", "k", "j", "h", "g", "f", "d", "s", "a"],
        ["p", "o", "i", "u", "y", "t", "r", "e", "w", "q"],
    ]
    # 左臂点按的字母
    left_control = ["q", "w", "e", "r", "t",
                    "a", "s", "d", "f", "g",
                    "z", "x", "c", "v", ","]
    # 左臂点按的字母
    right_control = ["y", "u", "i", "o", "p",
                     "h", "j", "k", "l",
                     "b", "n", "m", "."]

这样X1就能够知道键盘是什么,以及相对应的键位在哪里了,接下来就要解决的是机械臂的手眼标定的问题了,需要将目标的物体的坐标系和机械臂末端的坐标系转化到同一个坐标系当中去。

手眼标定-眼在手中

1.  标记检测

使用相机捕获图像,并检测STag标记,获取到标记码的三维坐标。调用 solve_marker_pnp 计算标记在相机坐标系中的位置和方向。

def calc_markers_base_position(corners: NDArray, ids: T.List, marker_size: int, mtx: NDArray, dist: NDArray) -> T.List:
    if len(corners) == 0:
        return []
    rvecs, tvecs = solve_marker_pnp(corners, marker_size, mtx, dist)
    res = []
    for i, tvec, rvec in zip(ids, tvecs, rvecs):
        tvec = tvec.squeeze().tolist()
        rvec = rvec.squeeze().tolist()
        rotvector = np.array([[rvec[0], rvec[1], rvec[2]]])
        Rotation = cv2.Rodrigues(rotvector)[0]
        Euler = CvtRotationMatrixToEulerAngle(Rotation)
        cam_coords = tvec + rvec
        target_coords = cam_coords
    return target_coords

2.  坐标转换

将标记的旋转向量转换为旋转矩阵,再转换为欧拉角,以便于进一步的计算和分析,组合平移向量和旋转向量,得到目标坐标。

cv2.Rodrigues 函数用于在旋转向量和旋转矩阵之间进行转换。这个函数将旋转向量转换为旋转矩阵,或者将旋转矩阵转换为旋转向量。

rotvector = np.array([[rvec[0], rvec[1], rvec[2]]])
Rotation = cv2.Rodrigues(rotvector)[0]

 #欧拉角和旋转矩阵的相互转换
def CvtRotationMatrixToEulerAngle(pdtRotationMatrix):
    pdtEulerAngle = np.zeros(3)
    pdtEulerAngle[2] = np.arctan2(pdtRotationMatrix[1, 0], pdtRotationMatrix[0, 0])
    fCosRoll = np.cos(pdtEulerAngle[2])
    fSinRoll = np.sin(pdtEulerAngle[2])
    pdtEulerAngle[1] = np.arctan2(-pdtRotationMatrix[2, 0], (fCosRoll * pdtRotationMatrix[0, 0]) + (fSinRoll * pdtRotationMatrix[1, 0]))
    pdtEulerAngle[0] = np.arctan2((fSinRoll * pdtRotationMatrix[0, 2]) - (fCosRoll * pdtRotationMatrix[1, 2]), (-fSinRoll * pdtRotationMatrix[0, 1]) + (fCosRoll * pdtRotationMatrix[1, 1]))
    return pdtEulerAngle

def CvtEulerAngleToRotationMatrix(ptrEulerAngle):
    ptrSinAngle = np.sin(ptrEulerAngle)
    ptrCosAngle = np.cos(ptrEulerAngle)
    ptrRotationMatrix = np.zeros((3, 3))
    ptrRotationMatrix[0, 0] = ptrCosAngle[2] * ptrCosAngle[1]
    ptrRotationMatrix[0, 1] = ptrCosAngle[2] * ptrSinAngle[1] * ptrSinAngle[0] - ptrSinAngle[2] * ptrCosAngle[0]
    ptrRotationMatrix[0, 2] = ptrCosAngle[2] * ptrSinAngle[1] * ptrCosAngle[0] + ptrSinAngle[2] * ptrSinAngle[0]
    ptrRotationMatrix[1, 0] = ptrSinAngle[2] * ptrCosAngle[1]
    ptrRotationMatrix[1, 1] = ptrSinAngle[2] * ptrSinAngle[1] * ptrSinAngle[0] + ptrCosAngle[2] * ptrCosAngle[0]
    ptrRotationMatrix[1, 2] = ptrSinAngle[2] * ptrSinAngle[1] * ptrCosAngle[0] - ptrCosAngle[2] * ptrSinAngle[0]
    ptrRotationMatrix[2, 0] = -ptrSinAngle[1]
    ptrRotationMatrix[2, 1] = ptrCosAngle[1] * ptrSinAngle[0]
    ptrRotationMatrix[2, 2] = ptrCosAngle[1] * ptrCosAngle[0]
    return ptrRotationMatrix

#合并旋转和平移向量
cam_coords = tvec + rvec
target_coords = cam_coords

他们本身不是一个世界的人,现在强行转化到一个世界里,就能够互相知道在哪里了!这样就能够直接获取到键盘键位的result了 。

机械臂的运动控制

当我们有了目标物体的坐标之后,就到我们的X1闪亮登场了,开始执行运动,这里我们用到pymycobot来控制机械臂运动。

    r是右手的控制,l是左手的控制
    mr = Mercury("/dev/ttyACM0")
    ml = Mercury("/dev/ttyTHS0")
    #发送角度位置和速度给机械臂,
    mr.send_angles(mr_pos, sp)
    ml.send_angles(ml_pos, sp)
    #发送坐标和速度给机械臂
    mr.send_coords(mr_pos, sp)
    ml.send_coords(ml_pos, sp)

就是将目标的坐标传递给机械臂去运动,就能实现打字了,我们一起来看看运动的效果如何。

视频链接

总结

通过本文,我们详细介绍了Mercury X1轮式人形机器人在打字任务中的应用实例。Mercury X1凭借其19自由度的灵活结构、丰富的感知能力和高性能的控制系统,展示了在自动化办公领域的巨大潜力。结合适配的摄像头模组和先进的机器视觉算法,Mercury X1能够精准识别并操作键盘,显著提升了工作效率和准确性。随着技术的不断进步,我们期待Mercury X1在更多领域展现其卓越的性能,为智能自动化带来更多可能性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1972809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

xcode使用

1. 界面 1.1. Build Settings,Build Phases和Build Rules三个设置项 Build Settings(编译设置): 每个选项由标题(Title)和定义(Definition)组成。这里主要定义了Xcode在编译项目时的一些具体配置 Build Phases(编译资源):用于指定编译过程中项目所链接的原文件,依赖对象,库…

安装 electron 报错解决

1. 报错 大概率由镜像问题导致 2. 解决 2.1 打开 npm 配置 npm config edit 2.2 添加配置 registryhttps://registry.npmmirror.comelectron_mirrorhttps://cdn.npmmirror.com/binaries/electron/electron_builder_binaries_mirrorhttps://npmmirror.com/mirrors/electron…

Tensor安装和测试

1: 打开git官方 https://github.com/NVIDIA/TensorRT 2: 下载得到:TensorRT-10.2.0.19.Linux.x86_64-gnu.cuda-11.8.tar.gz 3: 下载后配置环境变量,上面地址记得改成真实地址。 4: 如果想python使用tensorrt,那么 解压后目录&#xff0c…

深入理解单元测试与JUnit:从基础概念到实践操作

文章目录 前言一、单元测试是什么?单元测试的特点单元测试的好处 二、junit是什么?三、操作步骤1.junit安装2.maven新建项目3. 新建java文件4. 生成测试类5. 编写测试方法6. 测试结果 总结 前言 随着软件开发行业的不断发展,测试的重要性日益…

清华和字节联合推出的视频理解大模型video-SALMONN(ICML 2024)

video-SALMONN: Speech-Enhanced Audio-Visual Large Language Models 论文信息 paper:https://arxiv.org/abs/2406.15704 code:https://github.com/bytedance/SALMONN/ AI也会「刷抖音」!清华领衔发布短视频全模态理解新模型 | ICML 2024 …

Python数值计算(10)——PPoly对象

在scipy中,scipy.interpolate下还有一个PPoly的类,用于表示插值多项式,很多插值算法的结果,都以该类的实例返回,因此有必要了解该类的使用方法。要使用该类,首先要引入相应的模块: from scipy.…

基于docker的 nacos安装部署

一、拉取镜像 拉取nacos官方镜像,这里使用默认命令 docker pull nacos/nacos-server二、创建挂载目录 创建本地的映射文件application.properties mkdir -p /home/docker/nacos/conf /home/docker/nacos/logstouch /home/docker/nacos/conf/application.propert…

举个栗子!Tableau 技巧(280):创建点象限图( Dot Quadrant Chart )

之前分享过 🌰 :四象限图 和 葡萄干布丁图。今天,我们将两者的呈现方式结合起来,创建如下的点象限图( Dot Quadrant Chart ),可以帮助数据粉在有限的看板区域内展示更多的数据信息。 那么,如何在 Tableau 中…

一文弄清Java的四大引用及其两大传递

开场白 Hello大家好呀,我是CodeCodeBond✊最近在复习很多很多的基础知识,有了很多新的感悟~ 话不多说,直接发车✈ 四大引用 问题切入点 在学习 Thread线程利用ThreadLocalMap实现线程的本地内存(变量副本)的时候&…

简单的docker学习 第1章 docker 概述

Docker 学习笔记 本文是b站动力节点docker学习视频的笔记整理,主要用于自己学习复习使用,视频具体地址为 : 动力节点docker 第一章 docker 概述 1.1 课程引人入 1.1.1 开发/运维互掐 ​ 开发与测试和运维间的矛盾,主要是由于环境的不同而…

flutter 做代码混淆

第一种、手动混淆 修改代码中出现次数多的 类目 方法 。修改静态资源的名字,转静态资源为webp 第二种、使用flutter 自带的命令行工具进行混淆 混淆 Dart 代码 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 使用pragma(vm:entry-point) 装饰器修改方…

【界面开发实战】使用DevEco Studio编写支付宝首页

效果展示 知识点 层叠布局 上一篇文章已经介绍了,这篇文章中不再赘述,如果想了解的话可以去看上一篇文章,链接如下: http://t.csdnimg.cn/CnBZMhttp://t.csdnimg.cn/CnBZM 弹性布局 作用:提供更加有效的方式对容器…

YOLOV5 改进:替换backbone为MobileVIT

1. 介绍 yolov5替换主干网络的步骤如下,依旧和之前的一样 2. 更改common文件 将下面代码加入common最下面即可: from einops import rearrange import torch import torch.nn as nn# Transformer Attention模块定义 class TAttention(nn.Module):def __init__(self, dim, …

string的底层简单实现(造轮子)

文件:String.h ----- 头文件 String.cpp ----- 源文件 Test.cpp ----- 源文件 实现细节: 实现带参构造: 在实现带参构造建议不使用初始化列表,初始化去写不太好: :_str(new char[strlen(str)1]) 用初始化列表要在…

如何在 Jupyter Notebook 中直接设置全局随机种子的方法及易错地方、notebook和pycharm中设置随机种子的区别

结论: 在 Jupyter Notebook 中直接设置全局随机种子的方法是确保每个单独的代码块中都调用相同的 set_seed 函数。这是最简单且有效的方法。在每个代码块开头设置随机种子,确保代码在每次执行时具有相同的随机数生成顺序。 易错地方: …

mac配置git的sshkey

在MAC中配置Git的SSH Key: 1.打开终端 2.生成SSH密钥,输入以下命令: ssh-keygen -t rsa -b 4096 -C “你自己的账号电子邮件地址” 按回车键后,系统会提示你输入文件保存路径,默认为~/.ssh/id_rsa直接按回车键使用默…

数据结构初阶之排序(上)

排序的概念及其应用 排序的概念 排序:所谓排序,就是使⼀串记录,按照其中的某个或某些关键字的⼤⼩,递增或递减的排列起来的操作。 排序的应用 如下图: 样例数组 下面我们给出一组乱序的数组,接下来的算…

程序员进阶架构知识体系、开发运维工具使用、Java体系知识扩展、前后端分离流程详解、设计模式开发实例汇总专栏分享

场景 作为一名开发者,势必经历过从入门到自学、从基础到进阶、从学习到强化的过程。 当经历过几年企业级开发的磨炼,再回头看之前的开发过程、成长阶段发现确实是走了好多的弯路。 作为一名终身学习的信奉者,秉承持续学习、持续优化的信念…

GitHub推出全新AI模型平台:简化开发者体验

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…