AirSim 提供一项功能,可以直接从虚幻引擎构建世界的地面真实体素网格。体素网格通过将世界/地图离散化为特定大小的单元格来表示其占用情况;如果某个特定位置被占用,则记录一个体素。

构建体素网格的逻辑位于 WorldSimApi.cpp->createVoxelGrid() 中。目前,假设体素网格是一个立方体 - Python 中的 API 调用结构如下

simCreateVoxelGrid(self, position, x, y, z, res, of)

position (Vector3r): Global position around which voxel grid is centered in m
x, y, z (float): Size of each voxel grid dimension in m
res (float): Resolution of voxel grid in m
of (str): Name of output file to save voxel grid as

createVoxelGrid() 中,返回占用率的主要虚幻引擎函数是 OverlapBlockingTestByChannel

OverlapBlockingTestByChannel(position, rotation, ECollisionChannel, FCollisionShape, params);

此函数在我们要将地图离散化的所有“单元格”位置上调用,返回的占用结果收集到一个数组 voxel_grid_ 中。单元格占用值的索引遵循 binvox 格式的约定。

for (float i = 0; i < ncells_x; i++) {
    for (float k = 0; k < ncells_z; k++) {
        for (float j = 0; j < ncells_y; j++) {
            int idx = i + ncells_x * (k + ncells_z * j);
            FVector position = FVector((i - ncells_x /2) * scale_cm, (j - ncells_y /2) * scale_cm, (k - ncells_z /2) * scale_cm) + position_in_UE_frame;
            voxel_grid_[idx] = simmode_->GetWorld()->OverlapBlockingTestByChannel(position, FQuat::Identity, ECollisionChannel::ECC_Pawn, FCollisionShape::MakeBox(FVector(scale_cm /2)), params);
        }
    }
}

地图的占用率是针对所有离散化单元格迭代计算的,这可能是一个密集型操作,具体取决于单元格的分辨率以及所测量区域的总大小。如果用户的目标地图变化不大,可以在此地图上运行一次体素网格操作,并保存体素网格以重复使用。为了提高性能,或者在动态环境中,我们建议在机器人周围的小区域内生成体素网格;然后将其用于局部规划目的。

体素网格以 binvox 格式存储,用户可以将其转换为 octomap .bt 或任何其他相关、所需的格式。随后,这些体素网格/八叉树图可以用于映射/规划。一个巧妙的小工具可以可视化已创建的 binvox 文件,那就是 viewvox。同样,binvox2bt 可以将 binvox 转换为 octomap 文件。

Blocks 中的体素网格示例:#

image

转换为 Octomap 格式的 Blocks 体素网格(在 rviz 中可视化):#

image

例如,一旦 Blocks 环境启动并运行,就可以如下构建体素网格

import airsim
c = airsim.VehicleClient()
center = airsim.Vector3r(0, 0, 0)
output_path = os.path.join(os.getcwd(), "map.binvox")
c.simCreateVoxelGrid(center, 100, 100, 100, 0.5, output_path)

并通过 viewvox map.binvox 进行可视化。