Coverage Path Planning 项目部署与场景转换完整指南
项目概述
rodriguesrenato/coverage-path-planning 是一个覆盖路径规划算法,使用A*搜索算法的变体来为机器人生成最优的全覆盖轨迹。项目要求输入格式为NxM的numpy数组,其中:
0
= 自由区域1
= 障碍物2
= 起始点
环境准备
激活Isaac Lab环境
bash
cd ~/Project
conda activate isaaclab_4_5_0
验证依赖包
scss
# 检查必要的Python包
python -c "import numpy; print('NumPy version:', numpy.__version__)"
python -c "import matplotlib; print('Matplotlib available')"
python -c "import enum; print('Enum available')"
第一步:克隆项目
bash
cd ~/Project
git clone https://github.com/rodriguesrenato/coverage-path-planning.git
cd coverage-path-planning
第二步:安装项目依赖
根据项目要求,安装必要的Python包:
bash
# 如果缺少matplotlib
pip install matplotlib
# 如果缺少其他依赖包
pip install numpy matplotlib scipy
第三步:理解项目结构
根据实际克隆的项目结构:
bash
coverage-path-planning/
├── docs/ # 文档文件夹(包含PDF论文)
├── images/ # 算法说明图片
├── maps/ # 示例地图文件(map0.npy 到 map4.npy)
├── output_images/ # 输出结果图片(map0.png 到 map4.png)
├── coverage_planner.py # 主要的规划器类
├── coverage_test.py # 测试脚本
├── generate_np_maps.py # 地图生成脚本
├── maps_orig # 原始地图数据
├── README.md # 项目说明
└── LICENSE # 许可证
第四步:从IsaacSim USD场景生成occupancy map
使用Isaac Sim内置的Occupancy Map生成器
- 启动Isaac Sim
bash
cd ~/isaacsim
./isaac-sim.sh
- 加载你的USD场景
markdown
* File → Open → 选择你的 `.usd` 场景文件
- 确保所有几何体有碰撞属性
markdown
* 选中场景中的对象
* 右键 → Add → Physics → Collider Preset
- 生成Occupancy Map
markdown
* 顶部菜单:tools → robotics → Occupancy Map
* 设置参数:
* Origin: 起始位置 (确保是空闲区域)
* Lower Bound: 地图下边界
* Upper Bound: 地图上边界
* Cell Size: 网格大小 (建议0.1米)
* Origin Z: 扫描高度 (如0.1米)
* 点击 **CALCULATE**
* 点击 **VISUALIZE IMAGE**
* 点击 **SAVE IMAGE** 保存为PNG文件
如下图所示:

