NVIDIA Omniverse和Isaac Sim笔记4:OmniGraph与键盘操控机器人

Dec 8,2022   11413 words   41 min

Tags: SLAM

在上一篇笔记中,我们搭建了一个简易的机器人,并利用Synthetic Data Recorder进行了简单的数据录制。然而也正如结尾提到的,依然有很多没有解决的问题。比如说我们如何更加“优雅”地控制机器人(比如用键盘),而不是将其速度设为恒定。而要回答好这个问题,就需要一些其它知识。因此在进一步研究之前,我们简单了解一下OmniGraph。

1.OmniGraph简介

什么是OmniGraph?根据官方文档介绍,OmniGraph是整个Omniverse的计算引擎。在Omniverse中,OmniGraph又可以分为Action Graph、Particle Graph等。而在现阶段,对我们最重要的就是Action Graph,一种允许基于事件行为的图(也是本篇笔记的重点)。当构建好了Action Graph后,我们就可以根据一个事件启动另一个事件。既然是一个“图”,那么不可避免的应该由节点和边构成,如下图所示。 在Action Graph中,边可以看作是节点之间的关联关系或者是数据流动,而节点可以看作是要操作的对象(比如立方体、圆柱体等)、变量(比如double类型的静态常量等)或者动作(加减乘除运算等)。所以从某种角度上来说,Action Graph也可以看作是一种可视化编程:当满足某些条件的时候,场景中的物体根据你设计的逻辑执行设计好的事件。在下面的部分,我们会进一步介绍OmniGraph的使用。

2.OmniGraph中的节点

前面说了,既然是“图”,那么不可避免就会有各种节点。在Action Graph中,节点的类型是有限的,完整列表可以参考官方文档。在Isaac Sim中部分分类展示如下。 有Constants Nodes、Event Nodes、Input Nodes、Math Nodes、Replicator Nodes、Scene Graph Nodes等。当需要某种功能的时候,可以尝试去寻找相应节点。

3.OmniGraph的初步使用

3.1 Action Graph面板的打开

在Isaac Sim或Omniverse Creator中,我们需要先加载Action Graph的Extension,如下图所示。 然后,再点击菜单栏中的Windows->Visual Scripting->Action Graph即可打开面板,如下所示。 之后我们编辑、新建Action Graph都是在这个面板里执行的,而面板左边就是各种上面展示的可选的各种Node。下面我们就以实际例子为主,对Action Graph进行介绍。其实Action Graph构建的流程并不复杂,没什么需要理解的东西。可能难就难在这种基于Action Graph的思考方式,需要自己多尝试才能理解。

3.2 小例子1:改变立方体位置
3.2.1 需求分析

我们先从简单的例子开始:场景中有一个立方体,我们现在需要通过Action Graph,使其不停沿x轴运动。这个例子参考了这个官方文档,但有所简化和改动。

首先可以简单分析一下这个需求。我们的输入是立方体的位置,然后需要在此基础上对其x坐标加上一定常量,并将改变后的x坐标重新赋给这个立方体。然后重复上述过程。进一步,我们首先需要一个节点来获取这个立方体的当前坐标,然后还需要一个常量节点来指明改变量。同时需要一个“加法”节点,来将获取到的立方体位置与位置改变量相加。最后,将结果节点的数值赋给立方体,也就是还需要一个写入立方体位置的节点。简单总结如下:

  • 立方体位置读取节点
  • 位置改变量数值节点
  • 加法节点
  • 立方体位置写入节点

如果我们按照上面的节点构造了Action Graph能不能运行呢?答案是否定的。因为我们前面说了,Action Graph是基于事件触发的,如果没有触发事件,这个Action Graph永远也不会被执行。那问题是,我怎么让Isaac Sim每一帧都触发/执行上面构造的Action Graph呢?答案也很简单。Omniverse已经贴心地为我们准备好了On Playback Tick节点,如下图所示。 当我们运行仿真环境的时候,这个节点每一帧都会被触发。所以,我们就可以根据它来实现我们真正的Action Graph。

