카테고리 없음

turtlebot3-world pcd 파일을 pgm 파일로 변환하기(ROS2 NAV)

Wood Pecker 2023. 12. 19. 21:46

1. 개요

    PCD로 작성된  3차원 맵으로 부터 기존 navigation 모듈 사용을 위한  2차원의 이미지 pgm파일과 yaml 파일로 만들어 본다.  pcd파일에서 직접 가능한 방법?이 있을 것 같긴 한데 추후에 알아 보기로 하고 일단 이미지로 변경하는 방법을 시도해 본다.. 

 

2. 지난번 포스트에서 gazebo 플러인으로 작성한 turtlebot3_world의 pcd파일 예시는 다음과 같다.

# .PCD v0.7 - Point Cloud Data file format
VERSION 0.7
FIELDS x y z
SIZE 4 4 4
TYPE F F F
COUNT 1 1 1
WIDTH 17139
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 17139
DATA ascii
-4.8000002 4.5 0.029999999
-4.8000002 4.4010987 0.029999999
-4.8000002 4.3021979 0.029999999
-4.8000002 4.2032967 0.029999999

 

SIZE 4는 4 byte, TYPE F는 float, COUNT 1은 각 필드에서 사용되는 데이터의 개수를 정의합니다. 여기서는 'x', 'y', 'z' 모두 1로, 각 필드에 하나의 값을 가진다는 것을 의미합니다.  WIDTH, HEIGHT: 포인트 클라우드의 차원을 정의합니다. 여기서는 WIDTH가 17139로, 포인트의 개수를 나타냅니다. HEIGHT가 1이므로 이는 1D 포인트 클라우드를 나타냅니다.

 

3. yaml 파일을 다음과 같은 내용으로  만들어 있다.

free_thresh: 0.90
negate: 0
origin: [-4.800000, -4.500000, 0.0]
resolution: 0.100000
mode: trinary
occupied_thresh: 0.95
image: plugin4_map.pgm

free_thresh (자유 임계값): 로봇 맵에서 자유 영역으로 간주되는 확률 값의 임계값입니다. 여기서는 0.90으로 설정되어  이 값보다 작은 확률 값은 자유 영역으로 간주됩니다.
negate (부정): 지도를 부정적으로 표현하는지 여부를 나타내는 플래그입니다. 0은 부정적으로 표현하지 않음을 나타냅니다.
origin (원점): 맵의 원점을 나타내는 세 개의 값으로 구성된 배열입니다. 각 값은 x, y 및 z 좌표를 나타냅니다. 여기서는 [-4.800000, -4.500000, 0.0]으로 설정되어 있습니다.

 

resolution (해상도): 맵의 해상도를 나타내는 값으로, 미터 단위입니다. 여기서는 0.100000으로 설정되어 있습니다.


occupied_thresh (점유 임계값): 로봇 맵에서 점유 영역으로 간주되는 확률 값의 임계값입니다. 여기서는 0.95로 설정되어 있습니다. 이 값보다 큰 확률 값은 점유 영역으로 간주됩니다.

mode (모드): 맵의 표현 모드를 나타냅니다. 여기서는 'trinary'로 설정되어 있습니다. 'trinary' 모드는 확률 값이 자유, 점유 또는 미지의 세 가지 값 중 하나로 표현됨을 의미합니다.  즉 0.9보다 작으면 자유영역, 09 ~ 0.95 까지는 점유지역, 0.95~ 보다 크면 미지의 영역이다. 

image (이미지): 맵 데이터가 저장된 이미지 파일의 경로를 나타냅니다. 여기서는 "plugin4_map.pgm"으로 설정되어 있습니다. 이는 Portable Graymap Format(PGM)의 이미지 파일이다. 

 

4.  turtlebot3-world에서 만들은 pcd 파일의  x,y,z의 min~max 값을 알아내면 다음과 같았다. 

    X: (-4.8000002, 4.7010317)
    Y: (-4.4010992, 4.5)
    Z: (0.029999999, 1.524375)

 

5.  pcd 파일을 파이썬에서 visualize하면 다음과 같다.

import open3d as o3d

