系列概述

临近本科毕业,考虑到未来读研的方向以及自己的兴趣方向,我选择的课题大致为“基于VLA结构的指令驱动式机械臂仿真系统的实现”。

为什么说是VLA(Vision-Language-Action)“结构”,因为就目前而言,我认为在目前剩下的几个月时间内从0实现一个正儿八经的VLA模型,所需要的时间、资金、模型资源的获取都是比较麻烦的。因此,我选择使用ROS1+LLM+视觉算法来实现一个“伪VLA”结构,就目前阶段(开题一个月)而言,我能给出的场景示意如下:

场景:指令输入“机械臂将蓝色方块夹起放到红色圆柱体上面”,系统接收指令后,先通过视觉模块确定当前系统中各个物体的坐标,再通过开源大模型,结合已知坐标信息,通过预设的prompt生成动作序列,作为参数送入ROS1架构下的启动文件中,实现动作行为在gazebo下的仿真。

当前阶段我能给出粗糙的逻辑示意图如下:

接下来,我将给出目前阶段我所计划的步骤实现,之后该系列的博客都会依照下面的框架进行更新。由于我也是初次入门ROS以及深度学习相关的内容,所以本系列博客更多的充当学习笔记的作用,在书写过程中难免会出现错误以及天真的理论理解,还请各位指正。

我将该项目的实现分成下面几个步骤(每个步骤下的博客会一步一步地更新):

1. 实现机械臂在ROS1+Gazebo环境下的控制、仿真。目标效果是给出任意坐标的方块,机械臂要能稳定的抓取,并放置到指定的坐标。

该步骤博客目录如下:

https://blog.csdn.net/m0_75114363/article/details/156164226?spm=1001.2014.3001.5501
https://blog.csdn.net/m0_75114363/article/details/156166592?spm=1001.2014.3001.5502
https://blog.csdn.net/m0_75114363/article/details/156426200?spm=1001.2014.3001.5501
https://blog.csdn.net/m0_75114363/article/details/156544524?spm=1001.2014.3001.5502

2. 加入视觉模块与算法。目标效果是在仿真环境下,对于随意放置的方块,视觉系统需要计算出其真实坐标给予机械臂控制模块,使得机械臂能够实现对其的抓取与放置。

该步骤博客目录如下:

https://blog.csdn.net/m0_75114363/article/details/156641634?spm=1001.2014.3001.5502

3. 加入LLM(本地部署或使用API)。目标效果是对于输入的任意文本指令,LLM能根据预设的prompt,结合视觉系统给予的信息,给予执行模块对应的动作序列,使得机械臂正确地实现输入的文本指令想达到的效果,实现V-L-A的完整交互。

该步骤博客目录如下:

4. 实现整体系统的优化与完善,包括基于QT搭建软件前端、优化模型外观、加入更复杂的机械臂、实现更复杂的指令解析与运行。

该步骤博客目录如下:

我所使用的环境如下:

1. 系统:Ubuntu20.04

2. ROS1:Noetic

项目地址:

https://github.com/Dukiyaaa/Cmd2Action_ROS1

此外,额外说明一下为什么这个项目会选择ROS1做框架而不是更现代的ROS2。其实我在初期也是用的ROS2的框架,但发现机械臂夹爪始终无法夹起物体,网上相关的机械臂开源资料基本都是基于ROS1的,加上ROS1提供了grasp_fix插件可防止物体掉落,所以最终我选择了ROS1做项目框架,同时也希望自己在后期能够在ROS2上成功迁移项目。


章节概述

本章的题目为“在ROS1环境下建模机械臂”,具体来说,包含以下几部分内容:

1.环境搭建

2. 建立机械臂的URDF模型

3. 实现在Rviz中显示机械臂

4. 实现在Gazebo中显示机械臂


1. 环境搭建

我的开发环境为Ubuntu20.04+ROS1-Noetic,对于环境搭建,可直接参考下面两个视频:

1. Ubuntu系统搭建:

https://www.bilibili.com/video/BV1Z5pgzAEH7/?spm_id_from=333.1391.0.0

Windows和Linux双系统的保姆级安装教程,新手小白跟着也能装_windows安装linux双系统-CSDN博客

