这是最近斯坦福的李飞飞团队的一篇论文:VoxPoser: Composable 3D Value Maps for Robotic Manipulation with Language Models
主要是通过大语言模型LLM和视觉语言模型VLM结合,来对机器人做各种日常操作,我们可以先来看下实际效果:大语言模型加视觉模型的通用机器人
可以看到在不同的实际场景中都可以很好的进行日常操作,而且具备对机器人不需要进行训练的优势。对于这篇论文的解读,尽量通俗的按照自己的理解来表达,希望对大家有帮助,当然水平有限,有误之处,欢迎指正,一起进步。
1、VoxPoser开发的初衷
在以往的机器人操作当中,我们都是需要先预定义轨迹,这就使得机器人变得比较局限,更重要的是大规模的机器人数据的获取都是比较困难的,这就限制了机器人领域的发展。而ChatGPT4的出色回答,让我们感到让机器人成为通用机器人成为可能,可以利用这样的LLM来进行推理,然后给出机器人一些有用的步骤,再通过VLM来规划路径,这样理论上就做到了,机器人可以通过自然语言而跟现实世界进行交互了。所以VoxPoser就被开发出来了,这个工作的目标就是合成机器人轨迹,也就是接下来将要介绍的使用6自由度的末端执行器规划路径点序列,还开放了指令集和对象集。
我们先来看一张VOXPOSER示意图:
左边是VoxPoser的概览图,右边是一些操作的演示。
可以看到我们是在大语言模型提取了支持和约束(这里的支持就是机器人的操作目标,下面也可能有叫功能或功能可见性等叫法,意思是一样的,约束就是机器人在操作过程中要避开的东西),也可能叫做回避,比如这里的“Open the top drawer, and watch out for that vase!”(打开最上面的抽屉,小心那个花瓶!),故这里的抽屉就是支持,花瓶就是约束。
得到的步骤如下:
1、应抓住最上面的抽屉把手
2、把手需要向外平移
3、机器人应远离花瓶
这些步骤再结合视觉语言模型,使用VOXPOSER提供的代码接口就能够规划出机器人的轨迹。
2、VoxPoser原理实现
上面我们大概介绍了VOXPOSER,接下来具体介绍VOXPOSER的原理,上面的示意图我们仔细观察左边那个机器人所在的空间,会发现每个区域的颜色是有区别的,看那个颜色条,左边是high cost,右边是high reward,也就是成本越高颜色是红颜色,高奖励的地方是蓝色,这样就会让机器人对此作出路径规划。
2.1、LLM+VLM
那具体是如何去实现的呢?同样的,来看一张示意图:
这里给出了两个函数的代码,affordance_map()和constraint_map(),分别表示需要操作的目标和需要避开的目标的映射。
大语言模型生成与视觉语言模型交互的代码,以此来生成基于机器人观察空间的一系列3D功能图和约束图(统称为值图),然后,合成的值图作为运动规划者合成机器人操作轨迹的目标函数。
从两个函数来看,大同小异,都是先初始化一个三维数组(张量)映射,然后各自检测目标detect('handle')和detect('vase'),不同点就是affordance_map是需要将目标对象的位置(top_handle.pos)都设置为1,在constraint_map里面是将检测到的对象所占用的格子(vase.occupancy_grid)的位置都设置为-1,最后两个分别返回其对应的值图。
经过上述处理之后,就有了Motion Planning运动规划:一种在机器人学和自动控制领域中用于规划物体在空间中的运动路径的方法,以避免碰撞并满足其他约束条件。
2.2、方法与公式
我们知道语言所表达出来的东西是比较自由的,或者说比较的宽泛,而机器人需要的是,具体的有针对性的操作指令。
比如说,“打开最上面的抽屉”,单纯根据这句话可能生成轨迹就很困难,所以我们将一个问题进行分解,拆分成具体的子任务,明确地去指定操作任务,可以分解为“抓住顶层抽屉的把手”和“拉开抽屉”,这种由高级规划器(如LLM或基于搜索的规划器)得到的具体子任务就变得可操作性了。
接下来就是将每个子任务进行优化,这里的子任务是LLM语言指令和机器人的运动路径,其中运动路径是由操作空间控制器执行的密集末端执行器路径点序列,每个路径点是由6自由度末端执行器姿态、末端执行器速度和夹持器动作组成。
我们将这些表述为如下的一个公式:
:环境状态的演化
:机器人轨迹
:相关的动力学和运动学约束,subject to 受制于的意思。
: 完成指令的程度进行评分
:指定控制成本,例如,鼓励 最小化总控制时间
通过对每个子任务求解这个优化问题,我们得到一系列机器人轨迹,这些轨迹共同完成指令L指定的总体任务。
对于自由形式的语言指令,计算是极具挑战性的,不仅因为语义语言可以传达的空间丰富,而且因为缺乏机器人标记数据。然而,我们提供了一个关键的观察结果,即在机器人的观察空间中,大量的任务可以用一个体素值映射来表征。这里的体素值可以理解成空间三维的数组或坐标,然后做映射。
“抓住顶层抽屉把手”(由LLMs推断)。“感兴趣的实体”是机器人的末端执行器,体素值映射应该反映对抽屉手柄的吸引力。通过进一步命令“小心花瓶”,地图也可以更新,以反映来自花瓶的排斥。
我们表示“感兴趣的实体”为e,其轨迹为,使用给定指令的体素值映射, 任务可以通过累加遍历的e值来近似,公式如下:
其中的是e在第j步的离散(x, y, z)的位置,由于这些位置是稀疏的,我们通过平滑操作来增加体素地图的密度,鼓励运动规划器优化更平滑的轨迹。
由于VoxPoser使用具有丰富常识的大语言模型来合成机器人轨迹,因此零射击合成轨迹 可以作为行动抽样分布 的有用的先验偏差,这可以显著加快学习过程。在实践中,这可以通过在 附近的采样动作来实现,通过添加小噪声ε来鼓励局部探索,而不是在整个行动空间中探索。
2.3、重建点云
LLM使用他们自己生成的代码,其中每个语言模型程序language model program(LMP)负责一个独特的功能(例如,处理感知调用)。本论文用的是OpenAI的GPT-4开放的API。
我们先来看下对比基线的实验结果:
可以看到VoxPoser有着很高的执行日常任务的成功率,不仅在可见任务有着高成功率,而且在不可见的任务中同样有着差不多的成功率。
具体有哪些操作呢?
给定来自LLM的对象/部件的查询,我们首先调用open-vocab检测器OWL-ViT获取边界框,将其馈入到Segment Anything (Meta的分割一切对象模型)获取掩码,并使用视频跟踪器XMEM (基于Atkinson-Shiffrin记忆模型的长视频对象分割)跟踪掩码。利用跟踪掩码与RGB-D观测相结合,重建目标或部分的点云。
2.4、值图(映射)与运动规划器
我们定义了以下类型的值图:affordance功能可见、avoidance回避、end-effector velocity末端执行器速度、end-effector rotation末端执行器旋转和gripper action抓取动作。
每种类型使用不同的语言模型程序(LMP),它接受指令并输出形状为(100,100,100,k)的体素图,其中k对于每个值图是不同的(例如,k=1用于功能可见和回避,k=4用于旋转)
我们将Euclidean distance欧氏距离变换应用于功能可见性的映射,高斯滤波器则应用于回避映射。在值映射LMP的基础上,我们定义了两个高级LMPs来协调它们的行为:planner规划器以用户指令L作为输入(例如,“打开抽屉”)并输出一系列子任务,然后composer以子任务为输入并调用相关的值映射LMPs,并进行详细的语言参数化。
接下来就是构造运动规划器,在规划器优化中只考虑可见性映射和回避映射,使用贪心搜索找到一系列无碰撞的末端执行器位置。然后我们通过剩余的值映射(例如,旋转映射,速度映射)在每个p上强制其他参数化。运动规划器使用的代价映射被计算为权值为2和1的归一化可视性和回避映射的加权和的负数,合成6自由度轨迹后,执行第一个航路点,然后以5Hz的频率重新规划新的轨迹。
具体来说,我们首先使用VoxPoser合成k个不同的轨迹,每个轨迹都表示为一系列末端执行器路径点。然后,我们学习了一个MLP动态模型,通过迭代过程从和预测,其中代理在数据收集和模型学习之间交替进行。在MPC的动作抽样分布中,我们加入,将初始合成轨迹作为先验到中的每个路径点,鼓励本地探索,使用这些轨迹作为先验探索,我们可以在不到3分钟的在线交互中学习有效的动力学模型,从而获得较高的最终成功率。相比之下,如果没有先验,学习动态模型是非常困难的,因为大多数动作不会导致有意义的环境变化。
3、应对突发状况
大模型的一些不可预测现象,如何应对,这里我们使用了LLM,有着丰富的世界知识。特别是,我们将研究重点放在VoxPoser独有的行为能力上。我们观察到以下能力:
1.估计物理性质:给定两个未知质量的块,机器人的任务是使用可用的工具进行物理实验,以确定哪个块更重。
2.行为常识推理:在机器人布置桌子的任务中,用户可以指定行为偏好,例如“我是左撇子”,这需要机器人在任务的上下文中理解其含义。
3.细粒度的语言纠错:对于“用壶盖盖住茶壶”等要求高精度的任务,用户可以给机器人精确的指令,比如“你向左偏差了1cm”,这种精确的要求也可以没有问题的。
4.多步可视化程序:给定一个“精确打开抽屉一半”的任务,由于对象模型不可用,信息不足,VoxPoser可以提出多步操作策略,根据视觉反馈,首先在记录手柄位移的同时将抽屉完全打开,然后将其关闭回中点以满足要求。
上述的应急能力,对应体现在如下图:
4、VoxPoser局限性
在这项工作中,我们提出了VoxPoser,一个通用的机器人操作框架,从LLM中提取功能和约束,为开放集指令和对象提供了显著的泛化优势。特别是,我们使用代码编写LLM与VLM进行交互,以组成基于观测空间的3D值图,用于合成日常操作任务的轨迹。此外,我们展示了VoxPoser可以通过有效地学习一个动态模型来完成丰富的接触任务,从而从在线交互中受益。
当然VoxPoser也存在下面几点的限制:
首先,它依赖于外部感知模块,这在需要整体视觉推理或理解细粒度物体几何形状的任务中受到限制。
其次,虽然适用于高效的动态学习,但仍然需要一个通用的动态模型来实现具有相同泛化水平的多接触任务。
第三,我们的运动规划器只考虑末端执行器轨迹,而全臂规划也是可行的,可能是更好的设计选择。
5、VoxPoser的Transforms3d
其中VoxPoser的核心就是向LLM提供代码支持,除了有Numpy和Transforms3d库之外,还提供了很多APIs
Transforms3d库的详细说明:
http://matthew-brett.github.io/transforms3d/index.html
安装transforms3d命令,带上阿里云镜像速度快点:
pip install transforms3d -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
再检验下是否安装成功:
import transforms3d
transforms3d.__version__
#'0.4.1'
dir(transforms3d)
#['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_version', 'affines', 'axangles', 'euler', 'quaternions', 'reflections', 'shears', 'taitbryan', 'utils', 'zooms']
这个库主要是矩阵变换、欧拉角、四元数等等的转换,具体详细用法可以看前面给出的链接。
6、VoxPoser的APIs
我们来看下VoxPoser提供了哪些对外接口:
detect(obj name):接受一个对象名并返回一个字典列表,其中每个字典对应于匹配对象的一个实例,包含中心位置、占用网格和平均法向量(正态均值向量)
execute(movable,affordance map,avoidance map,rotation map,velocity map,gripper map):接受“感兴趣的实体”作为“可移动的”(由detect返回的字典)和(可选的)值映射列表,并调用运动规划器来执行轨迹。请注意,在MPC设置中,“可移动”和输入值映射是可以重新评估以反映最新环境观察的函数,另外这里的感兴趣实体就是机器人的末端执行器,通过它的移动来操作目标。
cm2index(cm,direction):沿方向获取所需的偏移距离(以厘米为单位),并返回以体素坐标反映位移的三维向量
index2cm(index,direction):接受一个整数“索引”和一个“方向”向量,并返回被体素坐标中的“整数”所取代的世界坐标中的距离
pointat2quat(vector):为末端执行器获取所需的指向方向,并返回一个令人满意的目标四元数
set_voxel_by_radius(voxel_map,voxel_xyz,radius_cm,value):从“voxel_map体素图”中的“voxel_xyz”中为“半径”内的voxel体素分配值。
get_empty_affordance_map():返回初始化为0的功能可见性图(映射),其中高值将吸引实体。
其中对affordance的翻译比较多,有功能可见性,功能可供性,自解释性,启示等,初次接触可能有点懵,没有关系,这里的关键点是需要知道具体是什么意思就行了。意思是说这个东西自己呈现出来的功能,自然会联想到怎么去使用它,比如看到抽屉手柄,就会想着去拉或者推;看到家里的水龙头,就可能是旋转进行关停;看到按钮就知道是按压,这就是所谓的功能可见性。
get_empty_avoidance_map(): 返回初始化为0的避障图(映射),其中高值将排斥实体。
get_empty_rotation_map():返回用当前末端执行器四元数初始化的默认旋转映射。
get_empty_gripper_map():返回用当前抓取器动作初始化的默认抓取器映射,其中1表示“关闭”,0表示“打开”
get_empty_velocity_map():返回一个初始化为1的默认功能映射,其中数字表示比例因子(例如,0.5表示默认速度的一半)
reset_to_default_pose():重置机器人为休息姿势
7、Prompts提示
最后是将上述在日常操作任务中,需要用到的提示,以及一些不可见的属性之类的,分解成具体的步骤。当然目前站点对下面这些代码还没有公开,后期应该会很快开放。
7.1、planner计划器
接收用户指令L并生成一系列子任务,这些子任务被送入composer(注意,在模拟中不使用计划器,因为评估的任务由单个操作阶段组成)。
https://voxposer.github.io/prompts/real_planner_prompt.txt
摘要如下
import numpy as np
from env_utils import execute
from perception_utils import parse_query_obj
import action_utils import composer
objects = ['blue block', 'yellow block', 'mug']# ['蓝色块','黄色块','马克杯']
# Query: place the blue block on the yellow block, and avoid the mug at all time.
# 把蓝色块放在黄色块上,并且一直避免杯子
composer("grasp the blue block while keeping at least 15cm away from the mug")#抓住蓝色方块,同时与杯子保持至少15厘米的距离
composer("back to default pose")#回到默认姿势
composer("move to 5cm on top of the yellow block while keeping at least 15cm away from the mug")#移动到黄色方块上方5厘米处,同时与杯子保持至少15厘米的距离
composer("open gripper")# 打开抓取器
可以看到,提供的方法还是很清晰简洁的,首先就是保存需要关注和避开的objects对象列表,然后通过composer去解析每条具体的语言指令。
7.2、composer编写器
接受子任务指令,调用必要的值映射LMPs组成功能映射和约束映射。
下面都将分为模拟器和真实环境
simulation: https://voxposer.github.io/prompts/sim_composer_prompt.txt
real-world: https://voxposer.github.io/prompts/real_composer_prompt.txt
摘要如下:
import numpy as np
from env_utils import execute, reset_to_default_pose
from perception_utils import parse_query_obj
from plan_utils import get_affordance_map, get_avoidance_map, get_velocity_map, get_rotation_map, get_gripper_map
# Query: move ee forward for 7cm.
# 将对象ee向前移动7厘米
movable = parse_query_obj('ee')
affordance_map = get_affordance_map(f'a point 7cm in front of {movable.position}')
execute(movable, affordance_map)
7.3、parse_query_obj
接受对象/部件名称的文本查询并返回一个字典列表,其中每个字典对应于包含中心位置、占用网格和正态均值向量的匹配对象的一个实例。
simulation: https://voxposer.github.io/prompts/sim_parse_query_obj_prompt.txt
real-world: https://voxposer.github.io/prompts/real_parse_query_obj_prompt.txt
摘要如下:
import numpy as np
from perception_utils import detect
objects = ['green block', 'yellow line']
# Query: ee.
ee = detect('ee')[0]
ret_val = ee
objects = ['drawer', 'blue block', 'yellow block']
# Query: topmost handle.
# 顶层把手
handles = detect('drawer handle')
# topmost so sort by z, take the last one
# 最上面:按z排序取最后一个
handles = sorted(handles, key=lambda x: x.position[2])
top_handle = handles[-1]
ret_val = top_handle
7.4、get_affordance_map
从编写器接受自然语言参数化并返回NumPy数组用于任务提供映射。
simulation: https://voxposer.github.io/prompts/sim_get_affordance_map_prompt.txt
real-world: https://voxposer.github.io/prompts/real_get_affordance_map_prompt.txt
摘要如下:
import numpy as np
from perception_utils import parse_query_obj
from plan_utils import get_empty_affordance_map, set_voxel_by_radius, cm2index
# Query: a point 10cm in front of [10, 15, 60].
# 在[10,15,60]位置前面10cm处的一个点
affordance_map = get_empty_affordance_map()
# 10cm in front of so we add to x-axis
# 在前面10cm处,我们添加到x轴
x = 10 + cm2index(10, 'x')
y = 15
z = 60
affordance_map[x, y, z] = 1
ret_val = affordance_map
# Query: a point on the right side of the table.
# 表右侧的一个点
affordance_map = get_empty_affordance_map()
table = parse_query_obj('table')
(min_x, min_y, min_z), (max_x, max_y, max_z) = table.aabb
center_x, center_y, center_z = table.position
# right side so y = max_y
x = center_x
y = max_y
z = center_z
affordance_map[x, y, z] = 1
ret_val = affordance_map
7.5、get_avoidance_map
从编写器接受自然语言参数化并返回NumPy数组用于任务回避映射。
simulation: https://voxposer.github.io/prompts/sim_get_avoidance_map_prompt.txt
real-world: https://voxposer.github.io/prompts/real_get_avoidance_map_prompt.txt
摘要如下:
import numpy as np
from perception_utils import parse_query_obj
from plan_utils import get_empty_avoidance_map, set_voxel_by_radius, cm2index
# Query: 10cm from the bowl.
# 离碗10cm
avoidance_map = get_empty_avoidance_map()
bowl = parse_query_obj('bowl')
set_voxel_by_radius(avoidance_map, bowl.position, radius_cm=10, value=1)
ret_val = avoidance_map
7.6、get_rotation_map
从编写器中接受自然语言参数化,并返回末端执行器旋转图的NumPy数组。
simulation: https://voxposer.github.io/prompts/sim_get_rotation_map_prompt.txt
real-world: https://voxposer.github.io/prompts/real_get_rotation_map_prompt.txt
摘要如下:
import numpy as np
from plan_utils import get_empty_rotation_map, set_voxel_by_radius, cm2index, vec2quat
from perception_utils import parse_query_obj
from transforms3d.euler import euler2quat, quat2euler
from transforms3d.quaternions import qmult, qinverse
# Query: face the support surface of the bowl.
# 面朝碗的支撑面
rotation_map = get_empty_rotation_map()
bowl = parse_query_obj('bowl')
target_rotation = vec2quat(-bowl.normal)
rotation_map[:, :, :] = target_rotation
ret_val = rotation_map
7.7、get_gripper_map
从编写器接受自然语言参数化,并为抓手动作图返回NumPy数组
simulation: https://voxposer.github.io/prompts/sim_get_gripper_map_prompt.txt
real-world: https://voxposer.github.io/prompts/real_get_gripper_map_prompt.txt
摘要如下:
import numpy as np
from perception_utils import parse_query_obj
from plan_utils import get_empty_gripper_map, set_voxel_by_radius, cm2index
# Query: open everywhere except 1cm around the green block.
# 除绿色块周围1cm外,所有地方都打开
gripper_map = get_empty_gripper_map()
# open everywhere
gripper_map[:, :, :] = 0
# close when 1cm around the green block
green_block = parse_query_obj('green block')
set_voxel_by_radius(gripper_map, green_block.position, radius_cm=1, value=1)
ret_val = gripper_map
7.8、get_velocity_map
从编写器中接受自然语言参数化,并返回末端执行器速度图的NumPy数组。
simulation: https://voxposer.github.io/prompts/sim_get_velocity_map_prompt.txt
real-world: https://voxposer.github.io/prompts/real_get_velocity_map_prompt.txt
摘要如下:
import numpy as np
from plan_utils import get_empty_velocity_map, set_voxel_by_radius, cm2index
from perception_utils import parse_query_obj
# Query: faster when on the right side of the table and slower when on the left side of the table.
# 在桌子右边时速度更快,在桌子左边时速度更慢
velocity_map = get_empty_velocity_map()
table = parse_query_obj('table')
center_x, center_y, center_z = table.position
# faster on right side so 1.5 when y > center_y, slower on left side so 0.5 when y < center_y
velocity_map[:, center_y:, :] = 1.5
velocity_map[:, :center_y, :] = 0.5
ret_val = velocity_map
更多的细节,建议看论文原文,这里主要是个人对其的理解,有错误之处,还请指正。
引用:
voxposer:https://voxposer.github.io/
XMem:https://github.com/hkchengrex/XMem