3.2.2 Action Graph搭建

我们首先在场景中新建一个立方体,并按照上面的步骤打开Action Graph面板,如下所示。 然后,我们点击面板中的New Action Graph即可新建一个Action Graph。根据上面的分析,首先要添加立方体位置读取节点,具体做法是将Stage里的Cube拖拽到Action Graph面板中,然后选择“Read Prim Attribute”,就可以创建一个读取立方体属性的节点了。然而,这个时候我们并没有指定让这个节点读取立方体的哪个属性,所以我们选中新建出来的节点,在右边的属性面板中找到“Attribute Name”属性,在下拉列表中选择xformOp:translate。这个属性是表示立方体的空间位置。动图演示如下。 然后,我们在Action Graph面板左边的搜索框中搜索Constant Point3d节点,并将其拖拽到面板中,如下。 然后选中该节点,将其Inputs属性中的x修改为1(因为根据需求是沿x轴运动)。然后,我们再搜索Add节点,并将其拖拽到面板中,如下。 最后,我们要将修改后的结果赋给立方体,和前面类似的,我们拖拽Stage面板中的Cube,只不过这里我们选择“Write Prim Attribute”,如下。 当然,类似的,我们也要指定写入哪个属性,在属性面板中选择xformOp:translate即可。

在节点基本构建好以后,下一步就是连线。首先,将获取到的立方体位置与改变量作为输入给Add节点,如下所示。 最后,将加法的输出给位置写入节点,这里我们连接到该节点的Value属性上,如下图所示。 这样,Action Graph就构造好了。但如果你点击工具栏中的运行按钮会发现不会有任何变化。因为前面我们说了,这个Action Graph没有被触发。所以我们添加Tick节点。搜索tick,找到On Playback Tick节点,然后,连接它的Tick输出给Write Prim Attribute的Exec In,如下所示。 这样,我们的Action Graph就真正构建好了。

3.2.3 效果展示

点击运行按钮,就会看到如下效果。 可以看到立方体的translate属性实时发生了变化,说明我们的Action Graph是正常工作的。

3.3 小例子2:立方体按键自动跟踪

上面我们实现了方块的自动移动,但这显然不能够满足我们的要求。比如,我们还可以通过按键盘上的某个键,使得其自动跟踪到目标。

3.3.1 需求分析

尽管我们可以人工设置立方体的位置等实现这个功能,但在Omniverse中预置好了Move To Target节点,可以轻松让我们实现这个功能。而至于按键事件的触发,同样有On Keyboard Input节点。所以实现起来非常简单。

3.3.2 Action Graph构建

首先,我们在场景中创建两个立方体,为了区别,其中一个可以使用不同的材质,如下。 然后,点击Action Graph面板中的“New Action Graph”新建Action Graph。搜索找到Move To Target节点并添加,然后在它的属性中分别找到sourcePrim和targetPrim,分别设置成刚刚新建的两个立方体,如下。 这样其实就可以完成我们要的功能,只是缺少触发。所以我们添加On Keyboard Input节点,如下,并指定案件为R,如下。 最后,将On Keyboard Input节点的Pressed输出连接到Move To Target节点的Execute In输入即可,如下。

3.3.3 效果展示

运行仿真环境,然后按R,就会看到如下效果。 通过这个例子,我们便实现了仿真环境与键盘的交互。可以看到,这里的Move To Target其实不仅仅是位置发生了变化,连同姿态、甚至是形状都发生了变化。

3.4 小例子3:立方体固连跟随

在实际应用中,可能还存在这样的情形:我们有一个立方体和一个圆柱体,当我们移动立方体的时候,圆柱体也要跟立方体保持固定的相对位置关系,一起移动。

3.4.1 需求分析