2. ROS1环境搭建:

https://tars-cat.github.io/docs/tutorial-basics/ros/ros_noetic_install/

1.1. 搭建工作区

如下示例,使用该指令可以创建一个ROS1功能包,使用包来管理我们的代码是一种规范的开发习惯:

catkin_create_pkg 包名 rospy std_msgs

观察我的仓库可以发现,还有别的功能的包,但创建的指令都大差不差,区别仅在于名字。截止目前我的项目结构为:


2. 建立机械臂的URDF模型

2.1. URDF与Xacro

什么是URDF,下面是其百度定义:

URDF全称为Unified Robot Description Format,中文可以翻译为“统一机器人描述格式”。与计算机文件中的.txt文本格式、.jpg图像格式等类似,URDF是一种基于XML规范、用于描述机器人结构的格式。根据该格式的设计者所言,设计这一格式的目的在于提供一种尽可能通用(as general as possible)的机器人描述规范。

简单来说,urdf就是一种专门用在机器人上的描述语言,与html类似,其由各种闭合的标签组合而成。通过urdf,可以让各类仿真器读出机器人的外观以及相关特性,进而进行进一步的仿真。

而所谓Xacro,则是类似于C语言中宏定义一样的存在,其通过给URDF块起名字,起到了复用机器人组件的作用,降低了代码量。

2.2. 我的URDF

我的URDF参考了这个项目:

yangliu28/two_scara_collaboration: Simulation of collaboration of two SCARA robots with collision avoidance. (github.com)

下面是截止到目前(12.22),我的部分机器人描述文件:

