从零部署点云实例分割项目PointGroup

环境配置

python 3.7

NVIDIA RTX 4090 

一、下载

论文地址:2004.01658.pdf (arxiv.org)

Github地址:dvlab-research/PointGroup: PointGroup: Dual-Set Point Grouping for 3D Instance Segmentation (github.com)

二、环境配置

依据原项目的步骤进行环境配置,训练阶段报错RuntimeError: cublas runtime error : the GPU program failed to execute at …………aten/src/THC/THCBlas.cu:259

实验环境为4090,不能兼容使用torch1.1,故重新安装高版本torch 1.13.1+cu116

conda create -n pointgroup python=3.7
conda activate pointgroup
pip install torch==1.13.1+cu116 torchvision==0.14.1+cu116 torchaudio==0.13.1 --extra-index-url https://download.pytorch.org/whl/cu116
pip install cmake
pip install plyfile
pip install tensorboardX
pip install pyyaml
pip install scipy

conda install libboost
conda install -c bioconda google-sparsehash 

#编译spconv
cd lib/spconv
python setup.py bdist_wheel
cd dist
pip install spconv-1.0-cp37-cp37m-linux_x86_64.whl

①在编译spconv的时候,若提示Your installed Caffe2 version uses cuDNN but I cannot find the cuDNN libraries.  Please set the proper cuDNN prefixes and / or install cuDNN.

在lib/spconv/CMakeLists.txt中,添加以下内容:(cudnn是之前电脑中已经自带的)

set(CUDNN_INCLUDE_DIR "/home/your_name/.conda/pkgs/cudnn-8.2.1.32-h86fa8c9_0/include")
set(CUDNN_INCLUDE_PATH "/home/your_name/.conda/pkgs/cudnn-8.2.1.32-h86fa8c9_0/include")
set(CUDNN_LIBRARY "/home/your_name/.conda/pkgs/cudnn-8.2.1.32-h86fa8c9_0/lib/libcudnn.so")
set(CUDNN_LIBRARY_PATH "/home/your_name/.conda/pkgs/cudnn-8.2.1.32-h86fa8c9_0/lib/libcudnn.so")

②报错error: no matching function for call to ‘torch::jit::RegisterOperators::RegisterOperators(const char [28], <unresolved overloaded function type>)’ 

torch版本升级带来的API改变问题,用 torch::RegisterOperators()替代 torch::jit::RegisterOperator()

③Unsupported gpu architecture 'compute_89'

在shell中先执行export TORCH_CUDA_ARCH_LIST="8.6",降低算力

④error: more than one operator ">" matches these operands:

 error: more than one operator "==" matches these operands:

cuda和torch的头文件都提供了相同的重载运算符,编译器不知道用哪一个

在lib/spconv/CMakeLists.txt中,添加以下内容:

add_definitions(-D__CUDA_NO_HALF_OPERATORS__)

然后编译pointgroup 

#编译pointgroup
cd ../../../lib/pointgroup_ops
python setup.py develop

①报错src/bfs_cluster/bfs_cluster.h:11:10: fatal error: THC/THC.h: No such file or directory

删除掉该文件中这一行头文件,即可。

三、数据准备

1.下载

参考网址:scannet v2 数据集下载(WP)_scannet数据集网盘-CSDN博客

 下载后是如下目录结构:

 2.组织数据目录结构

按照github中的目标目录结构进行组织,编写如下脚本实现:

import os
import shutil


root_dir = "dataset/scannetv2"
dataset_dir_src = "dataset/scannetv2/Scannet_V2"
dataset_train = os.path.join(root_dir, 'train')
dataset_val = os.path.join(root_dir, 'val')
dataset_test = os.path.join(root_dir, 'test')

sub_str_list = ['_vh_clean_2.ply', '_vh_clean_2.labels.ply', '_vh_clean_2.0.010000.segs.json', '.aggregation.json']

if not os.path.exists(dataset_train):
    os.makedirs(dataset_train)
if not os.path.exists(dataset_val):
    os.makedirs(dataset_val)
if not os.path.exists(dataset_test):
    os.makedirs(dataset_test)

# 生成train目录
for root, dir, files in os.walk(os.path.join(dataset_dir_src, 'scans_train')):
    for file in files:
        for sub_str in sub_str_list:
            if sub_str in file:
                file_src_path = os.path.join(root, file)
                file_target_path = os.path.join(dataset_train, file)
                shutil.copy(file_src_path, file_target_path)
                break
    pass
pass

