(七)Graphics and Camera Sensors
Isaac Gym提供API来以编程方式控制场景的视觉方面。此外,Isaac Gym还提供API来管理多个摄像机的视图,并将这些摄像机视为机器人上的传感器。以下部分描述了摄像机属性、摄像机传感器、视觉属性修改和与图形和摄像机传感器相关的其他主题。
Isaac Gym提供API来以编程方式控制场景的视觉方面。此外,Isaac Gym还提供API来管理多个摄像机的视图,并将这些摄像机视为机器人上的传感器。以下部分描述了摄像机属性、摄像机传感器、视觉属性修改和与图形和摄像机传感器相关的其他主题。
摄像机属性
Isaac Gym中的所有摄像机,包括查看器的摄像机,都可以通过传递给GymCameraProperties结构的参数来创建。摄像机属性结构包含诸如视场、分辨率、近距离和远距离裁剪距离以及一些其他选项的属性。当以正常方式创建查看器时:
viewer = gym.create_viewer(sim, gymapi.CameraProperties())
将构造一个具有默认值的摄像机属性结构并用于查看器摄像机。可以像以下代码一样自定义属性:
camera_props = gymapi.CameraProperties()
camera_props.horizontal_fov = 75.0
camera_props.width = 1920
camera_props.height = 1080
viewer = gym.create_viewer(sim, camera_props)
摄像机属性:
- width - 图像宽度(以像素为单位)
- height - 图像高度(以像素为单位)
- horizontal_fov - 水平视场角,以弧度表示。垂直视场角将是高度/宽度 * horizontal_fov
- near_plane - 摄像机点到近裁剪面的距离(以世界单位/米表示)
- far_plane - 摄像机点到远裁剪面的距离(以世界单位/米表示)
- supersampling_horizontal - 超采样的整数倍数。将其设置为大于1的值将导致渲染宽度*超采样水平像素宽,并重新采样为输出分辨率
- supersampling_vertical - 超采样的整数倍数。将其设置为大于1的值将导致渲染高度*超采样垂直像素高,并重新采样为输出分辨率
- use_collision_geometry - 如果设置为True,则摄像机将渲染碰撞几何
- enable_tensors - 如果设置为True,将为该摄像机启用张量访问。有关更多信息,请参见下面的GPU张量访问部分。
摄像机传感器
Isaac Gym中的摄像头传感器用于模拟观察或连接到机器人的摄像头。可以使用以下方式轻松创建摄像头传感器:
camera_props = gymapi.CameraProperties()
camera_props.width = 128
camera_props.height = 128
camera_handle = gym.create_camera_sensor(env, camera_props)
与查看器摄像机一样,也可以将自定义的摄像机属性传递给摄像机创建。与查看器摄像机不同,摄像机传感器无法通过查看器进行控制,它连接到特定的环境。这意味着当摄像机传感器创建图像时,它只能“看到”所选环境中的那些参与者。
可以使用三种方式设置摄像机传感器的位置。首先,可以使用以下方法直接放置摄像机传感器:
gym.set_camera_location(camera_handle, env, gymapi.Vec3(x,y,z), gymapi.Vec3(tx,ty,yz))
其中(x,y,z)
是摄像机在环境局部坐标系中的坐标,(tx, ty,tz)
是摄像机所看到的点的坐标,同样是以环境的局部坐标表示。
第二种放置摄像机的方法是直接指定一个GymTransform
,该变换指定了摄像机在环境局部坐标系中的位置和姿态,例如:
transform = gymapi.Transform()
transform.p = (x,y,z)
transform.r = gymapi.Quat.from_axis_angle(gymapi.Vec3(0,1,0), np.radians(45.0))
gym.set_camera_transform(camera_handle, env, transform)
最后,相机可以直接附加到刚体上。这在创建随着物体移动跟踪物体的相机或模拟相机直接附加到角色的某个部分时非常有用。可以按以下方式将相机附加到刚体上:
local_transform = gymapi.Transform()
local_transform.p = (x,y,z)
local_transform.r = gymapi.Quat.from_axis_angle(gymapi.Vec3(0,1,0), np.radians(45.0))
gym.attach_camera_to_body(camera_handle, env, body_handle, local_transform, gymapi.FOLLOW_TRANSFORM)
最后一个参数决定了附加行为:
-
gymapi.FOLLOW_POSITION
表示相机将保持与刚体的固定偏移(local_transform.p
),但不会随着刚体旋转。 -
gymapi.FOLLOW_TRANSFORM
表示相机将保持固定的偏移并随刚体一起旋转。
当附加到刚体上时,相机的变换将在 step_graphics(sim)
中更新。
为了提高性能,所有相机传感器都一起渲染:
gym.render_all_camera_sensors(sim)
在 render_all_camera_sensors(sim)
之后,所有相机的所有输出都已准备好获取。
相机图像类型
每个相机渲染多个不同的输出图像。当访问图像时,API 使用图像选择器,指示应用程序想要检索或访问的输出图像中的哪一个。
相机传感器图像类型:
-
IMAGE_COLOR - 4x8位无符号整数 - RGBA颜色
-
IMAGE_DEPTH - 浮点数 - 摄像机到像素的负距离,以世界坐标单位(米)表示
-
IMAGE_SEGMENTATION - 32位无符号整数 - 每个像素的真实分割ID。参见
-
IMAGE_OPTICAL_FLOW - 2x16位有符号整数 - 每个像素的屏幕空间运动矢量,归一化
深度图像被线性化为世界坐标。这意味着像素的值以仿真单位(米)表示。分割图像的每个像素包含生成该像素的刚体的分割ID。请参见以下代码,展示了如何将光流图像中的值转换为像素单位的值:
optical_flow_image = gym.get_camera_image(sim, camera_handle, gymapi.IMAGE_OPTICAL_FLOW)
optical_flow_in_pixels = np.zeros(np.shape(optical_flow_image))
# 水平方向(u)
optical_flow_in_pixels[0,0] = image_width*(optical_flow_image[0,0]/2**15)
# 垂直方向(v)
optical_flow_in_pixels[0,1] = image_height*(optical_flow_image[0,1]/2**15)
访问相机图像
可以通过调用 get_camera_image
访问使用相机传感器渲染的图像类型,如下所示:
color_image = gym.get_camera_image(sim, env, camera_handle, gymapi.IMAGE_COLOR)
第三个参数选择该相机的哪个图像应该返回。上述代码要求返回颜色(RGBA)图像。图像以numpy数组的形式返回。当图像包含矢量输出时,例如 IMAGE_COLOR
和 IMAGE_OPTICAL_FLOW
,这些矢量在最快移动的维度上紧密打包。
参见图形示例。
GPU张量访问
在使用摄像头传感器输出进行模型训练时,关键的优化是在Isaac Gym中防止将图像复制到CPU中,只是让学习框架将其复制回GPU。为了提高性能,Isaac Gym提供了一种直接访问摄像头输出的GPU张量的方法,而无需复制到CPU内存中。
为了激活张量访问,必须在摄像头属性结构中将enable_tensors
标志设置为True
来创建摄像头。:
camera_props = gymapi.CameraProperties()
camera_props.enable_tensors = True
cam_handle = gym.create_camera_sensor(env, camera_props)
在设置阶段,应用程序应该通过调用以下方法来检索和存储摄像头的张量结构:
camera_tensor = gym.get_camera_image_gpu_tensor(sim, env, cam_handle, gymapi.IMAGE_COLOR)
返回的GPU张量具有指向存储在GPU上的数据的GPU设备端指针,以及有关数据类型和张量形状的信息。将此数据与深度学习框架共享需要一个张量适配器,比如用于PyTorch互操作的gymtorch
模块中提供的适配器:
camera_tensor = gym.get_camera_image_gpu_tensor(sim, env, cam_handle, gymapi.IMAGE_COLOR)
torch_camera_tensor = gymtorch.wrap_tensor(camera_tensor)
现在,在模拟的主循环中,应用程序必须声明何时开始和停止访问张量,以防止内存危害:
while True:
gym.simulate(sim)
gym.fetch_results(sim, True)
gym.step_graphics(sim)
gym.render_all_camera_sensors(sim)
gym.start_access_image_tensors(sim)
#
# 用户代码以处理张量
#
gym.end_access_image_tensors(sim)
start_access_image_tensors(sim)
API通知Isaac Gym用户代码想直接访问图像张量缓冲区。该API将停顿直到上一次调用render_all_camera_sensors(sim)
的所有图像张量都被写入。为了最小化停顿,该API只会一次停顿所有使用enable_tensors
标志设置为True
创建的摄像头传感器。
直到调用end_access_image_tensors(sim)
为止,Isaac Gym将不会再次写入张量。按照惯例,访问图像张量应在主循环的同一次迭代内开始和停止。在张量访问的开始和结束之间调用render_all_camera_sensors(sim)
可能导致应用程序死锁。
有关更多信息,请参见PyTorch interop example和有关tensor API的章节。
可视化属性
Isaac Gym包含一些用于允许以编程方式修改物体或场景的可视属性的API,以实现可视域随机化等技术。本节简要介绍了用于修改物体或场景的可视属性的相关例程。
颜色
您可以使用以下API来设置任何刚体的颜色:
gym.set_rigid_body_color(env, actor_handle, rigid_body_index, gymapi.MESH_VISUAL_AND_COLLISION, gymapi.Vec3(r, g, b))
在上述调用中,env
是包含角色的环境,actor_handle
是环境中角色的句柄,rigid_body_index
是要修改颜色的刚体索引。第四个参数是一个网格选择器,允许调用者指定是设置刚体的可视网格的颜色 MESH_VISUAL
,刚体的碰撞网格的颜色 MESH_COLLISION
,还是同时设置两者的颜色 MESH_VISUAL_AND_COLLISION
。最后一个参数是一个颜色向量,表示新的颜色,其中 red
、green
和 blue
是介于0.0和1.0之间的浮点值。这将为整个刚体设置颜色。如果网格具有不同颜色的子网格,则将所有子网格的颜色设置为指定的颜色。
还可以使用以下方式查询刚体的当前颜色:
current_color = gym.get_rigid_body_color(env, actor_handle, rigid_body_index, gymapi.MESH_VISUAL)
当使用网格选择器 MESH_VISUAL_AND_COLLISION
调用 get_rigid_body_color
时,将返回可视网格的颜色。如果网格包含子网格,则返回第一个子网格的颜色。
纹理
Isaac Gym提供了两种从API创建纹理的方法,一种是从文件中读取纹理,另一种是接受numpy像素数组作为纹理。可以使用以下方法从文件创建纹理:
texture_handle = gym.create_texture_from_file(sim, texture_filename);
有效的文件类型为 .jpg、.png 和 .bmp。可以使用以下方法从缓冲区创建纹理:
texture_handle = gym.create_texture_from_buffer(sim, width, height, pixelArray)
其中 pixelArray
是具有大小为 [height, width*4]
的 uint8_t 类型的numpy数组,其中包含打包的RGBA值。输入时,Alpha值应始终为255。
创建纹理的两种方法都在图形系统中分配纹理,将像素数据复制到GPU,最后返回一个用于将来引用该纹理的句柄。由于纹理的创建需要内存分配和复制到GPU,强烈建议在设置期间创建模拟所需的所有纹理,并在模拟过程中使用句柄修改刚体。
与颜色一样,刚体的纹理的设置方式也基本相同。只需将颜色替换为 create_texture_from_file()
或 create_texture_from_buffer()
返回的纹理句柄,如下所示:
gym.set_rigid_body_texture(env, actor_handle, rigid_body_index, gymapi.MESH_VISUAL_AND_COLLISION, texture_handle)
纹理与颜色一样,可以使用相同的方式进行查询:
texture_handle = gym.get_rigid_body_texture(env, actor_handle, rigid_body_index, gymapi.MESH_VISUAL)
如果所选刚体没有分配纹理,则返回INVALID_HANDLE
。与颜色类似,如果mesh选择器为MESH_VISUAL_AND_COLLISION
,则返回分配给视觉资源的纹理(如果有)。
纹理坐标在创建资源时与mesh一起加载。如果一个mesh不包含任何纹理坐标,则在加载资源时使用球形投影生成它们。
如果不再需要某个纹理,可以使用以下方式释放它:
gym.free_texture(sim, texture_handle)
当销毁GymSim
时,所有纹理和mesh数据都会自动从GPU内存中释放。
纹理和颜色交互
纹理和颜色通过渲染器进行乘法应用。这意味着在一个像素处的纹理样本将乘以物体颜色以计算得到的像素颜色。为了正确显示纹理,设置刚体的颜色为(1.0, 1.0, 1.0)
,设置纹理时确保正确的颜色。然而,这个特性可以通过给纹理添加色调来增加域随机化的空间,或者可以根据刚体所受的力来着色。
重置
可以随时通过调用以下方式将整个actor的颜色和纹理重置为从asset文件加载的原始值:
gym.reset_actor_materials(env, actor, gymapi.MESH_VISUAL_AND_COLLISION)
这将针对指定类型(visual、collision或both)的actor中的所有刚体重置材质。
分割ID
为了区分语义分割ground truth图像(IMAGE_SEGMENTATION)中机器人的不同部分,需要为不同的刚体分配不同的分割ID值。刚体的分割ID设置方式与颜色和纹理相似:
gym.set_rigid_body_segmentation_id(env, actor_handle, rigid_body_index, segmentation_id)
当从相机中获取IMAGE_SEGMENTATION
时,任何源自指定刚体的像素将具有值segmentation_id
,它是一个32位无符号整数。
灯光
Isaac Gym的渲染具有一组有限的灯光,可以使用API程序控制:
gym.set_light_parameters(sim, light_index, intensity, ambient, direction)
light_index
是灯光的索引值,只有0到3的值有效。强度是表示灯光相对RGB值的Vec3,其中0表示关闭,1.0表示最大强度。强度值没有物理对应关系。ambient
是表示环境光的RGB值的Vec3,以与经典phong模型相同的方式。最后,direction
是表示灯光方向的Vec3。所有灯光都是方向灯光。
灯光模型是Isaac Gym图形系统中唯一共享的部分。调用set_light_parameters
会对所有环境中的所有相机传感器的RGB图像以及全局查看器产生影响。
线段API
线段API是一个简单的API,用于在查看器中绘制线段,以进行调试或可视化目的。线段可以用于可视化接触、施加的力或任何其他矢量量。线段API非常简单,有一个API用于将线段添加到线段积累器中,另一个API用于刷新所有线段。添加线段的API如下所示:
gym.add_lines(viewer, env, num_lines, vertices, colors)
在这个调用中,vertices
是一个大小为[num_lines * 2 , 3]的数组,其中元素[2 * i, :]指定了env本地坐标系中线段起点的3D坐标,而元素2*i+1指定了线段i的终点坐标。colors
是一个大小为[num_lines, 3]的数组,其中每个索引[i,:]表示线段i的RGB颜色。
通过调用以下函数可以移除所有已添加用于绘制的线段:
while not gym.query_viewer_has_closed(viewer):
gym.simulate(sim)
gym.fetch_results(sim, True)
gym.step_graphics(sim)
# generate_lines是一个用户编写的函数,用于创建所需的线段顶点
line_vertices, line_colors, num_lines = generate_lines()
gym.add_lines(viewer, env, num_lines, line_vertices, line_colors)
gym.draw_viewer(viewer)
使用线段API的常见用途是在绘制查看器之前添加线段,并在立即绘制后清除它们。
使用Lines API绘制的线段仅在查看器中可见,它们不会渲染到相机传感器的视图中。
相机内参
Isaac Gym的一些高级用法,例如将深度图像反投影为3D点云,需要对用于创建输出图像的投影参数有完整的了解。为了方便,Isaac Gym提供了访问用于渲染相机视图的投影矩阵和视图矩阵的功能。它们是:
projection_matrix = np.matrix(gym.get_camera_proj_matrix(sim, env, camera_handle))
view_matrix = np.matrix(gym.get_camera_view_matrix(sim, env, camera_handle))
这两个函数返回一个numpy数组,并且在上面的代码示例中转换为numpy矩阵。
步进图形
当需要进行渲染时,必须将来自物理模拟的变换和信息传递到图形系统中。为了支持图形和物理不以相同的更新速率运行的用例,例如仅在每N个步骤时图形渲染一次,Isaac Gym允许手动控制此过程。当使用多个相机传感器时,这更为重要,因为渲染器必须知道何时移动到时间的新帧,但rende()调用不是隐式的帧边界,因为可能有多次render()调用用于查看器和相机传感器。帧边界更新通过step_graphics(sim)
API显式完成。
然而,在大多数情况下,step_graphics()
将在调用draw_viewer()
和/或render_all_camera_sensors()
之前立即调用,如下所示:
while not gym.query_viewer_has_closed(viewer):
gym.simulate(sim)
gym.fetch_results(sim, True)
gym.step_graphics(sim)
gym.render_all_camera_sensors(sim)
gym.draw_viewer(viewer)
更多推荐
所有评论(0)