base.urdf.xacro:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    <xacro:macro name="base_xacro">
        <link name="world"/>
        <joint name="joint_fix" type="fixed">
            <parent link="world"/>
            <child link="base_link"/>
        </joint>

        <link name="base_link">
            <collision>
                <origin xyz="0 0 0.2" rpy="0 0 0"/>
                <geometry>
                    <cylinder length="0.4" radius="0.1"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 0 0.2" rpy="0 0 0"/>
                <geometry>
                    <cylinder length="0.4" radius="0.1"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 0 0.2" rpy="0 0 0"/>
                <mass value="2.0"/>
                <inertia ixx="0.0317" ixy="0.0" ixz="0.0" iyy="0.0317" iyz="0.0" izz="0.01"/>
            </inertial>
        </link>
        <gazebo reference="base_link">
            <material>Gazebo/White</material>
        </gazebo>

        <joint name="rotation1" type="revolute">
            <parent link="base_link"/>
            <child link="arm1"/>
            <origin xyz="0 0 0.4" rpy="0 0 0"/>
            <axis xyz="0 0 1"/>
            <limit effort="20" velocity="2.0" lower="-2.6" upper="2.6"/>
            <dynamics damping="0.1" friction="0.05"/>
        </joint>

        <link name="arm1">
            <collision>
                <origin xyz="0 -0.5 0.05" rpy="0 0 0"/>
                <geometry>
                    <box size="0.1 1.1 0.1"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 -0.5 0.05" rpy="0 0 0"/>
                <geometry>
                    <box size="0.1 1.1 0.1"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 -0.5 0.05" rpy="0 0 0"/>
                <mass value="1.5"/>
                <inertia ixx="0.1525" ixy="0.0" ixz="0.0" iyy="0.0025" iyz="0.0" izz="0.1525"/>
            </inertial>
        </link>
        <gazebo reference="arm1">
            <material>Gazebo/White</material>
        </gazebo>

        <joint name="rotation2" type="revolute">
            <parent link="arm1"/>
            <child link="arm2"/>
            <origin xyz="0 -1.0 0.1" rpy="0 0 0"/>
            <axis xyz="0 0 1"/>
            <limit effort="15" velocity="2.0" lower="-3.00" upper="3.00"/>
            <dynamics damping="0.1" friction="0.05"/>
        </joint>

        <link name="arm2">
            <collision>
                <origin xyz="0 -0.4 0.05" rpy="0 0 0"/>
                <geometry>
                    <box size="0.1 0.9 0.1"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 -0.4 0.05" rpy="0 0 0"/>
                <geometry>
                    <box size="0.1 0.9 0.1"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 -0.4 0.05" rpy="0 0 0"/>
                <mass value="1.0"/>
                <inertia ixx="0.0683" ixy="0.0" ixz="0.0" iyy="0.00167" iyz="0.0" izz="0.0683"/>
            </inertial>
        </link>
        <gazebo reference="arm2">
            <material>Gazebo/White</material>
        </gazebo>

        <joint name="gripper_joint" type="prismatic">
            <parent link="arm2"/>
            <child link="gripper_link"/>
            <origin xyz="0 -0.8 0" rpy="0 0 0"/>
            <axis xyz="0 0 1"/>
            <limit effort="10" velocity="0.1" lower="-0.8" upper="0.0"/>
            <dynamics damping="0.1" friction="0.05"/>
        </joint>

        <link name="gripper_link">
            <collision>
                <origin xyz="0 0 -0.1" rpy="0 0 0"/>
                <geometry>
                    <cylinder length="0.2" radius="0.02"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 0 -0.1" rpy="0 0 0"/>
                <geometry>
                    <cylinder length="0.2" radius="0.02"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 0 -0.1" rpy="0 0 0"/>
                <mass value="0.5"/>
                <inertia ixx="0.00172" ixy="0.0" ixz="0.0" iyy="0.00172" iyz="0.0" izz="0.0001"/>
            </inertial>
        </link>
        <gazebo reference="gripper_link">
            <material>Gazebo/White</material>
        </gazebo>

        <joint name="finger1_joint" type="prismatic">
            <parent link="gripper_link"/>
            <child link="finger1"/>
            <origin xyz="0 -0.04 -0.23" rpy="0 0 0"/>
            <axis xyz="0 1 0"/>
            <limit effort="20" velocity="0.05" lower="0.0" upper="0.02"/>
            <dynamics damping="0.5" friction="0.1"/>
        </joint>
        <link name="finger1">
            <collision>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.02 0.01 0.02"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.02 0.01 0.02"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <mass value="0.1"/>
                <inertia ixx="4.17e-6" ixy="0.0" ixz="0.0" iyy="6.67e-6" iyz="0.0" izz="4.17e-6"/>
            </inertial>
        </link>
        <gazebo reference="finger1">
            <material>Gazebo/White</material>
            <mu1 value="20.0" />
            <mu2 value="20.0" />
            <kp value="1000000000.0" />
            <kd value="1.0" />
        </gazebo>

        <joint name="finger2_joint" type="prismatic">
            <parent link="gripper_link"/>
            <child link="finger2"/>
            <origin xyz="0.04 0 -0.23" rpy="0 0 0"/>
            <axis xyz="1 0 0"/>
            <limit effort="5" velocity="0.05" lower="-0.02" upper="0.0"/>
            <dynamics damping="0.1" friction="0.01"/>
        </joint>
        <link name="finger2">
            <collision>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.01 0.02 0.02"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.01 0.02 0.02"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <mass value="0.1"/>
                <inertia ixx="6.67e-6" ixy="0.0" ixz="0.0" iyy="4.17e-6" iyz="0.0" izz="4.17e-6"/>
            </inertial>
        </link>
        <gazebo reference="finger2">
            <material>Gazebo/White</material>
            <mu1 value="20.0" />
            <mu2 value="20.0" />
            <kp value="1000000000.0" />
            <kd value="1.0" />
        </gazebo>

        <joint name="finger3_joint" type="prismatic">
            <parent link="gripper_link"/>
            <child link="finger3"/>
            <origin xyz="0 0.04 -0.23" rpy="0 0 0"/>
            <axis xyz="0 1 0"/>
            <limit effort="5" velocity="0.05" lower="-0.02" upper="0.0"/>
            <dynamics damping="0.1" friction="0.01"/>
        </joint>
        <link name="finger3">
            <collision>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.02 0.01 0.02"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.02 0.01 0.02"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <mass value="0.1"/>
                <inertia ixx="4.17e-6" ixy="0.0" ixz="0.0" iyy="6.67e-6" iyz="0.0" izz="4.17e-6"/>
            </inertial>
        </link>
        <gazebo reference="finger3">
            <material>Gazebo/White</material>
            <mu1 value="20.0" />
            <mu2 value="20.0" />
            <kp value="1000000000.0" />
            <kd value="1.0" />
        </gazebo>

        <joint name="finger4_joint" type="prismatic">
            <parent link="gripper_link"/>
            <child link="finger4"/>
            <origin xyz="-0.04 0 -0.23" rpy="0 0 0"/>
            <axis xyz="1 0 0"/>
            <limit effort="5" velocity="0.05" lower="0.0" upper="0.02"/>
            <dynamics damping="0.1" friction="0.01"/>
        </joint>
        <link name="finger4">
            <collision>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.01 0.02 0.02"/>
                </geometry>
            </collision>
            <visual>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <geometry>
                    <box size="0.01 0.02 0.02"/>
                </geometry>
                <material name="dark_gray">
                    <color rgba="0.4 0.4 0.4 1.0"/>
                </material>
            </visual>
            <inertial>
                <origin xyz="0 0 0" rpy="0 0 0"/>
                <mass value="0.1"/>
                <inertia ixx="6.67e-6" ixy="0.0" ixz="0.0" iyy="4.17e-6" iyz="0.0" izz="4.17e-6"/>
            </inertial>
        </link>
        <gazebo reference="finger4">
            <material>Gazebo/White</material>
            <mu1 value="20.0" />
            <mu2 value="20.0" />
            <kp value="1000000000.0" />
            <kd value="1.0" />
        </gazebo>

    </xacro:macro>
