Примечание. Инструкции по загрузке, запуску и устранению неполадок кода, представленного в этой статье, приведены в конце.
В рамках своих экспериментов с Open3D-ML для облаков точек я написал статьи, объясняющие, как установить эту библиотеку с поддержкой Tensorflow и PyTorch. Чтобы протестировать установку, я объяснил, как запустить простой скрипт Python для визуализации помеченного набора данных для семантической сегментации под названием SemanticKITTI. В этой статье я расскажу о шагах, которые я выполнил, чтобы сделать вывод о любом облаке точек, включая тестовую часть SemanticKITTI, а также о моем частном наборе данных.
В остальной части этой статьи предполагается, что вы успешно установили и протестировали Open3D-ML с серверной частью PyTorch, следуя моей предыдущей статье. Это также означает, что вы загрузили набор данных SemanticKITTI. Чтобы запустить модель семантической сегментации для немаркированных данных, вам необходимо загрузить конвейер Open3D-ML. Конвейер будет состоять из модели семантической сегментации, набора данных и, возможно, других этапов предварительной/постобработки. Open3D-ML поставляется с модулями и файлами конфигурации для простой загрузки и запуска популярных конвейеров.
Чтобы сделать вывод о новых облаках точек, мы будем использовать популярную модель под названием RandLA-Net, представленную в статье 2019 года под названием RandLA-Net: эффективная семантическая сегментация крупномасштабных облаков точек. Удобно, что Open3D-ML имеет реализацию этого метода и имеет конфигурации для загрузки и запуска такого метода в наборе данных SemanticKITTI без особых усилий.
Чтобы загрузить файл конфигурации, нам нужен следующий код, заменив /path/to/Open3D/ на путь, по которому вы клонировали репозиторий Open3D при установке.
# Load an ML configuration file cfg_file = "/path/to/Open3D/build/Open3D-ML/ml3d/configs/randlanet_semantickitti.yml" cfg = _ml3d.utils.Config.load_from_file(cfg_file)
Далее мы создадим модель RandLANet, используя объект конфигурации, и добавим пути к набору данных SemanticKITTI, а также к нашему пользовательскому набору данных. Обязательно замените /path/to/save/dataset/SemanticKitti/ на путь, по которому вы сохранили данные SemanticKITTI при установке Open3D-ML. На данный момент пользовательский набор данных указывает на некоторые из моих личных облаков точек, собранных с помощью моего робота и представленных в репозитории, сопровождающем эту статью.
# Load the RandLANet model model = ml3d.models.RandLANet(**cfg.model) # Add path to the SemanticKitti dataset and your own custom dataset cfg.dataset['dataset_path'] = '/path/to/save/dataset/SemanticKitti/' cfg.dataset['custom_dataset_path'] = './pcds'
Следующим шагом является загрузка наборов данных. Для загрузки набора данных SementicKITTI в Open3D-ML есть удобные вспомогательные классы и методы.
# Load the datasets dataset = ml3d.datasets.SemanticKITTI(cfg.dataset.pop('dataset_path', None), **cfg.dataset) custom_dataset = load_custom_dataset(cfg.dataset.pop('custom_dataset_path', None))
Добавлена простая пользовательская функция для загрузки пользовательского набора данных. Обратите внимание, что этот набор данных должен быть в формате PCD.
def load_custom_dataset(dataset_path): print("Loading custom dataset") pcd_paths = glob.glob(dataset_path+"/*.pcd") pcds = [] for pcd_path in pcd_paths: pcds.append(o3d.io.read_point_cloud(pcd_path)) return pcds
Затем создается конвейер с использованием объектов конфигурации, модели и набора данных. Если они недоступны, параметры модели (контрольная точка) загружаются перед загрузкой в конвейер.
# Create the ML pipeline pipeline = ml3d.pipelines.SemanticSegmentation(model, dataset=dataset, device="gpu", **cfg.pipeline) # Download the weights. ckpt_folder = "./logs/" os.makedirs(ckpt_folder, exist_ok=True) ckpt_path = ckpt_folder + "randlanet_semantickitti_202201071330utc.pth" randlanet_url = "https://storage.googleapis.com/open3d-releases/model-zoo/randlanet_semantickitti_202201071330utc.pth" if not os.path.exists(ckpt_path): cmd = "wget {} -O {}".format(randlanet_url, ckpt_path) os.system(cmd) # Load the parameters of the model. pipeline.load_ckpt(ckpt_path=ckpt_path)
Чтобы запустить модель на немаркированном облаке точек из набора тестов SemanticKITTI, мы сначала выбираем заданную точку данных по ее индексу, а затем запускаем действие вывода из конвейера. Вы можете изменить значение переменной pc_idx, чтобы выбрать другое облако точек.
# Get one test point cloud from the SemanticKitti dataset pc_idx = 58 # change the index to get a different point cloud test_split = dataset.get_split("test") data = test_split.get_data(pc_idx) # run inference on a single example. # returns dict with 'predict_labels' and 'predict_scores'. result = pipeline.run_inference(data)
Экземпляр данных Point Cloud в наборе данных SemanticKITTI загружается как словарь Python, содержащий ключи «point», «feat» и «label». Последние два имеют None и массив Numpy, заполненный нулями в качестве значений соответственно, и не используются во время логического вывода. Ключ point связан с массивом Numpy, содержащим координаты x, y и z точек LiDAR. Чтобы визуализировать результат вывода с помощью визуализатора Open3D, нам нужно создать объект «Облако точек» из «точки» части словаря, а затем раскрасить точки с помощью меток, возвращенных выводом.
# Create a pcd to be visualized pcd = o3d.geometry.PointCloud() xyz = data["point"] # Get the points pcd.points = o3d.utility.Vector3dVector(xyz) # Get the color associated with each predicted label colors = [COLOR_MAP[clr] for clr in list(result['predict_labels'])] pcd.colors = o3d.utility.Vector3dVector(colors) # Add color data to the point cloud # Create visualization custom_draw_geometry(pcd)
Набор данных SemanticKITTI содержит 19 классов плюс фоновый класс. Должно быть предоставлено цветовое отображение от метки класса к цвету точки. Для удобочитаемости цвета RGB определены как целые числа, но визуализатор использует двойные числа от 0,0 до 1,0, поэтому предоставляется некоторый код для преобразования.
# Class colors, RGB values as ints for easy reading COLOR_MAP = { 0: (0, 0, 0), 1: (245, 150, 100), 2: (245, 230, 100), 3: (150, 60, 30), 4: (180, 30, 80), 5: (255, 0., 0), 6: (30, 30, 255), 7: (200, 40, 255), 8: (90, 30, 150), 9: (255, 0, 255), 10: (255, 150, 255), 11: (75, 0, 75), 12: (75, 0., 175), 13: (0, 200, 255), 14: (50, 120, 255), 15: (0, 175, 0), 16: (0, 60, 135), 17: (80, 240, 150), 18: (150, 240, 255), 19: (0, 0, 255), } # Convert class colors to doubles from 0 to 1, as expected by the visualizer for label in COLOR_MAP: COLOR_MAP[label] = tuple(val/255 for val in COLOR_MAP[label])
Пользовательская функция, которая рисует облако точек с результатом семантической сегментации, выглядит следующим образом.
def custom_draw_geometry(pcd): vis = o3d.visualization.Visualizer() vis.create_window() vis.get_render_option().point_size = 2.0 vis.get_render_option().background_color = np.asarray([1.0, 1.0, 1.0]) vis.add_geometry(pcd) vis.run() vis.destroy_window()
Чтобы выполнить вывод на наших личных данных, мы следуем аналогичному процессу. Предоставляется индекс для желаемой точки данных, и выполняется пользовательская функция для загрузки и предварительной обработки файла PCD перед передачей результирующего словаря в конвейер. Затем мы раскрашиваем и отображаем сегментированное облако точек.
# Get one test point cloud from the custom dataset pc_idx = 2 # change the index to get a different point cloud data, pcd = prepare_point_cloud_for_inference(custom_dataset[pc_idx]) # Run inference result = pipeline.run_inference(data) # Colorize the point cloud with predicted labels colors = [COLOR_MAP[clr] for clr in list(result['predict_labels'])] pcd.colors = o3d.utility.Vector3dVector(colors) # Create visualization custom_draw_geometry(pcd)
Пользовательская функция для подготовки данных получает PCD, полученный из списка PCD, удаляет неконечные точки (значения nan и +/-inf), получает данные точек из PCD и создает с их помощью словарь, подходящий для конвейера. . Затем он возвращает PCD и словарь.
def prepare_point_cloud_for_inference(pcd): # Remove NaNs and infinity values pcd.remove_non_finite_points() # Extract the xyz points xyz = np.asarray(pcd.points) # Set the points to the correct format for inference data = {"point":xyz, 'feat': None, 'label':np.zeros((len(xyz),), dtype=np.int32)} return data, pcd
Чтобы запустить код и увидеть результаты самостоятельно, активируйте среду Conda и выполните следующие действия.
Шаг 1. Клонируйте репозиторий
git clone https://github.com/carlos-argueta/open3d_experiments.git
Шаг 2: Запустите код
cd open3d_experiments python3 semantic_torch.py
Шаг 3. Устранение неполадок
Если вы получаете сообщение об ошибке: RuntimeError: Ожидается, что все тензоры будут на одном устройстве, но обнаружено как минимум два устройства, cpu и cuda:0! (при проверке аргумента на индекс аргумента в методе wrapper_gather)
Откройте файл /path/to/your/conda-env/lib/python3.9/site-packages/open3d/_ml3d/torch/modules/losses/semseg_loss.py, заменив /path/to/your/conda-env с путем к вашей среде Conda и python3.9 с вашей версией Python.
Затем найдите строку 9 и добавьте в ее конец .to(device).
Закройте и сохраните файл, и это должно решить проблему.
Если вы получаете сообщение об ошибке: ModuleNotFoundError: No module named ‘tensorboard’, запустите:
pip install tensorboard
Шаг 4: Наслаждайтесь!