Pytorch1.7 は PointNet++ 点群セグメンテーション (Open3D 視覚化を含む) を再現します (記事の最後に私が作成した本の継ぎ目認識プロジェクトのコードがあります)

  デザインを完成させるには、PointNet++ のオブジェクト分類、パーツ分割、シーン分割を再現し、インスピレーションやアイデアを見つけ、ピットを踏んだ記録を作成します。

ダウンロードコード

https://github.com/yanx27/Pointnet_Pointnet2_pytorch
  私の動作環境はpytorch1.7+cuda11.0です。

訓練

  PointNet++ コードにより、3D オブジェクトの分類、オブジェクト部分のセグメンテーション、およびセマンティック シーンのセグメンテーションが可能になります。

オブジェクトの分類

  データセットModelNet40 をダウンロードし、フォルダーに保存しますdata/modelnet40_normal_resampled/

## e.g., pointnet2_ssg without normal features
python train_classification.py --model pointnet2_cls_ssg --log_dir pointnet2_cls_ssg
python test_classification.py --log_dir pointnet2_cls_ssg

## e.g., pointnet2_ssg with normal features
python train_classification.py --model pointnet2_cls_ssg --use_normals --log_dir pointnet2_cls_ssg_normal
python test_classification.py --use_normals --log_dir pointnet2_cls_ssg_normal

## e.g., pointnet2_ssg with uniform sampling
python train_classification.py --model pointnet2_cls_ssg --use_uniform_sample --log_dir pointnet2_cls_ssg_fps
python test_classification.py --use_uniform_sample --log_dir pointnet2_cls_ssg_fps
  • メイン フォルダーでコードを実行すると、python train_classification.py --model pointnet2_cls_ssg --log_dir pointnet2_cls_ssgエラーが報告される場合があります。
    ImportError: cannot import name 'PointNetSetAbstraction'
    その理由は、pointnet2_cls_ssg.py ファイルがインポートされるときの作業ディレクトリは models フォルダーですが、実際の作業ディレクトリはモデルの上位ディレクトリであるためです。したがって、 from pointnet2_utils import PointNetSetAbstractionpointnet2_cls_ssg.pyで変更する必要がありますfrom models.pointnet2_utils import PointNetSetAbstraction

  README.md ファイルを参照してください。分類については主な焦点ではないため、ここでは省略します。

部品部門

  パーツ分割とは、椅子の脚を分割するなど、オブジェクトのパーツを分割することです。
  データセットShapeNet をダウンロードし、フォルダーに保存しますdata/shapenetcore_partanno_segmentation_benchmark_v0_normal/
  ランニングも簡単です。

## e.g., pointnet2_msg
python train_partseg.py --model pointnet2_part_seg_msg --normal --log_dir pointnet2_part_seg_msg
python test_partseg.py --normal --log_dir pointnet2_part_seg_msg

  シェイプネット データ セット txt ファイル形式: 最初の 3 点は点群の位置座標である xyz で、最後の 3 点は点群の法線情報、最後の点はこの点が属する小さなカテゴリです。つまり、1 はカテゴリの最初の 50 個の小さいものに属することを意味します。

  open3d (ランダム カラー マッチング) を使用してシェイプネット データセットの txt ファイルを視覚化するコードを作成します。

import open3d as o3d
import numpy as np
'''
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(BASE_DIR)
sys.path.append(BASE_DIR)
sys.path.append(os.path.join(ROOT_DIR, 'data_utils'))
'''
 
txt_path = '/home/lin/CV_AI_learning/Pointnet_Pointnet2_pytorch-master/data/shapenetcore_partanno_segmentation_benchmark_v0_normal/02691156/1b3c6b2fbcf834cf62b600da24e0965.txt'
# 通过numpy读取txt点云
pcd = np.genfromtxt(txt_path, delimiter=" ")
 