</robot>

scara.urdf.xacro:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="scara_robot">
    <xacro:include filename="$(find robot_description)/urdf/base.urdf.xacro"/>

    <xacro:base_xacro/>
</robot>

base.urdf.xacro可视作c语言中的头文件,我们真正给仿真器看的文件是scara.urdf.xacro,其调用了base.urdf.xacro定义的xacro宏,仿真器在接收到该文件后,可以自动解析,最后可转换为一个完整的urdf文件。

base.urdf.xacro描述了该机械臂的核心组成--其为一个scara类型的三轴机械臂,组件如下:

1. base_link: 基座,为一个半径0.1,长度0.4的圆柱体,与world之间由joint_fix固定

2. rotation1: joint1,base_link与arm1之间的连接件,为转轴结构,转动范围为-2.6-2.6rad,质心相对于joint_fix的坐标为0 0 0.4

3. arm1: 第一节臂,为一个0.1x1.1x0.1的长方体,质心相对于rotation1的坐标为0 -0.5 0.05

4. rotation2: joint2,arm1与arm2之间的连接件,为转轴结构,转动范围为-3.0-3.0rad,质心相对于rotation1的坐标为0 -1.0 0.1

5. arm2: 第二节臂,为一个0.1x0.9x0.1的长方体,质心相对于rotation2的坐标为0 -0.4 0.05

6. gripper_joint:joint3,为arm2与gripper_link之间的连接件,为垂直运动连接轴,质心相对于rotation2的坐标为0 -0.8 0

7. 剩余几个都是夹爪相关的描述内容,可大致理解为该夹爪由4个0.02 0.01 0.02的小方块构成,每个小方块的质心相对于gripper_joint在z轴和x/y轴上有不同的偏移。

下面给出机械臂在rviz和gazebo中的示意图,方便大家有个直观的感受:

rviz中:

gazebo中:


3. 实现在Rviz中显示机械臂

3.1. launch文件

核心launch文件如下(display.launch):

<?xml version="1.0"?>
<launch>
    <!-- 参数设置 -->
    <arg name="model" default="$(find arm_description)/urdf/scara.urdf.xacro"/>
    <arg name="gui" default="false"/>
    <arg name="rvizconfig" default="$(find arm_description)/config/urdf.rviz"/>

    <!-- 将 xacro 转换为 URDF 并设置到参数服务器 
    ROS 约定:robot_state_publisher、RViz 的 RobotModel 
    显示器默认都会从参数服务器读取 robot_description 这个名字的参数,
    里面应是完整的 URDF-->
    <param name="robot_description" command="xacro $(arg model)"/>

    <!-- 启动 joint_state_publisher 用于发布关节状态 -->
    <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher">
        <param name="use_gui" value="$(arg gui)"/>
    </node>

    <!-- 启动 robot_state_publisher 用于发布 TF 和状态 -->
    <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher"/>

    <!-- 启动 RViz -->
    <node name="rviz" pkg="rviz" type="rviz" args="-d $(arg rvizconfig)" required="true"/>