如果只是单论这个需求,我们可以将立方体和圆柱体都放在同一个Xfrom下,然后移动Xfrom就会同时移动这两个物体,如下所示。 或者像这篇笔记说的,利用各种Joint也可以实现共同移动的功能。不过在这里,我们希望实现的是基于实时结算的固连跟随。比如我们以立方体作为目标,让圆柱体实时跟随立方体。实现思路也很简单。首先,实时获取立方体的位置,然后将其与我们设定好的固定偏移量相加,得到圆柱体应该所在的位置,最后,把这个位置赋给圆柱体,从而实现目标。具体而言,应该会包含以下节点:

  • 立方体位置读取节点
  • 固定偏移量数值节点
  • 相加运算节点
  • 圆柱体位置写入节点
  • 触发节点

明确了以上的流程以后,就可以构建图了。

3.4.2 Action Graph构建

首先在场景中新建一个立方体、一个圆柱体,让圆柱体和立方体在X轴方向上保持150距离,如下所示。 然后,将Cube拖入Action Graph中,建立“Read Attribute”节点,并选择属性为xformOp:translate,如下。 然后,新建Constant Point3d节点,将其x属性设为150,如下。 然后,添加Add运算节点,将两者作为输入,如下。 然后,将圆柱体拖入图中,选择“Write Attribute”,设置属性为xformOp:translate,并接收相加的结果,如下。 最后一步,添加触发节点,并连接到Write Prim Attribute的Exec In端口,如下。 至此,Action Graph构建完成。

3.4.3 效果展示

运行仿真环境,鼠标拖动立方体,如下。 可以看到,圆柱体实时跟随立方体运动。

3.5 小例子4:控制立方体运动速度

在前面的这篇笔记中,我们搭建了一个简易的双轮机器人,并通过增加一些Joint和修改属性,实现了基于双轮驱动的机器人恒速运动。这里我们不妨再简单分析一下这个例子的实现过程以及简单总结一下需要注意的地方。首先,我们新建了两个圆柱体作为车轮,一个立方体作为身体,然后给每一个物体都添加了物理属性(Rigid Body with Colliders Preset)。然后,分别将左右两个轮子以Revolute Joint连接起来。这里需要说明的是,Revolute Joint必须连接两个或多个具有物理属性的物体,如果其中任何一个缺乏物理属性,就会导致无法正常工作(可以正常添加,但运行时会报错)。添加Revolute Joint时,要选中两个物体。而这两个物体的选择顺序会导致不同的结果,因为这对应了Revolute Joint的Body 0和Body 1属性(同样两个物体,选择顺序不同,最后新建出来的Joint的位置是不同的,你可以试一试)。选择的原则是,Body 0是主体,Body 1是绑定到主体上的旋转的物体。比如在这个例子中,机器人身体就是主体,而车轮应该是关联到它上面的。所以,选择的顺序应该是先选择立方体,再选择圆柱体,而不是反过来。或者再总结一个好记的规则就是:旋转的物体最后选。添加好Revolute Joint以后,我们分别给这两个Joint添加了Angular Drive属性。通过这个属性,我们就可以给Revolute Joint设置Target Velocity,最终实现车轮的旋转,使得机器人运动起来。那么,我们能否用Action Graph实现这个功能呢?答案是肯定的。

3.5.1 需求分析

我们要让小车动起来,本质上来说是通过设置车轮的旋转速度实现的,而车轮的旋转速度设置又是通过改变Joint的速度实现的。所以,我们首先应该需要一个节点,这个节点的功能是将速度设置给指定的Joint。在Isaac Sim中,这个功能可以通过Articulation Controller实现。需要说明的是,这个节点的作用对象是各种Joint,而不是各种几何实体,这点需要注意。这个节点的输入有两个:一个是要控制的Joint的名称(单个或多个),另一个是需要设置的速度。对于第一个输入,我们并不能直接在Articulation Controller的属性里修改,它接收的是Array类型的Joint名称,而它可以通过Make Array节点构造。Make Array节点的输入就是Joint Name了。在Action Graph中,可以用Constant Token来表示。Articulation Controller的另一个输入是速度,但实际而言,并非是一个三维的向量,而是叫做Velocity Command。这个Velocity Command可以由Differential Controller节点构造出来。我们可以在这个Differential Controller节点的属性中,设置速度。最后,我们需要On Playback Tick节点来触发整个Action Graph。所以,需要的节点列举如下:

  • Articulation Controller节点:用于速度赋值
  • Make Array节点:将Joint Name整合成一个Array
  • Constant Token节点:代表要控制的Joint名称
  • Differential Controller节点:构造Velocity Command
  • On Playback Tick节点:触发整个Action Graph