def visualize_pcd(pcd_file_path):
    pcd = o3d.io.read_point_cloud(pcd_file_path)
    o3d.visualization.draw_geometries([pcd])

if __name__ == '__main__':
    pcd_file_path = 'plugin5_map.pcd'
    visualize_pcd(pcd_file_path)

6. 해당 pcd 파일에서 Z 값을 변경하면서 새로운 pgm 파일을 만들어 네비게이션에 활용해 보기로 한다. 

    다음과 같은 파이썬 프로그램을 작성하였다.

import numpy as np
from PIL import Image  #pip install Pillow
import yaml  # pip install pyyaml

def save_pgm_image(array, filename):
    # 배열의 최소 및 최대 값을 정규화하여 0~255 범위로 매핑
    normalized_array = ((array - np.min(array)) / (np.max(array) - np.min(array))) * 255
    # 소수점 이하를 정수로 변환
    normalized_array = normalized_array.astype(np.uint8)
    # PGM 이미지 파일 헤더 작성
    height, width = normalized_array.shape
    header = f"P2\n{width} {height}\n255\n"
    # PGM 이미지 파일 작성
    with open(filename, 'w') as file:
        file.write(header)
        np.savetxt(file, normalized_array, fmt='%d', delimiter=' ')

def get_min_max_coordinates_from_pcd(pcd_file_path,altitude, resolution):
    array_2d = [[255 for _ in range(256)] for _ in range(256)]
    with open(pcd_file_path, 'r') as file:
        lines = file.readlines()
    # 데이터 시작 라인 찾기
    data_start = lines.index("DATA ascii\n") + 1 if "DATA ascii\n" in lines else None
    if data_start is None:
        raise ValueError("Invalid PCD file format. DATA ascii not found.")
    # 데이터 부분만 추출
    data_lines = lines[data_start:]
    # x, y, z 좌표 추출 및 최소값 및 최대값 계산
    x_values = []
    y_values = []
    z_values = []

    for line in data_lines:
        values = [float(v) for v in line.split()]
        x_values.append(values[0])
        y_values.append(values[1])
        z_values.append(values[2])
        j= values[0]/resolution+256/2
        i= -values[1]/resolution+256/2
        if(abs(values[2] - altitude)<0.1):
            if 0 <= i < 256 and 0 <= j < 256:
                array_2d[int(i)][int(j)]=0

    min_x, max_x = min(x_values), max(x_values)
    min_y, max_y = min(y_values), max(y_values)
    min_z, max_z = min(z_values), max(z_values)

    return min_x, max_x, min_y, max_y, min_z, max_z,array_2d


if __name__ == '__main__':
    image_size = 256
    resolution = 0.05
    altitude = 0.8  # Z is altitude

    pcd_file_path = 'plugin5_map.pcd'  #<== input pcd file name
    output_file_name='myout'           #<== output file name

    pgm_file_name=output_file_name+'.pgm'
    png_file_name = output_file_name+'.png'
    yaml_file_name = output_file_name+'.yaml'
    # 최소 최대값 출력
    min_x,max_x, min_y,max_y, min_z,max_z,array_2d = get_min_max_coordinates_from_pcd(pcd_file_path,altitude,resolution)
    x_range= max_x - min_x
    y_range= max_y - min_y
    z_range= max_z - min_z
    print(min_x,max_x, min_y,max_y, min_z,max_z)

    print(f"Image saved to {pgm_file_name}")
    save_pgm_image(array_2d, pgm_file_name)

    print(f"Image saved to {png_file_name}")
    data_array = np.array(array_2d, dtype=np.uint8)
    image = Image.fromarray(data_array)
    image.save(png_file_name)

    print(f"YAML file '{yaml_file_name}' has been created.")
    data = {
        'mode': 'trinary',
        'occupied_thresh': 0.95,
        'free_thresh': 0.90,
        'negate': 0,
        'origin': [-image_size*resolution/2, -image_size*resolution/2, 0.0],
        'resolution': resolution,
        'image': pgm_file_name
    }
    with open(yaml_file_name, 'w') as file:
        yaml.dump(data, file, default_flow_style=False)

 

네비게이션에 적용 테스트 하였다.  기존 SALM으로 작성한 이미지와 같이 잘 작동한다. 

반응형