pcd_vector = o3d.geometry.PointCloud()
# 加载点坐标
# txt点云前三个数值一般对应x、y、z坐标,可以通过open3d.geometry.PointCloud().points加载
# 如果有法线或颜色,那么可以分别通过open3d.geometry.PointCloud().normals或open3d.geometry.PointCloud().colors加载
pcd_vector.points = o3d.utility.Vector3dVector(pcd[:, :3])
pcd_vector.colors = o3d.utility.Vector3dVector(pcd[:, 3:6])
o3d.visualization.draw_geometries([pcd_vector])

  GPU メモリは、batch_size を減らすのに十分ではありません。
  ここでトレーニングを行った後、コードの best_model.pth が 150 ラウンドのトレーニングを継続しました。RTX3080 シングル グラフィックス カードでは 1 ラウンドのトレーニングに 6 ~ 7 分かかり、150 ラウンドには半日以上かかりました。インターネット上のコードは基本的に、セグメンテーションのいくつかのパラメーターをテストして視覚化せずに終了します。結果を視覚化するには、このブログを参照してください: PointNet++ セグメンテーション予測結果
  の視覚化このブログでは、まずネットワークを使用して入力画像の予測結果を txt ファイルとして保存し、その後 Matplotlib を使用して可視化します。処理は少し複雑です。可視化には open3d を使用する方が簡単です。コードは次のとおりです。

import tqdm
import matplotlib
import torch
import os
import warnings
import numpy as np
import open3d as o3d
from torch.utils.data import Dataset
import pybullet as p
from models.pointnet2_part_seg_msg import get_model as pointnet2
import time

warnings.filterwarnings('ignore')
matplotlib.use("Agg")
def pc_normalize(pc):
    centroid = np.mean(pc, axis=0)
    pc = pc - centroid
    m = np.max(np.sqrt(np.sum(pc ** 2, axis=1)))
    pc = pc / m
    return pc,centroid,m

def generate_pointcloud(color_image, depth_image,width=1280,height=720,fov=50,near=0.01,far=5):
    rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(color_image, depth_image,convert_rgb_to_intensity=False)
    intrinsic = o3d.camera.PinholeCameraIntrinsic(o3d.camera.PinholeCameraIntrinsicParameters.Kinect2DepthCameraDefault )

    aspect = width / height

    projection_matrix = p.computeProjectionMatrixFOV(fov, aspect, near, far)
    intrinsic.set_intrinsics(width=width, height=height, fx=projection_matrix[0]*width/2, fy=projection_matrix[5]*height/2, cx=width/2, cy=height/2)
    point_cloud = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, intrinsic)
    
    point_cloud.estimate_normals( search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30))
    return point_cloud

class PartNormalDataset(Dataset):
    def __init__(self, point_cloud, npoints=2500, normal_channel=False):
        self.npoints = npoints # 采样点数
        self.cat = {}
        self.normal_channel = normal_channel # 是否使用法向信息

        position_data = np.asarray(point_cloud.points)
        normal_data = np.asarray(point_cloud.normals)
        self.raw_pcd = np.hstack([position_data,normal_data]).astype(np.float32)

        self.cat = {'board':'12345678'}
        # 输出的是元组,('Airplane',123.txt)

        self.classes = {'board': 0} 

        data = self.raw_pcd

        if not self.normal_channel:  # 判断是否使用法向信息
            self.point_set = data[:, 0:3]
        else:
            self.point_set = data[:, 0:6]

        self.point_set[:, 0:3],self.centroid,self.m = pc_normalize(self.point_set[:, 0:3]) # 做一个归一化

        choice = np.random.choice(self.point_set.shape[0], self.npoints, replace=True) # 对一个类别中的数据进行随机采样 返回索引,允许重复采样
        # resample
        self.point_set =  self.point_set[choice, :] # 根据索引采样

    def __getitem__(self, index):

        cat = list(self.cat.keys())[0]
        cls = self.classes[cat] # 将类名转换为索引
        cls = np.array([cls]).astype(np.int32)

        return self.point_set, cls, self.centroid, self.m # pointset是点云数据,cls十六个大类别,seg是一个数据中,不同点对应的小类别

    def __len__(self):
        return 1