理解Isaac Sim输出信息
从输出信息示例:
yaml
Top Left: (-29.975, -39.975)
Top Right: (-29.975, 64.975)
Bottom Left: (9.975, -39.975)
Bottom Right: (9.975, 64.975)
Coordinates of top left of image (pixel 0,0) as origin, + X down, + Y right: (39.975, 9.975)
Image size in pixels: 2100, 800
分析:
- 实际世界坐标范围:X轴从-29.975到9.975米(约40米),Y轴从-39.975到64.975米(约105米)
- 图像尺寸:2100x800像素
- 分辨率:X方向约0.019m/pixel,Y方向约0.131m/pixel
第五步:创建PNG到NumPy转换脚本
创建 convert_isaac_png_to_coverage.py
:
python
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import os
def convert_isaac_png_to_coverage_format(png_path, cell_size_info, start_position=None, output_name="isaac_scene"):
"""
将Isaac Sim生成的PNG占用栅格转换为coverage-path-planning格式
Args:
png_path: PNG文件路径
cell_size_info: Isaac Sim输出的坐标信息字典
start_position: 起始点位置 (x, y) 在numpy数组坐标系中,如果为None则自动选择
output_name: 输出文件名前缀
"""
print("=== Isaac Sim PNG 转 Coverage-Path-Planning 格式 ===")
print(f"处理文件: {png_path}")
# 1. 读取PNG图像
if not os.path.exists(png_path):
print(f"错误: 找不到文件 {png_path}")
return None
img = Image.open(png_path)
if img.mode != 'L': # 如果不是灰度图,转换为灰度
img = img.convert('L')
img_array = np.array(img)
print(f"原始图像尺寸: {img_array.shape}")
print(f"像素值范围: {img_array.min()} - {img_array.max()}")
# 2. 显示原始图像
plt.figure(figsize=(12, 5))
plt.subplot(1, 3, 1)
plt.imshow(img_array, cmap='gray', origin='upper')
plt.title('原始Isaac Sim图像')
plt.colorbar()
# 3. 转换为二值图像 (0=自由区域, 1=障碍物)
# Isaac Sim通常: 黑色(低值)=障碍物, 白色(高值)=自由区域
# 使用阈值方法转换
threshold = 128 # 可以根据实际情况调整
binary_map = (img_array < threshold).astype(int)
print(f"二值化后统计:")
print(f" 自由区域(0): {np.sum(binary_map == 0)} 像素")
print(f" 障碍物(1): {np.sum(binary_map == 1)} 像素")
# 4. 显示二值化结果
plt.subplot(1, 3, 2)
plt.imshow(binary_map, cmap='viridis', origin='upper')
plt.title('二值化结果\n0=Free, 1=Obstacle')
plt.colorbar()
# 5. 选择起始点
if start_position is None:
# 自动选择起始点:在自由区域中心附近
free_positions = np.where(binary_map == 0)
if len(free_positions[0]) > 0:
center_x, center_y = binary_map.shape[0] // 2, binary_map.shape[1] // 2
distances = (free_positions[0] - center_x)**2 + (free_positions[1] - center_y)**2
closest_idx = np.argmin(distances)
start_x = free_positions[0][closest_idx]
start_y = free_positions[1][closest_idx]
start_position = (start_x, start_y)
else:
print("警告: 没有找到自由区域,使用中心点作为起始点")
start_position = (binary_map.shape[0] // 2, binary_map.shape[1] // 2)
# 6. 创建最终的coverage map
coverage_map = binary_map.copy()
start_x, start_y = start_position
coverage_map[start_x, start_y] = 2 # 设置起始点
print(f"起始点设置在: ({start_x}, {start_y})")
print(f"最终地图统计:")
print(f" 自由区域(0): {np.sum(coverage_map == 0)}")
print(f" 障碍物(1): {np.sum(coverage_map == 1)}")
print(f" 起始点(2): {np.sum(coverage_map == 2)}")
# 7. 显示最终结果
plt.subplot(1, 3, 3)
plt.imshow(coverage_map, cmap='viridis', origin='upper')
plt.title('Coverage Map\n0=Free, 1=Obstacle, 2=Start')
plt.colorbar()
# 标记起始点
plt.plot(start_y, start_x, 'r*', markersize=15, label='Start Point')
plt.legend()
plt.tight_layout()
plt.show()
# 8. 保存结果
output_dir = '/home/lwb/Project/coverage-path-planning/maps/'
os.makedirs(output_dir, exist_ok=True)
npy_path = os.path.join(output_dir, f'{output_name}.npy')
png_output_path = os.path.join(output_dir, f'{output_name}.png')
np.save(npy_path, coverage_map)
# 保存可视化图像
plt.figure(figsize=(8, 6))
plt.imshow(coverage_map, cmap='viridis', origin='upper')
plt.colorbar(label='0=Free, 1=Obstacle, 2=Start')
plt.title(f'Coverage Map: {output_name}')
plt.plot(start_y, start_x, 'r*', markersize=15, label='Start Point')
plt.legend()
plt.savefig(png_output_path, dpi=150, bbox_inches='tight')
plt.show()
print(f"\n转换完成!")
print(f" NumPy文件: {npy_path}")
print(f" 图像文件: {png_output_path}")
return coverage_map, npy_path
def resize_map_for_coverage_planning(coverage_map, target_max_size=100):
"""
将地图调整到适合coverage planning的尺寸
coverage-path-planning算法对于大地图可能会很慢
"""
original_shape = coverage_map.shape
# 如果地图太大,进行下采样
if max(original_shape) > target_max_size:
from scipy import ndimage
scale_factor = target_max_size / max(original_shape)
new_shape = (int(original_shape[0] * scale_factor),
int(original_shape[1] * scale_factor))
print(f"地图尺寸从 {original_shape} 调整到 {new_shape}")
# 下采样
resized_map = ndimage.zoom(coverage_map, scale_factor, order=0) # order=0 保持整数值
# 确保值仍然是 0, 1, 2
resized_map = np.round(resized_map).astype(int)
# 确保至少有一个起始点
if np.sum(resized_map == 2) == 0:
free_positions = np.where(resized_map == 0)
if len(free_positions[0]) > 0:
center_idx = len(free_positions[0]) // 2
start_x = free_positions[0][center_idx]
start_y = free_positions[1][center_idx]
resized_map[start_x, start_y] = 2
return resized_map
return coverage_map
# 使用示例
if __name__ == "__main__":
# 你需要修改这个路径为你的PNG文件路径
png_file_path = "/path/to/your/isaac_sim_occupancy_map.png"
# Isaac Sim输出的坐标信息(根据你的实际输出调整)
cell_size_info = {
"top_left": (-29.975, -39.975),
"top_right": (-29.975, 64.975),
"bottom_left": (9.975, -39.975),
"bottom_right": (9.975, 64.975),
"image_size": (2100, 800),
"origin_offset": (39.975, 9.975)
}
# 转换地图
coverage_map, npy_path = convert_isaac_png_to_coverage_format(
png_file_path,
cell_size_info,
start_position=None, # 自动选择起始点
output_name="kitchen_scene"
)
if coverage_map is not None:
# 如果地图太大,调整尺寸
if max(coverage_map.shape) > 50: # coverage-path-planning对大地图可能很慢
print("\n地图较大,建议调整尺寸...")
resized_map = resize_map_for_coverage_planning(coverage_map, target_max_size=50)
# 保存调整尺寸后的地图
resized_npy_path = npy_path.replace('.npy', '_resized.npy')
np.save(resized_npy_path, resized_map)
print(f"调整尺寸后的地图保存到: {resized_npy_path}")
# 可视化调整后的地图
plt.figure(figsize=(8, 6))
plt.imshow(resized_map, cmap='viridis', origin='upper')
plt.colorbar(label='0=Free, 1=Obstacle, 2=Start')
plt.title('Resized Coverage Map')
start_pos = np.where(resized_map == 2)
if len(start_pos[0]) > 0:
plt.plot(start_pos[1][0], start_pos[0][0], 'r*', markersize=15, label='Start Point')
plt.legend()
plt.show()
第六步:运行转换脚本
bash
cd ~/Project/coverage-path-planning
# 创建转换脚本
nano convert_isaac_png_to_coverage.py
# 粘贴上面的代码,修改png_file_path为你的PNG文件路径,并且修改Isaac Sim输出的坐标信息
# 运行转换
python convert_isaac_png_to_coverage.py
运行转换效果图:

第七步:测试转换后的地图
python coverage_test.py
未缩放大小的结果

压缩大小的结果

输出的不同启发式算法对比

覆盖路径规划结果分析
性能排名(按代价Cost)
- HORIZONTAL + < : 4944步,542.70 最优
- HORIZONTAL + v: 4944步,542.80
- HORIZONTAL + > : 4980步,545.60
- HORIZONTAL + ^ : 4980步,545.70
- VERTICAL系列: 4979-5004步,560.30-562.50
- CHEBYSHEV系列: 4987-5027步,587.60-594.00
- MANHATTAN系列: 5027-5176步,562.70-586.90
启发式算法效果对比
算法 | 平均代价 | 性能评级 |
---|---|---|
HORIZONTAL | ~544 | 优秀 |
VERTICAL | ~561 | 良好 |
MANHATTAN | ~573 | 一般 |
CHEBYSHEV | ~591 | 较差 |
实用建议
推荐: 厨房环境使用HORIZONTAL算法,初始方向选择西向或南向
参数调优指南
如果coverage planning失败
-
地图太大:
ini# 在convert脚本中调整target_max_size resized_map = resize_map_for_coverage_planning(coverage_map, target_max_size=30)
-
起始点位置不好:
ini# 手动指定起始点 start_position = (map_height//4, map_width//4) # 左上角1/4位置
性能优化建议
- 对于大地图:将尺寸控制在50x50以内
- 对于复杂地图:可以先简化,移除小障碍物
- 起始点选择:选择在较大自由区域的中心