</launch>

在这个launch文件中,主要做了下面几个事情:

1. 获得scara.urdf.xacro和rviz配置文件urdf.rviz的路径

2. 通过xacro命令解析.xacro文件得到其内容,送入robot_description,这样模型才能在rviz中显示。

3. 发布关节状态发布节点joint_state_publisher和robot_state_publisher,这两个节点可以发布坐标变换关系,有了它们才可以在rviz中完整地展示机械臂

3. 启动rviz节点

要想启动该launch文件,需要在命令行中输入如下指令:

source ./devel/setup.bash & roslaunch arm_description display.launch

3.2. robot_state_publisher和joint_state_publisher

简单来说,joint_state_publisher用来发布关节状态,robot_state_publisher会订阅joint_state_publisher发布的关节状态,同时会解析URDF,计算出各个组建的坐标转换关系,当着一些列的信息整合起来送入rviz,才能成功地显示出机械臂模型。


4. 实现在Gazebo中显示机械臂

4.1. Rviz和Gazebo的区别

就我个人目前的理解而言,Rviz可以作为初期的建模查看工具,包括坐标变换、组建名称等都能在Rviz页面中看到,但它不能动;Gazebo则提供了运动驱动,我们可以在Gazebo中实现真实的运动仿真。

4.2. launch文件

核心launch文件如下(gazebo.launch):

<?xml version="1.0"?>
<launch>
    <!-- args -->
    <arg name="model" default="$(find arm_description)/urdf/scara.urdf.xacro"/>
    <arg name="world" default="$(find gazebo_ros)/worlds/empty.world"/>

    <!-- use simulation time from Gazebo -->
    <param name="use_sim_time" value="true"/>

    <!-- load robot_description by expanding xacro -->
    <param name="robot_description" command="xacro $(arg model)"/>

    <!-- load ros_control controllers -->
    <rosparam file="$(find arm_description)/config/controllers.yaml" command="load"/>

    <!-- start Gazebo (GUI) -->
    <include file="$(find gazebo_ros)/launch/empty_world.launch">
        <arg name="world_name" value="$(arg world)"/>
        <arg name="paused" value="false"/>
        <arg name="use_sim_time" value="true"/>
        <arg name="gui" value="true"/>
        <arg name="headless" value="false"/>
    </include>

    <!-- spawn the robot into Gazebo using robot_description -->
    <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" output="screen"
          args="-urdf -param robot_description -model scara_robot"/>

    <!-- start controllers (position control for two arms + prismatic) -->
    <node name="controller_spawner" pkg="controller_manager" type="spawner" output="screen"
        args="joint_state_controller rotation1_position_controller rotation2_position_controller gripper_position_controller finger1_position_controller finger2_position_controller finger3_position_controller finger4_position_controller"/>

    <!-- publish initial positions to rotate arm to +x direction (rotation1=pi/2=1.5708) -->
    <node name="init_positions" pkg="rostopic" type="rostopic" output="screen"
          args="pub -1 /rotation1_position_controller/command std_msgs/Float64 'data: 1.5708'"/>
    
    <node name="init_rotation2" pkg="rostopic" type="rostopic" output="screen"
          args="pub -1 /rotation2_position_controller/command std_msgs/Float64 'data: 0.0'"/>
    
    <node name="init_gripper" pkg="rostopic" type="rostopic" output="screen"
          args="pub -1 /gripper_position_controller/command std_msgs/Float64 'data: 0.0'"/>
</launch>

该launch文件做了以下几件事情:

1. 获取urdf、gazebo启动文件的地址

2. 解析urdf,获得机械臂构成信息

3. 加载ros_control(之后会用到)

4. 加载模型,初始化位姿


总结

本篇文章大致讲解了机械臂的组成以及如何在rviz和gazebo中建模,部分内容需要ROS1基础,由于我目前对整个系统理解还不够透彻,文章讲解不细致之处还请谅解。

代码段 小部件

Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