3.5.2 Action Graph构建

首先,构造出两个圆柱体和一个立方体,分别作为车轮和车身,并且赋予几何实体物理属性。然后,将车轮和车身通过Revolute Joint进行了连接。如下图所示。 然后,我们在Action Graph中首先添加Articulation Controller节点,如下。 在节点属性中,输入robotPath,或者把usePath前面的勾去掉,在上面的“Add Target(s)”中手动选择。然后再添加Make Array和Constant Token节点。Token节点分别在属性中修改成要控制的Joint名称。然后按照上面的分析,将这几个节点连接起来,如下。 然后添加Differential Controller节点,并在节点中设置好相应的角速度、线速度、轮子的半径等参数。因为我们这里没有实际的物理模型,只是简单测试,可以填个差不多的数值就行。填写完成以后,将它的Velocity Command输出端口连接到Articulation Controller的对应输入端口上即可,如下。 最后,添加触发节点On Playback Tick,并分别给Articulation Controller和Differential Controller,如下。 这样,Action Graph就构造好了。

3.5.3 效果展示

运行仿真环境,效果展示如下。 可以看到,车轮是在转动的,说明我们的Action Graph构造是成功的。当然当立方体倒下来以后就不动了,这是因为我们没有设置其它的一些物理仿真参数,模型也不够真实。在后面的例子中我们可以看到,采用这种方式是可以正常控制小车的。

3.6 小结

以上通过四个例子,我们对于Action Graph有了初步的了解,基本掌握了Action Graph控制场景中物体的一般流程。下一部分,我们将构建一个Action Graph,并利用它通过键盘实际控制一个真实的机器人(由Isaac Sim提供)。

4.真实的轮式机器人仿真控制

在这里,我们以Isaac Sim提供的Jetbot为例进行讲解。

4.1 设置环境与插入机器人

首先,给整个场景插入Ground Plane,防止机器人掉下去。然后在主界面下方的Isaac Assets中找到Jetbot,并拖入场景,如下所示。 在Stage面板中可以看到,机器人有两个轮子,对应于两个Revolute Joint。我们之后要控制机器人运动,就是要控制这两个Joint。

4.2 构建Action Graph

和上面的例子4是一样的,我们首先插入Articulation Controller,并且将其robotPath属性设为/World/jetbot(也即我们插入的JetBot的名字),如下。 然后,我们要分别插入两个Constant Token(Value属性分别设置为left_wheel_jointright_wheel_joint)和一个Make Array节点,再将其和Articulation Controller连接起来,如下。 然后,再插入Differential Controller用来生成Velocity Command,并在它的属性中设置相关数值,如下所示。 这里,JetBot是真实的机器人,所以它的wheelDistance和wheelRadius是固定的,分别为0.1125和0.03。而角速度和线速度则可以根据喜好自行设置,比如我们这里分别设置为1.5和1。然后将它输出的Velocity Command连接到Articulation Controller的对应输入节点上即可。最后,我们添加触发节点,并且分别给Differential Controller和Articulation Controller发送信号,如下。 这样,Action Graph就构造完成了。

4.3 运行测试

运行仿真环境,就可以看到机器人在做圆周运动了,如下。 当然,你可以尝试调整不同的速度,看看效果。比如把线速度降为0,那么机器人就会原地打转,而如果把角速度降为0,机器人就会直线运动。当然,你也可以打开机器人自带相机的Camera View。

4.4 举一反三

