目前,验证连续控制强化学习算法的仿真环境,最常用的是Mujoco,但是需要license,学生邮箱可申请单机一年的免费使用权。同时,大部分情况下,如果使用Gym和Python,还需要安装Mujoco-py,安装过程都不友好。另外很重要的是,据个人了解,Mujoco原生版,也就是C++版的文档写的水平一般,Mujoco-py本身就没什么文档,只有几个example。
相比之下,PyBullet使用免费的Bullet引擎,安装过程也仅是pip install pybullet
这么简单。PyBullet的文档也是良心所在,文档几乎面面俱到,文档地址PyBullet Quickstart Guide,同时,每个API都提供了详细的example,还有PyBullet应用在Gym、强化学习算法、机器人等特殊目的的example。对于想用Bullet引擎做游戏和视频的小伙伴,Bullet的每个C++ API也是提供了详细的example。
总结起来就是,PyBullet上手难度更低,国外有博主说明年(2021年)PyBullet有可能会成为机器人强化学习领域的仿真环境的事实标准。
目前,在用PyBullet做仿真的强化学习项目越来越多,像
Deep Mimic
Agile Robottic Locomotion
Table Tennis Robot
tossingBot
和很多很多其他的项目。
同时,也有很多项目是用的Mujoco,具体可参考Reinforcement learning for robotics applications。
一波推销过后,说回正题,下面将一步步的说明pybulelt的使用。
我们将以Panda机器人为例。
PyBulelt使用URDF解析运动学、形状、动力学等特性。同时也支持Gazebo的SDF、Mujoco的MJCF。在pybullet_data文件夹,已经定义了很多机器人的描述文件。
我们首先在环境中添加一个机器人。我们需要os
模块来寻找目录中的文件;pybullet.connect(pybullet.GUI)
用来启动仿真;pybullet.stepSimulation()
运行一步仿真。
import os
import pybullet as p
import pybullet_data as pd
p.connect(p.GUI)
pandaUid=p.loadURDF(os.path.join(pybullet_data.getDataPath(),"franka_panda/panda.urdf"),useFixedBase=True)
while True:
p.stepSimulation()
当然如果不用os模块,也可以使用p.setAditionalSearchPath()
这个函数来做。
import pybullet as p
import pybullet_data as pd
p.connect(p.GUI)
p.setAdditionalSearchPath(pd.getDataPath())
pandaUid=p.loadURDF("franka_panda/panda.urdf",useFixedBase=True)
while True:
p.stepSimulation()
结果就是有个机器人出现了。
那为什么os模块和setAdditionalSearchPath()都可以实现相同的效果呢?
原因很简单。因为他俩做的是同样的工作。有点废话废键盘。
在Pycharm中,我们在import pybullet_data as pd
这句的pybullet_data上面按Ctrl+B,将会跳转到pybullet_data的定义上面。可以看到打开的是__init__.py文件。
哎,这不和上面用os的时候一样的操作吗。就是把pybullet_data文件夹添加进来。
同样,要注意到一个奇怪的现象,看看pybullet_data的安装位置。为什么没有安装在anaconda的env的site-package里面? 我也不知道,已经提交issue了(2020-8-30)。
issue erwin大神已经回了. 链接
我们打开pybullet_data文件夹看一下目录结构。里面全都是描述文件。
其实说到pybullet_data文件夹的位置比较奇怪,那看看pybullet的位置,那简直叫离奇了。在import pybullet as p
的pybullet上按Ctrl+B,跳转到了pybullet.py,看位置。
C:\Users\born_\AppData\Local\JetBrains\PyCharm2020.2\python_stubs\-520983747
什么玩意??黑人问号走起来。
pybullet.py中全是函数的定义,函数体都是pass,说明只是暴漏在外的API,还有封装好的文件。
看pybullet.py的前面几行注释,其中有一行
可以看到封装好的文件就是这个。pybullet的函数体就定义在里面。
下面,我们将机器人放在桌子上。在pandaUid下面添加这句。
tableUid=p.loadURDF("table/table.urdf",basePosition=[0.5,0,-0.65])
再添加一个盒子
trayUid=p.loadURDF("tray/traybox.urdf",basePosition=[0.65,0,0])
最后的代码就是
import pybullet as p
import pybullet_data as pd
p.connect(p.GUI)
p.setAdditionalSearchPath(pd.getDataPath())
pandaUid=p.loadURDF("franka_panda/panda.urdf",useFixedBase=True)
tableUid=p.loadURDF("table/table.urdf",basePosition=[0.5,0,-0.65])
trayUid=p.loadURDF("tray/traybox.urdf",basePosition=[0.65,0,0])
while True:
p.stepSimulation()
所有的模型都可以在pybullet_env
文件夹(文件夹的位置上面已经说到了)找到(可以将pybullet的Github仓库clone下来浏览一下目录结构)。
文件夹中全是.urdf或者其他描述文件,没办法一下子看出模型长什么样,除非把他们用loadURDF()
添加进来查看。但是呢,有更好的办法,走起,不通过ROS直接查看URDF文件。
我们继续添加模型。
objectUid=p.loadURDF("random_urdfs/000/000.urdf",basePosition=[0.7,0,0.7])
咋飘着? 模型飘了? 没加重力而已。
p.setGravity(0,0,-10)
在p.connect(p.GUI)
下面添加这句就行。
上面的截图肯定和刚运行出来的时候不一样,我是拉近了不知道多少才能看到这样,不然显示的效果离自己很远很小。
咋办?
调整视窗白。
p.resetDebugVisualizerCamera(cameraDistance=1.5,cameraYaw=0,\
cameraPitch=-40,cameraTargetPosition=[0.55,-0.35,0.2])
当然也可以自己调整一下参数值,看一下效果。比如这样也是可以的
p.resetDebugVisualizerCamera(cameraDistance=1,cameraYaw=0,\
cameraPitch=-40,cameraTargetPosition=[0.5,-0.9,0.5])
参数中cameraTargetPosition
指摄像机的位置,cameraDistance
指相机的焦距的变化,类似变焦。
完整代码
import pybullet as p
import pybullet_data as pd
# cameraTargetPosition=[0.55,-0.35,0.2]
p.connect(p.GUI)
p.setGravity(0,0,-10)
# p.configureDebugVisualizer(p.COV_ENABLE_SINGLE_STEP_RENDERING)
p.setAdditionalSearchPath(pd.getDataPath())
p.resetDebugVisualizerCamera(cameraDistance=1,cameraYaw=0,\
cameraPitch=-40,cameraTargetPosition=[0.5,-0.9,0.5])
pandaUid=p.loadURDF("franka_panda/panda.urdf",useFixedBase=True)
tableUid=p.loadURDF("table/table.urdf",basePosition=[0.5,0,-0.65])
trayUid=p.loadURDF("tray/traybox.urdf",basePosition=[0.65,0,0])
objectUid=p.loadURDF("random_urdfs/000/000.urdf",basePosition=[0.7,0,0.7])
object1Uid=p.loadURDF("random_urdfs/001/001.urdf",basePosition=[0.7,0.1,0.7])
object2Uid=p.loadURDF("random_urdfs/002/002.urdf",basePosition=[0.6,-0.2,0.7])
object3Uid=p.loadURDF("random_urdfs/003/003.urdf",basePosition=[0.8,0.1,0.7])
object4Uid=p.loadURDF("random_urdfs/004/004.urdf",basePosition=[0.7,-0.1,0.7])
object5Uid=p.loadURDF("random_urdfs/005/005.urdf",basePosition=[0.6,0.1,0.3])
object6Uid=p.loadURDF("random_urdfs/006/006.urdf",basePosition=[0.8,-0.2,0.4])
object7Uid=p.loadURDF("random_urdfs/007/007.urdf",basePosition=[0.6,0.2,0.4])
object8Uid=p.loadURDF("random_urdfs/008/008.urdf",basePosition=[0.7,-0.1,0.4])
object9Uid=p.loadURDF("random_urdfs/009/009.urdf",basePosition=[0.6,0.1,0.4])
while True:
p.stepSimulation()
下面,我们来控制机械臂运动。
控制电机的API是setJointMotorControl2()
。在Panda机器人的URDF定义里,joint 0~6是关节电机,9, 10是夹爪电机。
我们将机械臂的抓取过程分成4个步骤:current_state==0
时,将机械臂移动到物体的上面,打开夹爪;current_state==1
时,往下移动夹爪;current_state==3
时,夹住物体。
每个步骤经过1个单位时间,即比如设置setTimeStep=1./240.
时,经过240个stepSimulation()
完成当前步骤。
完整代码:
import pybullet as p
import pybullet_data as pd
import math
import time
# cameraTargetPosition=[0.55,-0.35,0.2]
p.connect(p.GUI)
p.setGravity(0,0,-10)
# p.configureDebugVisualizer(p.COV_ENABLE_SINGLE_STEP_RENDERING)
p.setAdditionalSearchPath(pd.getDataPath())
p.resetDebugVisualizerCamera(cameraDistance=1,cameraYaw=0,\
cameraPitch=-40,cameraTargetPosition=[0.5,-0.9,0.5])
pandaUid=p.loadURDF("franka_panda/panda.urdf",useFixedBase=True)
tableUid=p.loadURDF("table/table.urdf",basePosition=[0.5,0,-0.65])
trayUid=p.loadURDF("tray/traybox.urdf",basePosition=[0.65,0,0])
objectUid=p.loadURDF("random_urdfs/000/000.urdf",basePosition=[0.7,0,0.1])
# object1Uid=p.loadURDF("random_urdfs/001/001.urdf",basePosition=[0.7,0.1,0.7])
# object2Uid=p.loadURDF("random_urdfs/002/002.urdf",basePosition=[0.6,-0.2,0.7])
# object3Uid=p.loadURDF("random_urdfs/003/003.urdf",basePosition=[0.8,0.1,0.7])
# object4Uid=p.loadURDF("random_urdfs/004/004.urdf",basePosition=[0.7,-0.1,0.7])
# object5Uid=p.loadURDF("random_urdfs/005/005.urdf",basePosition=[0.6,0.1,0.3])
# object6Uid=p.loadURDF("random_urdfs/006/006.urdf",basePosition=[0.8,-0.2,0.4])
# object7Uid=p.loadURDF("random_urdfs/007/007.urdf",basePosition=[0.6,0.2,0.4])
# object8Uid=p.loadURDF("random_urdfs/008/008.urdf",basePosition=[0.7,-0.1,0.4])
# object9Uid=p.loadURDF("random_urdfs/009/009.urdf",basePosition=[0.6,0.1,0.4])
state_durations=[1,1,1,1]
control_dt=1./240.
p.setTimeStep=control_dt
state_t=0.
current_state=0
while True:
state_t+=control_dt
#p.configureDebugVisualizer(p.COV_ENABLE_SINGLE_STEP_RENDERING)
if current_state==0:
p.setJointMotorControl2(pandaUid,0,p.POSITION_CONTROL,0)
p.setJointMotorControl2(pandaUid,1,p.POSITION_CONTROL,math.pi/4.)
p.setJointMotorControl2(pandaUid,2,p.POSITION_CONTROL,0)
p.setJointMotorControl2(pandaUid,3,p.POSITION_CONTROL,-math.pi/2.)
p.setJointMotorControl2(pandaUid,4,p.POSITION_CONTROL,0)
p.setJointMotorControl2(pandaUid,5,p.POSITION_CONTROL,3*math.pi/4)
p.setJointMotorControl2(pandaUid,6,p.POSITION_CONTROL,-math.pi/4.)
p.setJointMotorControl2(pandaUid,9,p.POSITION_CONTROL,0.08)
p.setJointMotorControl2(pandaUid,10,p.POSITION_CONTROL,0.08)
if current_state==1:
p.setJointMotorControl2(pandaUid,1,p.POSITION_CONTROL,math.pi/4.+.15)
p.setJointMotorControl2(pandaUid,3,p.POSITION_CONTROL,-math.pi/2.+.15)
if current_state==2:
p.setJointMotorControl2(pandaUid,9,p.POSITION_CONTROL,force=200)
p.setJointMotorControl2(pandaUid,10,p.POSITION_CONTROL,force=200)
if current_state==3:
p.setJointMotorControl2(pandaUid,1,p.POSITION_CONTROL,math.pi/4.-1)
p.setJointMotorControl2(pandaUid,3,p.POSITION_CONTROL,-math.pi/2.-1)
if state_t>state_durations[current_state]:
current_state+=1
if current_state>=len(state_durations):
current_state=0
state_t=0
p.stepSimulation()
运行代码你会发现,除了机械臂是在突变位置外,其他一切正常。机械臂咋会突变位置呢?
这就牵扯到一个API:
p.configureDebugVisualizer(p.COV_ENABLE_SINGLE_STEP_RENDERING)
p.COV_ENABLE_SINGLE_STEP_RENDERING
是指允许机械臂慢慢的渲染。
在while True:
下面的第二句,把注释取消掉,就可以看到缓慢渲染的过程了。
评论(3)
您还未登录,请登录后发表或查看评论