# 生成val目录
for root, dir, files in os.walk(os.path.join(dataset_dir_src, 'scans_train')):
    for file in files:
        for sub_str in sub_str_list:
            if sub_str in file:
                file_src_path = os.path.join(root, file)
                file_target_path = os.path.join(dataset_val, file)
                shutil.copy(file_src_path, file_target_path)
                break
    pass
pass

# 生成test目录
for root, dir, files in os.walk(os.path.join(dataset_dir_src, 'scans_test')):
    for file in files:
        for sub_str in sub_str_list:
            if sub_str in file:
                file_src_path = os.path.join(root, file)
                file_target_path = os.path.join(dataset_test, file)
                shutil.copy(file_src_path, file_target_path)
                break
    pass
pass

# 复制tsv文件
shutil.copy(os.path.join(dataset_dir_src, 'scannetv2-labels.combined.tsv'), os.path.join(root_dir, 'scannetv2-labels.combined.tsv'))

3.生成最终数据集 

cd dataset/scannetv2
python prepare_data_inst.py --data_split train
python prepare_data_inst.py --data_split val
python prepare_data_inst.py --data_split test

预处理后数据解析】:

dataset/scannetv2/train/*_inst_nostuff.pth:内部为一个元组,总共有4个元素;其他文件用不上。

第一个元素是一个维度为(N, 3)的数组,为点云三维坐标;

第二个元素是一个维度为(N, 3)的数组,为归一化到[-1, 1]的点云rgb颜色;

第三个元素是一个维度为(N, 1)的数组,为该场景内该点云每个点的语义标签;-100为忽略标签

第四个元素是一个维度为(N, 1)的数组,为该场景内该点云每个点的实例标签;-100为忽略标签

四、代码分析

Dataset——Dataset

Dataloader——torch.utils.data.DataLoader

Model——PointGroup

Optimizer——torch.optim.Adam

Scheduler——无

Loss——torch.nn.CrossEntropyLoss交叉熵函数以及torch.nn.BCELoss二分类交叉熵损失函数

五、训练流程分析

python train.py --config config/pointgroup_run1_scannet.yaml

细节参考原论文以及其他翻译论文(CVPR 2020——PointGroup: Dual-Set Point Grouping for 3D Instance Segmentation_pointgroup源代码分析-CSDN博客

六、推理流程分析

1.使用验证集进行推理

首先生成验证集的groud_truth:

cd dataset/scannetv2
python prepare_data_inst_gttxt.py
cd ../..

执行推理。 

CUDA_VISIBLE_DEVICES=0 python test.py --config config/pointgroup_default_scannet.yaml --pretrain 'checkpoint/pointgroup.pth'

 结果如下(只产生指标不保存可视化结果):

2.使用测试集进行推理

分别设置config文件中TEST模块的(splitevalsave_instance) (testFalseTrue)

CUDA_VISIBLE_DEVICES=0 python test.py --config config/pointgroup_run1_scannet.yaml --pretrain 'checkpoint/pointgroup.pth'

3.可视化

执行以下代码,产生的文件可在CloudCompare点云处理软件中打开。

"""
@Author     :   jiguotong
@Contact    :   xxxx@qq.com
@site       :   
-----------------------------------------------
@Software   :   VScode
@Project    :   utils
@File       :   
@Version    :   v0.1
@Time       :   2024/2/21
@License    :   (C)Copyright    2021-2024,  jiguotong
@Reference  :
@Description:   可视化由pointgroup的test过程所产生的文件
@Thought    :
"""
import numpy as np
import torch
import os

points_path_root = "dataset/scannetv2/test"
pred_files_root = "exp/scannetv2/pointgroup/pointgroup_run1_scannet/result/epoch384_nmst0.3_scoret0.09_npointt100/test"
save_path_root = os.path.join(pred_files_root, 'output')
if not os.path.exists(save_path_root):
    os.makedirs(save_path_root)

scenes_list = os.listdir(pred_files_root)
scenes_list = [file for file in scenes_list if file.endswith(".txt")]

for scene_name in scenes_list:
    point_name = scene_name.split('.')[0] + '_inst_nostuff.pth'
    point_path = os.path.join(points_path_root, point_name)
    coord = torch.load(point_path)[0]
    ins_pred = np.zeros((coord.shape[0], 1), dtype=np.uint)
    save_path = os.path.join(save_path_root, scene_name)
    scene_path = os.path.join(pred_files_root, scene_name)
    with open(scene_path, "r") as f:
        sub_scene_list = f.readlines()
        for index, instance_line in enumerate(sub_scene_list):
            instance_name = instance_line.split(' ')[0]
            instance_path = os.path.join(pred_files_root, instance_name)
            mask = np.loadtxt(instance_path)
            mask[mask!=0] = index + 1
            mask = mask.reshape((mask.shape[0], 1))
            ins_pred[mask != 0] = mask[mask != 0]
        pass
    result = np.column_stack((coord, ins_pred))
    np.savetxt(save_path, result)
    print(scene_name+'保存完成!')
    pass
pass

可视化效果如下: 

 

七、使用自己的数据集进行训练

1.注意事项

①在dataloader中读到的坐标与颜色的数据类型应该为float32

②制作数据集时应该提前对坐标进行归一化与中心化

③注意ignore_label的赋值,scannetv2数据集中是-100;代码中,作者将语义标签>1的才进行实例聚类,由此可见,相当于是将0 1 作为了backgroup_label。

0-wall

1-floor

④若训练过程中出现train loss为负值的情况,原作者提到由于loss中含有offset,所以是正常的。

⑤ 配置文件中的scale(voxel_size)以及group中的cluster_radius等参数,要根据自己数据集进行调整。

八、代码剖析

1.Voxelization_Idx类

class Voxelization_Idx(Function):
    @staticmethod
    def forward(ctx, coords, batchsize, mode=4):
        '''
        :param ctx:
        :param coords:  long (N, dimension + 1) or (N, dimension) dimension = 3
        :param batchsize
        :param mode: int 4=mean
        :param dimension: int
        :return: output_coords:  long (M, dimension + 1) (M <= N)
        :return: output_map: int M * (maxActive + 1)
        :return: input_map: int N
        '''
        assert coords.is_contiguous()
        N = coords.size(0)
        output_coords = coords.new()

        input_map = torch.IntTensor(N).zero_()
        output_map = input_map.new()

        PG_OP.voxelize_idx(coords, output_coords, input_map, output_map, batchsize, mode)
        return output_coords, input_map, output_map


    @staticmethod
    def backward(ctx, a=None, b=None, c=None):
        return None

voxelization_idx = Voxelization_Idx.apply

PG_OP.voxelize_idx函数做了什么?

其作用是将分布在体素空间中的坐标点进行去重,并且返回每个体素含有的坐标点、原每个坐标点在体素中的索引、每个体素所含有的坐标点的索引

coords: long (N, 3)或者(N,4),若是4,则第一列为批次中的样本索引,例如0,1;其余为显示在体素空间中的索引坐标,如(1,5,6),(15,25,69),体素空间坐标从(0,0,0)开始。

batchsize:该coords中所包含的坐标,来自于几个样本。

mode:合并方式,4为mean。

output_coords:去重之后的体素空间坐标点,shape为(M,3)或者(M,4)。

input_map:shape为(N),标记第N_{i}个点,所在的体素编号,0~M-1

output_map:shape为(M,k),列出在M_{i}体素中,含有的coords索引,k为单个体素中含有最大的点个数,由此得知,output_map含有很多0,因为有的体素含有的点个数远远小于k。

2.Voxelization类

class Voxelization(Function):
    @staticmethod
    def forward(ctx, feats, map_rule, mode=4):
        '''
        :param ctx:
        :param map_rule: cuda int M * (maxActive + 1)
        :param feats: cuda float N * C
        :return: output_feats: cuda float M * C
        '''
        assert map_rule.is_contiguous()
        assert feats.is_contiguous()
        N, C = feats.size()
        M = map_rule.size(0)
        maxActive = map_rule.size(1) - 1

        output_feats = torch.cuda.FloatTensor(M, C).zero_()

        ctx.for_backwards = (map_rule, mode, maxActive, N)

        PG_OP.voxelize_fp(feats, output_feats, map_rule, mode, M, maxActive, C)
        return output_feats


    @staticmethod
    def backward(ctx, d_output_feats):
        map_rule, mode, maxActive, N = ctx.for_backwards
        M, C = d_output_feats.size()

        d_feats = torch.cuda.FloatTensor(N, C).zero_()

        PG_OP.voxelize_bp(d_output_feats.contiguous(), d_feats, map_rule, mode, M, maxActive, C)
        return d_feats, None, None

voxelization = Voxelization.apply

 PG_OP.voxelize_fp函数做了什么?

其作用是根据上一步的结果,将所有点的特征进行筛选,只剩下上一步所提到的在体素坐标空间内不重复的点,即将feats从N->M。

<think>好的,用户想了解如何实现PointGroup在单木分割上的应用,特别是在林业中的应用。首先,我需要回顾PointGroup的基本原理。PointGroup是用于点云实例分割的算法,主要应用于室内场景,但用户希望将其应用于林业中的单木分割。这可能涉及到不同的数据特性和处理步骤。 首先,林业中的点云数据通常来自机载LiDAR或地面激光扫描,与室内场景相比,树木点云更复杂,存在更多的重叠和自然变化。因此,数据处理和预处理是关键。可能需要参考引用[1]中的点云分割步骤,比如滤波方法,来去除噪声和地面点,提取树干和树冠部分。 接下来是数据预处理的具体步骤。用户需要将原始点云进行去噪和滤波,可能使用统计滤波或半径滤波。然后,分离地面点和非地面点,可能使用布料模拟滤波(CSF)等方法。此外,可能需要将大规模林业点云分割为小块,以适应算法输入尺寸。 然后,网络结构部分。PointGroup的核心是双分支结构,用于点分组和语义预测。在林业中,可能需要调整语义类别,比如树干、枝叶、地面等。此外,点云特征提取可能需要使用适合林业数据的特征,如强度、高程、密度等。 训练策略方面,林业数据通常样本量较少,可能需要数据增强,如随机旋转、缩放、添加噪声等。此外,考虑到单木实例的尺寸差异大,可能需要设计多尺度训练策略,或者调整聚类参数,如DBSCAN的eps和min_samples参数。 后处理部分,聚类结果可能出现过分割或欠分割,需要基于林业知识进行合并或拆分,比如根据树干连续性或树冠形态。同时,可能需要计算单木参数,如树高、胸径等,作为评估指标。 评估与优化方面,林业中常用指标包括检测率、分割精度等。可能需要结合实地测量数据验证结果。同时,针对树木点云密度不均的问题,可能需要优化采样策略或特征提取方法。 此外,用户提供的引用[3]提到了多种数据格式,如IMDF、AVF、RVT等,可能需要将处理后的点云数据转换为适合林业应用的格式,或与其他GIS数据集成。例如,将分割结果导出为GeoJSON或DWG格式,方便后续分析。 最后,用户可能需要参考引用[2]中提到的GNN或深度生成模型,考虑是否可以将这些方法融入PointGroup的改进中,比如利用图神经网络处理树木点云的结构关系。 需要提醒用户注意林业数据的特殊性,如点云密度低、遮挡严重等,可能需要调整算法参数或引入领域知识。此外,实现过程中可能需要使用深度学习框架如PyTorch,并处理大规模点云数据时的计算资源问题。</think>### PointGroup在单木分割中的实现方法 #### 1. 数据预处理 - **点云滤波与降噪**:使用统计滤波或半径滤波去除离群点,参考机载LiDAR点云处理方法[^1]; - **地面点分离**:采用布料模拟滤波(CSF)或渐进形态学滤波(PMF),保留树干和树冠点云; - **数据分块**:将大规模林业点云切割为$32m \times 32m$的区块,满足网络输入尺寸要求; - **特征增强**:添加高程$z$、强度$I$、法向量$\vec{n}$等特征,提升语义分割效果。 #### 2. 网络结构适配 ```python class ForestryPointGroup(PointGroup): def __init__(self): super().__init__() self.semantic_head = nn.Sequential( nn.Linear(32, 16), # 调整语义类别数 nn.ReLU(), nn.Linear(16, 4) # 树干/枝叶/地面/背景 ) ``` - **双分支设计**: - 语义分支预测点类别:$P_{cls} = \text{softmax}(W_s \cdot F)$ - 偏移分支预测聚类偏移:$\Delta x = W_o \cdot F$ - **损失函数**: $$L = \lambda_{sem}L_{ce} + \lambda_{offset}L_{smooth-l1}$$ #### 3. 聚类优化 - **密度自适应聚类**: $$eps = \alpha \cdot \text{median}(k\text{-distance})$$ 其中$\alpha$根据林分密度动态调整,解决树木间距变化问题[^3] - **形态约束后处理**: - 合并条件:$H_{diff} < 1.5m$ 且 $D_{overlap} > 60\%$ - 拆分条件:检测到多个主干分叉点 #### 4. 林业参数计算 $$ \text{树高}H = \max(z) - \min(z_{ground}) $$ $$ \text{胸径}DBH = 2\sqrt{\frac{A_{1.3m}}{\pi}} $$ 其中$A_{1.3m}$为1.3米高度处树干横截面积 #### 5. 实现流程 ```mermaid graph TD A[原始点云] --> B{预处理} B --> C[去噪滤波] B --> D[地面分离] B --> E[分块处理] E --> F[网络推理] F --> G[语义预测] F --> H[偏移预测] G --> I[密度聚类] H --> I I --> J{后处理} J --> K[形态学优化] J --> L[参数计算] ``` #### 6. 性能优化建议 - 使用**八叉树空间索引**加速点云查询 - 采用**渐进式渲染**处理大规模数据 - 融合**多时相数据**提升分割稳定性
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值