事实上,上面介绍的步骤是一种通用的控制机器人运动的方法。换句话说,只要是这种类似的轮式机器人,都可以通过这种方法进行控制。比如,我们在Isaac Assets中找到一个新的叫做aws_robomaker_jetbot的机器人,插入场景。找到两个轮子对应的Revolute Joint的名字,如下。 可以看到分别叫做left_wheel_hingeright_wheel_hinge。我们只需要在Action Graph中找到相关变量并修改即可,然后就可以实现类似的控制了,如下。 甚至来说,我们可以将jetbot和aws_robomaker_jetbot同时插入场景中,同时控制它们。这时候我们需要分别为两个机器人准备两套Controller,Action Graph如下所示。 可以看到,整个Action Graph分为上下两部分,上面一部分用于控制aws_robomaker_jetbot,下面一部分控制jetbot,它们共享一个触发器。运行的效果如下。

5.用键盘控制机器人运动

在上面的所有例子中,我们都是设定了某种恒定速度,让机器人去走,可是在实际应用中这显然是不行的。也正如同本文开头提出的问题,如何用键盘控制机器人的运动。而我们说,可以通过Action Graph来实现。所以这一部分,我们就介绍利用Action Graph实现对于机器人的控制。

5.1 场景准备

类似的,我们在场景中增加一个Jetbot机器人,然后就可以尝试构建Action Graph了。

5.2 Action Graph构建

相比于上面的示例,其实这里我们只需要想办法将Differential Controller中的速度属性修改就可以了,并且这种修改通过键盘来实现。所以一个简单的想法是,首先添加一些节点,使得仿真环境能够接收键盘的输入。然后根据这些输入,计算对应的速度,最终赋给Differential Controller,实现键盘操控。首先,我们先把设置速度恒定的Action Graph搭好,如下所示。 这里,因为我们要通过键盘设置速度,所以线速度和角速度都设为0。

下面我们的核心就是如何由键盘的输入计算对应速度。对于键盘的输入,我们可以通过On Keyboard State节点输出的Is Pressed属性来获取。这个属性其实是一个0-1布尔类型的值,表示指定的某个键是否处于被按的状态。为了做数值计算,可利用To Double节点将这个0-1的布尔输出转换成double类型。然后将这个数值与一个常量相乘,那么就可以得到新的数值,这个数值要么为0,要么为这个常量。 在进一步讨论之前,还需要明确的是,用键盘控制速度到底怎么控制,至少需要回答两个问题:有没有速度上限、是静止假设还是匀速假设。 静止假设认为运动是靠外部信号的持续输入而维持的,如果没有外部信号的持续输入,物体最终都会静止。 匀速假设则认为当没有外部信号时,物体会永远保持当前运动状态(可能是运动或者静止),而非逐渐减速至静止。 所以可以有多种操控模式:一种是按一次速度增加固定的值,一直按一直加速(没有速度上限),松手则会保持当前状态(匀速运动假设)或者减速为0(静止假设);另一种是按一次速度增加固定的值,但存在最大值(速度上限),达到上限以后,不管按多少次(一直按),速度都不会改变,松手则会保持当前状态(匀速运动假设)或者减速为0(静止假设)。这两种控制模式都有一些实际的例子。比如前面介绍的AirSim平台,就属于没有速度上限、静止假设的例子。只要一直按着某个方向的键,汽车就会一直加速,没有上限。而松开按键,汽车就会在该方向上减速,直到为0。而像之前介绍的XTDrone的操控则是没有速度上限、匀速假设的例子。和AirSim相同之处是,每按一次按键,速度就会增加一点,没有上限。不同之处在于,如果没有任何输入,系统就会保持当前运动状态(静止或者匀速),而不会逐渐减速到静止。比如无人机在往前飞行,如果我们此时松开按键,无人机并不会逐渐减速直到静止,而是以当前速度一直向前飞行。而有速度上限的例子也是存在的。比如各种赛车游戏(如QQ飞车)。其操作逻辑是,当按住某个方向的按键的时候,小车会逐渐加速到一个最大值,当达到这个最大值之后,再一直按也只是保持这个速度,而不会更大。当松开手的时候,速度会逐渐减小到0。所以在这里,这种赛车游戏就是属于存在速度上限、静止假设的例子。事实上,很多竞速类的游戏都是基于这个操作逻辑,比如原神里的浪船等。可以看出,不同的操作逻辑会有不同的实现方式。从仿真环境操控小车的角度来说,QQ飞车这种存在速度上限、静止假设的例子可能更加适合我们的需求。一方面,速度上限保证了帧间的变化不会太剧烈,对于人为操控更加友好、在一定程度上保证了数据采集的质量。另一方面,静止假设也在一定程度上模拟了真实世界的情况(因为涉及到动力学相关内容,所以实现起来也更加复杂一些)。所以,以向前运动为例,我们可以构建出如下节点: 这里,我们设置W为前向按键。每次按W,速度都设置为1.0,否则就为0。并将速度输出传给了Differential Controller的Linear Velocity。运行仿真环境,可以看到如下效果。 按W键的时候,机器人就运动,一直按就一直以设置速度运动,松开W键则会逐渐减速至停止。这样,我们就实现了前向的速度控制。类似的,我们还需要实现后向/倒退的速度控制。实现过程其实是类似的,我们只需要计算反向的速度(为前向速度方向的负向)即可。当然,这里需要注意的是,Differential Controller的Linear Velocity接口只支持一个输入。如果我们前向算了一个速度,反向算了一个速度,这两个速度必须要在传给Differential Controller之前就合成好。所以,我们需要使用Substract节点(因为速度方向相反,所以做减法),构建的Action Graph如下所示。 运行仿真环境,按W向前,S向后,效果如下所示。 这样,我们就实现了前后向的控制。而对于左右的控制,我们可以通过修改角速度实现。类似的,我们可以构造出下面的Action Graph。 按A键向左转,D键向右转,效果如下所示。

