【机械臂】【基本驱动】一、在ROS1环境下建模机械臂
系列概述
临近本科毕业,考虑到未来读研的方向以及自己的兴趣方向,我选择的课题大致为“基于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参考了这个项目:
下面是截止到目前(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基础,由于我目前对整个系统理解还不够透彻,文章讲解不细致之处还请谅解。
代码段 小部件
更多推荐


所有评论(0)