class Generate_txt_and_3d_img:
    def __init__(self,num_classes,testDataLoader,model,visualize = False):
        self.testDataLoader = testDataLoader
        self.num_classes = num_classes
        self.heat_map = False # 控制是否输出heatmap
        self.visualize = visualize # 是否open3d可视化
        self.model = model

        self.generate_predict()
        self.o3d_draw_3d_img()

    def __getitem__(self, index):
        return self.predict_pcd_colored

    def generate_predict(self):

        for _, (points, label,centroid,m) in tqdm.tqdm(enumerate(self.testDataLoader),
                                                                      total=len(self.testDataLoader),smoothing=0.9):

            #点云数据、整个图像的标签、每个点的标签、  没有归一化的点云数据(带标签)torch.Size([1, 7, 2048])
            points = points.transpose(2, 1)
            #print('1',target.shape) # 1 torch.Size([1, 2048])
            xyz_feature_point = points[:, :6, :]

            model = self.model

            seg_pred, _ = model(points, self.to_categorical(label, 1))
            seg_pred = seg_pred.cpu().data.numpy()

            if self.heat_map:
                out =  np.asarray(np.sum(seg_pred,axis=2))
                seg_pred = ((out - np.min(out) / (np.max(out) - np.min(out))))
            else:
                seg_pred = np.argmax(seg_pred, axis=-1)  # 获得网络的预测结果 b n c

            seg_pred = np.concatenate([np.asarray(xyz_feature_point), seg_pred[:, None, :]],
                    axis=1).transpose((0, 2, 1)).squeeze(0) 

            self.predict_pcd = seg_pred
            self.centroid = centroid
            self.m = m


    def o3d_draw_3d_img(self):

        pcd = self.predict_pcd
        pcd_vector = o3d.geometry.PointCloud()
        # 加载点坐标
        pcd_vector.points = o3d.utility.Vector3dVector(self.m * pcd[:, :3] + self.centroid)
        # colors = np.random.randint(255, size=(2,3))/255
        colors = np.array([[0.8, 0.8, 0.8],[1,0,0]])
        pcd_vector.colors = o3d.utility.Vector3dVector(colors[list(map(int,pcd[:, 6])),:])

        if self.visualize:
            coord_mesh = o3d.geometry.TriangleMesh.create_coordinate_frame(size = 0.1, origin = [0,0,0])
            o3d.visualization.draw_geometries([pcd_vector,coord_mesh])
        self.predict_pcd_colored = pcd_vector

    def to_categorical(self,y, num_classes):
        """ 1-hot encodes a tensor """
        new_y = torch.eye(num_classes)[y.cpu().data.numpy(),]
        if (y.is_cuda):
            return new_y.cuda()
        return new_y

def load_models(model_dict={'PonintNet': [pointnet2(num_classes=2,normal_channel=True).eval(),r'./log/part_seg/pointnet2_part_seg_msg/checkpoints']}):
    model = list(model_dict.values())[0][0]
    checkpoints_dir = list(model_dict.values())[0][1]
    weight_dict = torch.load(os.path.join(checkpoints_dir,'best_model.pth'))
    model.load_state_dict(weight_dict['model_state_dict'])
    return model

class Open3dVisualizer():

	def __init__(self):

		self.point_cloud = o3d.geometry.PointCloud()
		self.o3d_started = False

		self.vis = o3d.visualization.VisualizerWithKeyCallback()
		self.vis.create_window()

	def __call__(self, points, colors):

		self.update(points, colors)

		return False

	def update(self, points, colors):
		coord_mesh = o3d.geometry.TriangleMesh.create_coordinate_frame(size = 0.15, origin = [0,0,0])
		self.point_cloud.points = points
		self.point_cloud.colors = colors
		# self.point_cloud.transform([[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]])
		# self.vis.clear_geometries()
		# Add geometries if it is the first time
		if not self.o3d_started:
			self.vis.add_geometry(self.point_cloud)
			self.vis.add_geometry(coord_mesh)
			self.o3d_started = True

		else:
			self.vis.update_geometry(self.point_cloud)
			self.vis.update_geometry(coord_mesh)

		self.vis.poll_events()
		self.vis.update_renderer()

if __name__ =='__main__':
    
    num_classes = 2 # 填写数据集的类别数 如果是s3dis这里就填13   shapenet这里就填50
    
    color_image = o3d.io.read_image('image/rgb1.jpg')
    depth_image = o3d.io.read_image('image/depth1.png')
    
    point_cloud = generate_pointcloud(color_image=color_image, depth_image=depth_image)

    TEST_DATASET = PartNormalDataset(point_cloud,npoints=30000, normal_channel=True)
    testDataLoader = torch.utils.data.DataLoader(TEST_DATASET, batch_size=1, shuffle=False, num_workers=0,drop_last=True)
    predict_pcd = Generate_txt_and_3d_img(num_classes,testDataLoader,load_models(),visualize = True)

  単一の点群の予測を視覚化するために前のコードを変更します。点群は GRB 画像と深度画像によって生成されます。点群を直接入力したい場合は、コードを少し変更できます。現在、シェイプネット データセット形式のデータのみが対象です。--normalここで、トレーニング中に選択した場合は、normal_channelに変更する必要があることに注意してくださいTrue
  トレーニング効果を確認し、modelnet40 のチェア ファイルを使用して予測を行います。

  椅子は大きく4つの部分に分かれていることがわかりますが、椅子の背もたれと脚はしっかりと分割されていますが、肘掛けの一部が座クッションに分割されており、結局のところ、トレーニング時間は長くありません。modelnet40 データ セットは分類のみに使用され、セグメンテーション アノテーションはありません。そのため、ここではシェイプネットでマークされた椅子の点群を視覚化して、椅子 (上の椅子ではありません) の各部分のセグメンテーションを確認します。

  ここでは、椅子が背もたれ、肘掛け、クッション、脚の 4 つの部分に分かれていることがより明らかです。
  最初に効果を観察した後、トレーニング用に独自のデータセットの作成を開始できます。私が書いた記事「CloudCompare が ShapeNet 形式の点群データセットを作成する」を参照してください。