5.3 实际测试

运行仿真环境,左右方向再搭配适当的前后方向,就可以实现机器人在地面上的任意移动了。 甚至我们还可以利用Action Graph同时控制两个机器人(比如jetbot和aws_robomaker_jetbot)的移动,Action Graph如下所示。 虽然看起来可能有些乱,但其实结构还是很简单的。运行的效果如下所示。 可以看到两个机器人是“共进退”的。最后,我们添加一个真实的仓库场景(Isaac Sim官方展示视频中的那个),如下图所示。 在该场景中操作Jetbot机器人,如下。 需要注意的是,这个场景非常大,而Jetbot本身非常小,所以可能出现Jetbot太小而找不到的情况。这个时候放大一些,一般都可以找到的。或者在Stage面板中选中jetbot,这样就会有橙色的轮廓,更方便跟踪一些。 当然,还可以配合上次笔记提到的方法采集数据,如下。 保存下来的帧以帧号依次命名,格式默认为png。

6.Isaac Sim中自带的一些机器人

事实上,Isaac Sim中自带了非常多的机器人以及对应的使用案例,如果需要可以参考学习。这里简单列举展示几个机器人,如下图所示。 包括ANYMAL、Carter、Jetracer、Kaya、O3dyn、Transporter等。关于相关机器人的更多使用,之后需要的话再做介绍。

7.小结

本篇笔记,我们重点介绍了我们从机器人的控制开始,首先介绍了OmniGraph的相关知识,并以此介绍了对机器人的各种控制。然后进一步,学习了如何使用键盘操作双轮式机器人。到本篇笔记结束为止,我们就能够实现基本的环境搭建、机器人组装、机器人操作与控制、数据录制的步骤了。最终采集的数据就可以用于SLAM了。当然了,目前来说还有最后一个问题,那就是如何获取到机器人的位姿真值。尽管我们可以在仿真环境中操作机器人录制数据,但缺乏机器人本身的轨迹真值。关于这个问题,解决办法则是深度挖掘我们之前提到过的Replicator,我们在下篇笔记中再探讨。

8.参考资料

  • [1] https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_gui_omnigraph.html
  • [2] https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_omnigraph.html
  • [3] https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_omnigraph/tutorials/gentle_intro.html
  • [4] https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_omnigraph/tutorials/quickstart.html
  • [5] https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_omnigraph/node-library.html

本文作者原创,未经许可不得转载,谢谢配合

返回顶部