シーンの分割

  パーツ セグメンテーション ネットワークは、ポイント ラベルがターゲット パーツ ラベルの代わりにセマンティック オブジェクト クラスになるセマンティック シーン セグメンテーションに簡単に拡張できます。
  実験はスタンフォード 3D セマンティック分析データセットで行われます。このデータセットには、271 の部屋を含む、Matterport スキャナーからの 6 つの地域の 3D スキャンが含まれています。スキャン内の各ポイントには、13 のカテゴリ (椅子、テーブル、床、壁などと散らかったもの) のいずれかの意味ラベルが付けられます。
  まず、ファイルS3DISをダウンロードし、フォルダーに保存しdata/s3dis/Stanford3dDataset_v1.2_Aligned_Version/
  データを処理すると、データが保存されますdata/stanford_indoor3d/

cd data_utils
python collect_indoor3d_data.py

  走る:

## Check model in ./models 
## e.g., pointnet2_ssg
python train_semseg.py --model pointnet2_sem_seg --test_area 5 --log_dir pointnet2_sem_seg
python test_semseg.py --log_dir pointnet2_sem_seg --test_area 5 --visual

  上記の操作が完了すると、log/sem_seg/pointnet2_sem_seg/visual/予測結果のobjファイルが生成され、open3dで可視化できますが、ここで生成されたobjファイルにも色が付いているため、o3d.io.read_triangle_mesh関数では可視化できません。セマンティクス情報を表す情報なので、リスト データとして読み取って、o3d.geometry.PointCloud() 変数表示として定義する必要があります。コードは次のとおりです。

import copy
import numpy as np
import open3d as o3d
import os

objFilePath = 'log/sem_seg/pointnet2_sem_seg/visual/Area_5_office_8_gt.obj'

with open(objFilePath) as file:
    points = []
    while 1:
        line = file.readline()
        if not line:
            break
        strs = line.split(" ")
        if strs[0] == "v":
            points.append(np.array(strs[1:7],dtype=float))
        if strs[0] == "vt":
            break
# points原本为列表,需要转变为矩阵,方便处理          
pcd = np.array(points)

pcd_vector = o3d.geometry.PointCloud()
pcd_vector.points = o3d.utility.Vector3dVector(pcd[:, :3])
pcd_vector.colors = o3d.utility.Vector3dVector(pcd[:,3:6])
o3d.visualization.draw_geometries([pcd_vector])

  カンカンエリア_5のオフィス_8の効果:
  元の画像:

  グラウンドトゥルース:
ここに画像の説明を挿入
  予測:


  OK、この PointNet++ を大まかに再現し、プロジェクトの完了に備えて次の点群セグメンテーションに焦点を当てました。一般的に、学習と予測のプロセスは難しくありませんが、健康効果については、可視化の部分にかなりの時間がかかりました。パーツ セグメンテーションとシーン セグメンテーションは本質的に同じものです。つまり、2 つのセグメンテーションはコードでのトレーニングに異なるモデルを使用します。その後、学習用のデータセットを自作する予定ですが、まずはパーツセグメンテーションのモデルを使ってやってみますが、やはりシーンセグメンテーションをS3DISという形でデータセットにするのはちょっと面倒です。つまり、このブログをフォローすれば、必ず PointNet++ を実行できるようになります。


  pointnet++ を使用して自作のブック シーム認識プロジェクトを追加します。データ セットとコードは GitHub: https://github.com/struggler176393/Pointnet_book_seatにあります。

おすすめ

転載: blog.csdn.net/astruggler/article/details/128354761