Repository: qinglew/PCN-PyTorch Branch: master Commit: 3b3d1ce97d92 Files: 40 Total size: 26.3 MB Directory structure: gitextract_fmkz5hde/ ├── .gitignore ├── README.md ├── checkpoint/ │ └── best_l1_cd.pth ├── data/ │ └── README.md ├── dataset/ │ ├── __init__.py │ └── shapenet.py ├── extensions/ │ ├── chamfer_distance/ │ │ ├── chamfer3D.cu │ │ ├── chamfer_3D.egg-info/ │ │ │ ├── PKG-INFO │ │ │ ├── SOURCES.txt │ │ │ ├── dependency_links.txt │ │ │ └── top_level.txt │ │ ├── chamfer_cuda.cpp │ │ ├── chamfer_distance.py │ │ └── setup.py │ └── earth_movers_distance/ │ ├── emd.cpp │ ├── emd.py │ ├── emd_cuda.egg-info/ │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ └── top_level.txt │ ├── emd_kernel.cu │ └── setup.py ├── metrics/ │ ├── loss.py │ └── metric.py ├── models/ │ ├── __init__.py │ └── pcn.py ├── render/ │ ├── README.md │ ├── blender.log │ ├── partial.sh │ ├── process_exr.py │ └── render_depth.py ├── requirements.txt ├── sample/ │ ├── CMakeLists.txt │ ├── README.md │ ├── mesh_sampling │ └── mesh_sampling.cpp ├── test.py ├── train.py └── visualization/ ├── __init__.py └── visualization.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .vscode __pycache__ log .idea data/pcn ================================================ FILE: README.md ================================================ # PCN: Point Completion Network ## Introduction ![PCN](images/network.png) This is implementation of PCN——Point Completion Network in pytorch. PCN is an autoencoder for point cloud completion. As for the details of the paper, please refer to [arXiv](https://arxiv.org/pdf/1808.00671.pdf). ## Environment * Ubuntu 18.04 LTS * Python 3.7.9 * PyTorch 1.7.0 * CUDA 10.1.243 ## Prerequisite Compile for cd and emd: ```shell cd extensions/chamfer_distance python setup.py install cd ../earth_movers_distance python setup.py install ``` **Hint**: Don't compile on Windows platform. As for other modules, please install by: ```shell pip install -r requirements.txt ``` ## Dataset Please reference `render` and `sample` to create your own dataset. Also, we decompressed all `.lmdb` data from [PCN](https://drive.google.com/drive/folders/1M_lJN14Ac1RtPtEQxNlCV9e8pom3U6Pa) data into `.ply` data which has smaller volume 8.1G and upload it into Google Drive. Here is the shared link: [Google Drive](https://drive.google.com/file/d/1OvvRyx02-C_DkzYiJ5stpin0mnXydHQ7/view?usp=sharing). ## Training In order to train the model, please use script: ```shell python train.py --exp_name PCN_16384 --lr 0.0001 --epochs 400 --batch_size 32 --coarse_loss cd --num_workers 8 ``` If you want to use emd to calculate the distances between coarse point clouds, please use script: ```shell python train.py --exp_name PCN_16384 --lr 0.0001 --epochs 400 --batch_size 32 --coarse_loss emd --num_workers 8 ``` ## Testing In order to test the model, please use follow script: ```shell python test.py --exp_name PCN_16384 --ckpt_path --batch_size 32 --num_workers 8 ``` Because of the computation cost for calculating emd for 16384 points, I split out the emd's evaluation. The parameter `--emd` is used for testing emd. The parameter `--novel` is for novel testing data contains unseen categories while training. The parameter `--save` is used for saving the prediction into `.ply` file and visualize the result into `.png` image. ## Pretrained Model The pretrained model is in `checkpoint/`. ## Results I trained the model on Nvidia GPU 1080Ti with L1 Chamfer Distance for 400 epochs with initial learning rate 0.0001 and decay by 0.7 every 50 epochs. The batch size is 32. Best model is the minimum L1 cd one in validation data. ### Quantitative Result The threshold for F-Score is 0.01. #### Seen Categories: Category | L1_CD(1e-3) | L2_CD(1e-4) | EMD(1e-3) | F-Score(%) -- | -- | -- | -- | -- Airplane | 6.0028 | 1.7323 | 10.5922 | 86.2954 Cabinet | 11.2092 | 4.7351 | 27.1505 | 61.6697 Car | 9.1304 | 2.7157 | 14.3661 | 70.5874 Chair | 12.0340 | 5.8717 | 22.4904 | 58.2958 Lamp | 12.6754 | 7.5891 | 58.7799 | 57.8894 Sofa | 12.8218 | 6.4572 | 19.2891 | 53.4009 Table | 9.8840 | 4.5669 | 23.7691 | 70.9750 Vessel | 10.1603 | 4.2766 | 17.9761 | 66.6521 **Average** | 10.4897 | 4.7431 | 24.3017 | 65.7207 #### Unseen Categories Category | L1_CD(1e-3) | L2_CD(1e-4) | EMD(1e-3) | F-Score(%) -- | -- | -- | -- | -- Bus | 10.5110 | 4.4648 | 17.0274 | 66.9774 Bed | 24.9320 | 32.4809 | 42.7974 | 32.2265 Bookshelf | 15.8186 | 13.1783 | 28.5608 | 50.0337 Bench | 12.1345 | 7.3033 | 12.7497 | 62.4376 Guitar | 11.4964 | 5.9601 | 28.4223 | 59.4976 Motorbike | 15.3426 | 8.7723 | 21.8634 | 44.7431 Skateboard| 13.1909 | 7.9711 | 17.9910 | 58.4427 Pistol | 17.4897 | 15.5062 | 33.8937 | 45.6073 **Average** | 15.1145 | 11.9546 | 25.4132 | 52.4958 ### Qualitative Result #### Seen Categories ![seen](images/seen_categories.png) #### Unseen Categories ![unseen](images/unseen_categories.png) ## Citation * [PCN: Point Completion Network](https://arxiv.org/pdf/1808.00671.pdf) * [PCN's official Tensorflow implementation](https://github.com/wentaoyuan/pcn) ================================================ FILE: checkpoint/best_l1_cd.pth ================================================ [File too large to display: 26.2 MB] ================================================ FILE: data/README.md ================================================ # data Please download `PCN.zip` from Cloud and unzip it here. ```shell unzip PCN.zip ``` ================================================ FILE: dataset/__init__.py ================================================ from dataset.shapenet import ShapeNet ================================================ FILE: dataset/shapenet.py ================================================ import sys sys.path.append('.') import os import random import torch import torch.utils.data as data import numpy as np import open3d as o3d class ShapeNet(data.Dataset): """ ShapeNet dataset in "PCN: Point Completion Network". It contains 28974 training samples while each complete samples corresponds to 8 viewpoint partial scans, 800 validation samples and 1200 testing samples. """ def __init__(self, dataroot, split, category): assert split in ['train', 'valid', 'test', 'test_novel'], "split error value!" self.cat2id = { # seen categories "airplane" : "02691156", # plane "cabinet" : "02933112", # dresser "car" : "02958343", "chair" : "03001627", "lamp" : "03636649", "sofa" : "04256520", "table" : "04379243", "vessel" : "04530566", # boat # alis for some seen categories "boat" : "04530566", # vessel "couch" : "04256520", # sofa "dresser" : "02933112", # cabinet "airplane" : "02691156", # airplane "watercraft": "04530566", # boat # unseen categories "bus" : "02924116", "bed" : "02818832", "bookshelf" : "02871439", "bench" : "02828884", "guitar" : "03467517", "motorbike" : "03790512", "skateboard": "04225987", "pistol" : "03948459", } # self.id2cat = {cat_id: cat for cat, cat_id in self.cat2id.items()} self.dataroot = dataroot self.split = split self.category = category self.partial_paths, self.complete_paths = self._load_data() def __getitem__(self, index): if self.split == 'train': partial_path = self.partial_paths[index].format(random.randint(0, 7)) else: partial_path = self.partial_paths[index] complete_path = self.complete_paths[index] partial_pc = self.random_sample(self.read_point_cloud(partial_path), 2048) complete_pc = self.random_sample(self.read_point_cloud(complete_path), 16384) return torch.from_numpy(partial_pc), torch.from_numpy(complete_pc) def __len__(self): return len(self.complete_paths) def _load_data(self): with open(os.path.join(self.dataroot, '{}.list').format(self.split), 'r') as f: lines = f.read().splitlines() if self.category != 'all': lines = list(filter(lambda x: x.startswith(self.cat2id[self.category]), lines)) partial_paths, complete_paths = list(), list() for line in lines: category, model_id = line.split('/') if self.split == 'train': partial_paths.append(os.path.join(self.dataroot, self.split, 'partial', category, model_id + '_{}.ply')) else: partial_paths.append(os.path.join(self.dataroot, self.split, 'partial', category, model_id + '.ply')) complete_paths.append(os.path.join(self.dataroot, self.split, 'complete', category, model_id + '.ply')) return partial_paths, complete_paths def read_point_cloud(self, path): pc = o3d.io.read_point_cloud(path) return np.array(pc.points, np.float32) def random_sample(self, pc, n): idx = np.random.permutation(pc.shape[0]) if idx.shape[0] < n: idx = np.concatenate([idx, np.random.randint(pc.shape[0], size=n-pc.shape[0])]) return pc[idx[:n]] ================================================ FILE: extensions/chamfer_distance/chamfer3D.cu ================================================ #include #include #include #include #include __global__ void NmDistanceKernel(int b,int n,const float * xyz,int m,const float * xyz2,float * result,int * result_i){ const int batch=512; __shared__ float buf[batch*3]; for (int i=blockIdx.x;ibest){ result[(i*n+j)]=best; result_i[(i*n+j)]=best_i; } } __syncthreads(); } } } // int chamfer_cuda_forward(int b,int n,const float * xyz,int m,const float * xyz2,float * result,int * result_i,float * result2,int * result2_i, cudaStream_t stream){ int chamfer_cuda_forward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor dist1, at::Tensor dist2, at::Tensor idx1, at::Tensor idx2){ const auto batch_size = xyz1.size(0); const auto n = xyz1.size(1); //num_points point cloud A const auto m = xyz2.size(1); //num_points point cloud B NmDistanceKernel<<>>(batch_size, n, xyz1.data(), m, xyz2.data(), dist1.data(), idx1.data()); NmDistanceKernel<<>>(batch_size, m, xyz2.data(), n, xyz1.data(), dist2.data(), idx2.data()); cudaError_t err = cudaGetLastError(); if (err != cudaSuccess) { printf("error in nnd updateOutput: %s\n", cudaGetErrorString(err)); //THError("aborting"); return 0; } return 1; } __global__ void NmDistanceGradKernel(int b,int n,const float * xyz1,int m,const float * xyz2,const float * grad_dist1,const int * idx1,float * grad_xyz1,float * grad_xyz2){ for (int i=blockIdx.x;i>>(batch_size,n,xyz1.data(),m,xyz2.data(),graddist1.data(),idx1.data(),gradxyz1.data(),gradxyz2.data()); NmDistanceGradKernel<<>>(batch_size,m,xyz2.data(),n,xyz1.data(),graddist2.data(),idx2.data(),gradxyz2.data(),gradxyz1.data()); cudaError_t err = cudaGetLastError(); if (err != cudaSuccess) { printf("error in nnd get grad: %s\n", cudaGetErrorString(err)); //THError("aborting"); return 0; } return 1; } ================================================ FILE: extensions/chamfer_distance/chamfer_3D.egg-info/PKG-INFO ================================================ Metadata-Version: 2.1 Name: chamfer-3D Version: 0.0.0 Summary: UNKNOWN Home-page: UNKNOWN License: UNKNOWN Platform: UNKNOWN UNKNOWN ================================================ FILE: extensions/chamfer_distance/chamfer_3D.egg-info/SOURCES.txt ================================================ chamfer3D.cu chamfer_cuda.cpp setup.py chamfer_3D.egg-info/PKG-INFO chamfer_3D.egg-info/SOURCES.txt chamfer_3D.egg-info/dependency_links.txt chamfer_3D.egg-info/top_level.txt ================================================ FILE: extensions/chamfer_distance/chamfer_3D.egg-info/dependency_links.txt ================================================ ================================================ FILE: extensions/chamfer_distance/chamfer_3D.egg-info/top_level.txt ================================================ chamfer_3D ================================================ FILE: extensions/chamfer_distance/chamfer_cuda.cpp ================================================ #include #include ///TMP //#include "common.h" /// NOT TMP int chamfer_cuda_forward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor dist1, at::Tensor dist2, at::Tensor idx1, at::Tensor idx2); int chamfer_cuda_backward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor gradxyz1, at::Tensor gradxyz2, at::Tensor graddist1, at::Tensor graddist2, at::Tensor idx1, at::Tensor idx2); int chamfer_forward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor dist1, at::Tensor dist2, at::Tensor idx1, at::Tensor idx2) { return chamfer_cuda_forward(xyz1, xyz2, dist1, dist2, idx1, idx2); } int chamfer_backward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor gradxyz1, at::Tensor gradxyz2, at::Tensor graddist1, at::Tensor graddist2, at::Tensor idx1, at::Tensor idx2) { return chamfer_cuda_backward(xyz1, xyz2, gradxyz1, gradxyz2, graddist1, graddist2, idx1, idx2); } PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { m.def("forward", &chamfer_forward, "chamfer forward (CUDA)"); m.def("backward", &chamfer_backward, "chamfer backward (CUDA)"); } ================================================ FILE: extensions/chamfer_distance/chamfer_distance.py ================================================ import importlib import os import torch from torch import nn from torch.autograd import Function chamfer_found = importlib.find_loader("chamfer_3D") is not None if not chamfer_found: ## Cool trick from https://github.com/chrdiller print("Jitting Chamfer 3D") from torch.utils.cpp_extension import load chamfer_3D = load(name="chamfer_3D", sources=[ "/".join(os.path.abspath(__file__).split('/')[:-1] + ["chamfer_cuda.cpp"]), "/".join(os.path.abspath(__file__).split('/')[:-1] + ["chamfer3D.cu"]), ]) # print("Loaded JIT 3D CUDA chamfer distance") else: import chamfer_3D # print("Loaded compiled 3D CUDA chamfer distance") # Chamfer's distance module @thibaultgroueix # GPU tensors only class chamfer_3DFunction(Function): @staticmethod def forward(ctx, xyz1, xyz2): """ xyz1: (B, N, 3) xyz2: (B, M, 3) """ batchsize, n, _ = xyz1.size() _, m, _ = xyz2.size() device = xyz1.device dist1 = torch.zeros(batchsize, n) dist2 = torch.zeros(batchsize, m) idx1 = torch.zeros(batchsize, n).type(torch.IntTensor) idx2 = torch.zeros(batchsize, m).type(torch.IntTensor) dist1 = dist1.to(device) dist2 = dist2.to(device) idx1 = idx1.to(device) idx2 = idx2.to(device) torch.cuda.set_device(device) chamfer_3D.forward(xyz1, xyz2, dist1, dist2, idx1, idx2) ctx.save_for_backward(xyz1, xyz2, idx1, idx2) return dist1, dist2, idx1, idx2 @staticmethod def backward(ctx, graddist1, graddist2, gradidx1, gradidx2): xyz1, xyz2, idx1, idx2 = ctx.saved_tensors graddist1 = graddist1.contiguous() graddist2 = graddist2.contiguous() device = graddist1.device gradxyz1 = torch.zeros(xyz1.size()) gradxyz2 = torch.zeros(xyz2.size()) gradxyz1 = gradxyz1.to(device) gradxyz2 = gradxyz2.to(device) chamfer_3D.backward( xyz1, xyz2, gradxyz1, gradxyz2, graddist1, graddist2, idx1, idx2 ) return gradxyz1, gradxyz2 class ChamferDistance(nn.Module): def __init__(self): super(ChamferDistance, self).__init__() def forward(self, input1, input2): """ input1: (B, N, 3) input2: (B, M, 3) """ dist1, dist2, _, _ = chamfer_3DFunction.apply(input1, input2) return dist1, dist2 ================================================ FILE: extensions/chamfer_distance/setup.py ================================================ from setuptools import setup from torch.utils.cpp_extension import BuildExtension, CUDAExtension setup( name='chamfer_3D', ext_modules=[ CUDAExtension('chamfer_3D', [ "/".join(__file__.split('/')[:-1] + ['chamfer_cuda.cpp']), "/".join(__file__.split('/')[:-1] + ['chamfer3D.cu']), ]), ], cmdclass={ 'build_ext': BuildExtension }) ================================================ FILE: extensions/earth_movers_distance/emd.cpp ================================================ #ifndef _EMD #define _EMD #include #include //CUDA declarations at::Tensor ApproxMatchForward( const at::Tensor xyz1, const at::Tensor xyz2); at::Tensor MatchCostForward( const at::Tensor xyz1, const at::Tensor xyz2, const at::Tensor match); std::vector MatchCostBackward( const at::Tensor grad_cost, const at::Tensor xyz1, const at::Tensor xyz2, const at::Tensor match); PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { m.def("approxmatch_forward", &ApproxMatchForward,"ApproxMatch forward (CUDA)"); m.def("matchcost_forward", &MatchCostForward,"MatchCost forward (CUDA)"); m.def("matchcost_backward", &MatchCostBackward,"MatchCost backward (CUDA)"); } #endif ================================================ FILE: extensions/earth_movers_distance/emd.py ================================================ import torch import torch.nn as nn import emd_cuda class EarthMoverDistanceFunction(torch.autograd.Function): @staticmethod def forward(ctx, xyz1, xyz2): xyz1 = xyz1.contiguous() xyz2 = xyz2.contiguous() assert xyz1.is_cuda and xyz2.is_cuda, "Only support cuda currently." match = emd_cuda.approxmatch_forward(xyz1, xyz2) cost = emd_cuda.matchcost_forward(xyz1, xyz2, match) ctx.save_for_backward(xyz1, xyz2, match) return cost @staticmethod def backward(ctx, grad_cost): xyz1, xyz2, match = ctx.saved_tensors grad_cost = grad_cost.contiguous() grad_xyz1, grad_xyz2 = emd_cuda.matchcost_backward(grad_cost, xyz1, xyz2, match) return grad_xyz1, grad_xyz2 class EarthMoverDistance(nn.Module): def __init__(self): super().__init__() def forward(self, xyz1, xyz2): """ Args: xyz1 (torch.Tensor): (b, N1, 3) xyz2 (torch.Tensor): (b, N2, 3) Returns: cost (torch.Tensor): (b) """ if xyz1.dim() == 2: xyz1 = xyz1.unsqueeze(0) if xyz2.dim() == 2: xyz2 = xyz2.unsqueeze(0) cost = EarthMoverDistanceFunction.apply(xyz1, xyz2) return cost ================================================ FILE: extensions/earth_movers_distance/emd_cuda.egg-info/PKG-INFO ================================================ Metadata-Version: 2.1 Name: emd-cuda Version: 0.0.0 Summary: UNKNOWN Home-page: UNKNOWN License: UNKNOWN Platform: UNKNOWN UNKNOWN ================================================ FILE: extensions/earth_movers_distance/emd_cuda.egg-info/SOURCES.txt ================================================ emd.cpp emd_kernel.cu setup.py emd_cuda.egg-info/PKG-INFO emd_cuda.egg-info/SOURCES.txt emd_cuda.egg-info/dependency_links.txt emd_cuda.egg-info/top_level.txt ================================================ FILE: extensions/earth_movers_distance/emd_cuda.egg-info/dependency_links.txt ================================================ ================================================ FILE: extensions/earth_movers_distance/emd_cuda.egg-info/top_level.txt ================================================ emd_cuda ================================================ FILE: extensions/earth_movers_distance/emd_kernel.cu ================================================ /********************************** * Original Author: Haoqiang Fan * Modified by: Kaichun Mo *********************************/ #ifndef _EMD_KERNEL #define _EMD_KERNEL #include #include #include #include // at::cuda::getApplyGrid #include #define CHECK_CUDA(x) TORCH_CHECK(x.type().is_cuda(), #x " must be a CUDA tensor") #define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") #define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) /******************************** * Forward kernel for approxmatch *********************************/ template __global__ void approxmatch(int b,int n,int m,const scalar_t * __restrict__ xyz1,const scalar_t * __restrict__ xyz2,scalar_t * __restrict__ match,scalar_t * temp){ scalar_t * remainL=temp+blockIdx.x*(n+m)*2, * remainR=temp+blockIdx.x*(n+m)*2+n,*ratioL=temp+blockIdx.x*(n+m)*2+n+m,*ratioR=temp+blockIdx.x*(n+m)*2+n+m+n; scalar_t multiL,multiR; if (n>=m){ multiL=1; multiR=n/m; }else{ multiL=m/n; multiR=1; } const int Block=1024; __shared__ scalar_t buf[Block*4]; for (int i=blockIdx.x;i=-2;j--){ scalar_t level=-powf(4.0f,j); if (j==-2){ level=0; } for (int k0=0;k0>>(b,n,m,xyz1,xyz2,match,temp); //} /* ApproxMatch forward interface Input: xyz1: (B, N1, 3) # dataset_points xyz2: (B, N2, 3) # query_points Output: match: (B, N2, N1) */ at::Tensor ApproxMatchForward( const at::Tensor xyz1, const at::Tensor xyz2){ const auto b = xyz1.size(0); const auto n = xyz1.size(1); const auto m = xyz2.size(1); CHECK_EQ(xyz2.size(0), b); CHECK_EQ(xyz1.size(2), 3); CHECK_EQ(xyz2.size(2), 3); CHECK_INPUT(xyz1); CHECK_INPUT(xyz2); auto match = at::zeros({b, m, n}, xyz1.type()); auto temp = at::zeros({b, (n+m)*2}, xyz1.type()); AT_DISPATCH_FLOATING_TYPES(xyz1.scalar_type(), "ApproxMatchForward", ([&] { approxmatch<<<32,512>>>(b, n, m, xyz1.data(), xyz2.data(), match.data(), temp.data()); })); THCudaCheck(cudaGetLastError()); return match; } /******************************** * Forward kernel for matchcost *********************************/ template __global__ void matchcost(int b,int n,int m,const scalar_t * __restrict__ xyz1,const scalar_t * __restrict__ xyz2,const scalar_t * __restrict__ match,scalar_t * __restrict__ out){ __shared__ scalar_t allsum[512]; const int Block=1024; __shared__ scalar_t buf[Block*3]; for (int i=blockIdx.x;i>>(b,n,m,xyz1,xyz2,match,out); //} /* MatchCost forward interface Input: xyz1: (B, N1, 3) # dataset_points xyz2: (B, N2, 3) # query_points match: (B, N2, N1) Output: cost: (B) */ at::Tensor MatchCostForward( const at::Tensor xyz1, const at::Tensor xyz2, const at::Tensor match){ const auto b = xyz1.size(0); const auto n = xyz1.size(1); const auto m = xyz2.size(1); CHECK_EQ(xyz2.size(0), b); CHECK_EQ(xyz1.size(2), 3); CHECK_EQ(xyz2.size(2), 3); CHECK_INPUT(xyz1); CHECK_INPUT(xyz2); auto cost = at::zeros({b}, xyz1.type()); AT_DISPATCH_FLOATING_TYPES(xyz1.scalar_type(), "MatchCostForward", ([&] { matchcost<<<32,512>>>(b, n, m, xyz1.data(), xyz2.data(), match.data(), cost.data()); })); THCudaCheck(cudaGetLastError()); return cost; } /******************************** * matchcostgrad2 kernel *********************************/ template __global__ void matchcostgrad2(int b,int n,int m,const scalar_t * __restrict__ grad_cost,const scalar_t * __restrict__ xyz1,const scalar_t * __restrict__ xyz2,const scalar_t * __restrict__ match,scalar_t * __restrict__ grad2){ __shared__ scalar_t sum_grad[256*3]; for (int i=blockIdx.x;i __global__ void matchcostgrad1(int b,int n,int m,const scalar_t * __restrict__ grad_cost,const scalar_t * __restrict__ xyz1,const scalar_t * __restrict__ xyz2,const scalar_t * __restrict__ match,scalar_t * __restrict__ grad1){ for (int i=blockIdx.x;i>>(b,n,m,xyz1,xyz2,match,grad1); // matchcostgrad2<<>>(b,n,m,xyz1,xyz2,match,grad2); //} /* MatchCost backward interface Input: grad_cost: (B) # gradients on cost xyz1: (B, N1, 3) # dataset_points xyz2: (B, N2, 3) # query_points match: (B, N2, N1) Output: grad1: (B, N1, 3) grad2: (B, N2, 3) */ std::vector MatchCostBackward( const at::Tensor grad_cost, const at::Tensor xyz1, const at::Tensor xyz2, const at::Tensor match){ const auto b = xyz1.size(0); const auto n = xyz1.size(1); const auto m = xyz2.size(1); CHECK_EQ(xyz2.size(0), b); CHECK_EQ(xyz1.size(2), 3); CHECK_EQ(xyz2.size(2), 3); CHECK_INPUT(xyz1); CHECK_INPUT(xyz2); auto grad1 = at::zeros({b, n, 3}, xyz1.type()); auto grad2 = at::zeros({b, m, 3}, xyz1.type()); AT_DISPATCH_FLOATING_TYPES(xyz1.scalar_type(), "MatchCostBackward", ([&] { matchcostgrad1<<<32,512>>>(b, n, m, grad_cost.data(), xyz1.data(), xyz2.data(), match.data(), grad1.data()); matchcostgrad2<<>>(b, n, m, grad_cost.data(), xyz1.data(), xyz2.data(), match.data(), grad2.data()); })); THCudaCheck(cudaGetLastError()); return std::vector({grad1, grad2}); } #endif ================================================ FILE: extensions/earth_movers_distance/setup.py ================================================ from setuptools import setup from torch.utils.cpp_extension import BuildExtension, CUDAExtension setup( name='emd_cuda', ext_modules=[ CUDAExtension( name='emd_cuda', sources=[ 'emd.cpp', 'emd_kernel.cu', ], # extra_compile_args={'cxx': ['-g'], 'nvcc': ['-O2']} ), ], cmdclass={ 'build_ext': BuildExtension }) ================================================ FILE: metrics/loss.py ================================================ import torch from extensions.chamfer_distance.chamfer_distance import ChamferDistance from extensions.earth_movers_distance.emd import EarthMoverDistance CD = ChamferDistance() EMD = EarthMoverDistance() def cd_loss_L1(pcs1, pcs2): """ L1 Chamfer Distance. Args: pcs1 (torch.tensor): (B, N, 3) pcs2 (torch.tensor): (B, M, 3) """ dist1, dist2 = CD(pcs1, pcs2) dist1 = torch.sqrt(dist1) dist2 = torch.sqrt(dist2) return (torch.mean(dist1) + torch.mean(dist2)) / 2.0 def cd_loss_L2(pcs1, pcs2): """ L2 Chamfer Distance. Args: pcs1 (torch.tensor): (B, N, 3) pcs2 (torch.tensor): (B, M, 3) """ dist1, dist2 = CD(pcs1, pcs2) return torch.mean(dist1) + torch.mean(dist2) def emd_loss(pcs1, pcs2): """ EMD Loss. Args: xyz1 (torch.Tensor): (b, N, 3) xyz2 (torch.Tensor): (b, N, 3) """ dists = EMD(pcs1, pcs2) return torch.mean(dists) ================================================ FILE: metrics/metric.py ================================================ import torch import open3d as o3d from extensions.chamfer_distance.chamfer_distance import ChamferDistance from extensions.earth_movers_distance.emd import EarthMoverDistance CD = ChamferDistance() EMD = EarthMoverDistance() def l2_cd(pcs1, pcs2): dist1, dist2 = CD(pcs1, pcs2) dist1 = torch.mean(dist1, dim=1) dist2 = torch.mean(dist2, dim=1) return torch.sum(dist1 + dist2) def l1_cd(pcs1, pcs2): dist1, dist2 = CD(pcs1, pcs2) dist1 = torch.mean(torch.sqrt(dist1), 1) dist2 = torch.mean(torch.sqrt(dist2), 1) return torch.sum(dist1 + dist2) / 2 def emd(pcs1, pcs2): dists = EMD(pcs1, pcs2) return torch.sum(dists) def f_score(pred, gt, th=0.01): """ References: https://github.com/lmb-freiburg/what3d/blob/master/util.py Args: pred (np.ndarray): (N1, 3) gt (np.ndarray): (N2, 3) th (float): a distance threshhold """ pred = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(pred)) gt = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(gt)) dist1 = pred.compute_point_cloud_distance(gt) dist2 = gt.compute_point_cloud_distance(pred) recall = float(sum(d < th for d in dist2)) / float(len(dist2)) precision = float(sum(d < th for d in dist1)) / float(len(dist1)) return 2 * recall * precision / (recall + precision) if recall + precision else 0 ================================================ FILE: models/__init__.py ================================================ from models.pcn import PCN ================================================ FILE: models/pcn.py ================================================ import torch import torch.nn as nn class PCN(nn.Module): """ "PCN: Point Cloud Completion Network" (https://arxiv.org/pdf/1808.00671.pdf) Attributes: num_dense: 16384 latent_dim: 1024 grid_size: 4 num_coarse: 1024 """ def __init__(self, num_dense=16384, latent_dim=1024, grid_size=4): super().__init__() self.num_dense = num_dense self.latent_dim = latent_dim self.grid_size = grid_size assert self.num_dense % self.grid_size ** 2 == 0 self.num_coarse = self.num_dense // (self.grid_size ** 2) self.first_conv = nn.Sequential( nn.Conv1d(3, 128, 1), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.Conv1d(128, 256, 1) ) self.second_conv = nn.Sequential( nn.Conv1d(512, 512, 1), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Conv1d(512, self.latent_dim, 1) ) self.mlp = nn.Sequential( nn.Linear(self.latent_dim, 1024), nn.ReLU(inplace=True), nn.Linear(1024, 1024), nn.ReLU(inplace=True), nn.Linear(1024, 3 * self.num_coarse) ) self.final_conv = nn.Sequential( nn.Conv1d(1024 + 3 + 2, 512, 1), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Conv1d(512, 512, 1), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Conv1d(512, 3, 1) ) a = torch.linspace(-0.05, 0.05, steps=self.grid_size, dtype=torch.float).view(1, self.grid_size).expand(self.grid_size, self.grid_size).reshape(1, -1) b = torch.linspace(-0.05, 0.05, steps=self.grid_size, dtype=torch.float).view(self.grid_size, 1).expand(self.grid_size, self.grid_size).reshape(1, -1) self.folding_seed = torch.cat([a, b], dim=0).view(1, 2, self.grid_size ** 2).cuda() # (1, 2, S) def forward(self, xyz): B, N, _ = xyz.shape # encoder feature = self.first_conv(xyz.transpose(2, 1)) # (B, 256, N) feature_global = torch.max(feature, dim=2, keepdim=True)[0] # (B, 256, 1) feature = torch.cat([feature_global.expand(-1, -1, N), feature], dim=1) # (B, 512, N) feature = self.second_conv(feature) # (B, 1024, N) feature_global = torch.max(feature,dim=2,keepdim=False)[0] # (B, 1024) # decoder coarse = self.mlp(feature_global).reshape(-1, self.num_coarse, 3) # (B, num_coarse, 3), coarse point cloud point_feat = coarse.unsqueeze(2).expand(-1, -1, self.grid_size ** 2, -1) # (B, num_coarse, S, 3) point_feat = point_feat.reshape(-1, self.num_dense, 3).transpose(2, 1) # (B, 3, num_fine) seed = self.folding_seed.unsqueeze(2).expand(B, -1, self.num_coarse, -1) # (B, 2, num_coarse, S) seed = seed.reshape(B, -1, self.num_dense) # (B, 2, num_fine) feature_global = feature_global.unsqueeze(2).expand(-1, -1, self.num_dense) # (B, 1024, num_fine) feat = torch.cat([feature_global, seed, point_feat], dim=1) # (B, 1024+2+3, num_fine) fine = self.final_conv(feat) + point_feat # (B, 3, num_fine), fine point cloud return coarse.contiguous(), fine.transpose(1, 2).contiguous() ================================================ FILE: render/README.md ================================================ # render ## Description `process_exr.py` and `render_depth.py` are used for generating the partial point cloud from CAD model. In order to run the `render_depth.py`, you need to install [Blender](https://www.blender.org/) firstly. After complete installing, you can use this command to render the depth images: ```bash blender -b -P render_depth.py [ShapeNet directory] [model list] [output directory] [num scans per model] ``` The images will be stored in OpenEXR format. The version of blender I used is `2.9.1`. In order to run the `process_exr.py`, you need to install `imath`、`OpenEXR` and `open3d-python`. These are third python modules, you can install with `pip`. The command to generate partial point clouds from `.exr` is: ```bash python3 process_exr.py [model list] [intrinsics file] [output directory] [num scans per model] ``` The version of Python should not be too high. I use the version of `3.7.9`. ## Example Complete point cloud: Partial point clouds: ================================================ FILE: render/blender.log ================================================ Progress: 0.00% ( 0.0000 sec | 0.0000 sec) Importing OBJ '/media/rico/BACKUP/Dataset/ShapeNetForPCN/02958343/167ec61fc29df46460593c98e3e63028/model.obj'... Progress: 0.00% ( 0.0008 sec | 0.0008 sec) Parsing OBJ file... Progress: 0.00% ( 0.8401 sec | 0.8393 sec) Done, loading materials and images... Progress: 33.33% ( 1.0985 sec | 1.0977 sec) Done, building geometries (verts:49443 faces:89741 materials: 79 smoothgroups:0) ... Progress: 66.67% ( 3.4071 sec | 3.4063 sec) Done. Progress: 66.67% Progress: 100.00% ( 3.4072 sec | 3.4072 sec) Finished importing: '/media/rico/BACKUP/Dataset/ShapeNetForPCN/02958343/167ec61fc29df46460593c98e3e63028/model.obj' Progress: 100.00% Progress: 100.00% Fra:0 Mem:179.97M (0.00M, Peak 179.98M) | Time:00:00.00 | Preparing Scene data Fra:0 Mem:329.52M (0.00M, Peak 329.89M) | Time:00:00.09 | Preparing Scene data Fra:0 Mem:329.52M (0.00M, Peak 329.89M) | Time:00:00.09 | Creating Shadowbuffers Fra:0 Mem:329.52M (0.00M, Peak 329.89M) | Time:00:00.09 | Raytree.. preparing Fra:0 Mem:341.84M (0.00M, Peak 341.84M) | Time:00:00.09 | Raytree.. building Fra:0 Mem:341.14M (0.00M, Peak 360.38M) | Time:00:00.22 | Raytree finished Fra:0 Mem:341.14M (0.00M, Peak 360.38M) | Time:00:00.22 | Creating Environment maps Fra:0 Mem:341.14M (0.00M, Peak 360.38M) | Time:00:00.22 | Caching Point Densities Fra:0 Mem:341.14M (0.00M, Peak 360.38M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:0 Mem:341.14M (0.00M, Peak 360.38M) | Time:00:00.22 | Loading voxel datasets Fra:0 Mem:341.14M (0.00M, Peak 360.38M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:0 Mem:341.15M (0.00M, Peak 360.38M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:0 Mem:341.15M (0.00M, Peak 360.38M) | Time:00:00.22 | Volume preprocessing Fra:0 Mem:341.15M (0.00M, Peak 360.38M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:0 Mem:341.15M (0.00M, Peak 360.38M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:0 Mem:343.08M (0.00M, Peak 360.38M) | Time:00:00.22 | Scene, Part 6-6 Fra:0 Mem:343.01M (0.00M, Peak 360.38M) | Time:00:00.22 | Scene, Part 5-6 Fra:0 Mem:342.82M (0.00M, Peak 360.38M) | Time:00:00.22 | Scene, Part 3-6 Fra:0 Mem:349.62M (0.00M, Peak 360.38M) | Time:00:00.24 | Scene, Part 4-6 Fra:0 Mem:348.73M (0.00M, Peak 360.38M) | Time:00:00.24 | Scene, Part 2-6 Fra:0 Mem:348.44M (0.00M, Peak 360.38M) | Time:00:00.25 | Scene, Part 1-6 Fra:0 Mem:186.83M (0.00M, Peak 360.38M) | Time:00:00.26 | Compositing Fra:0 Mem:186.83M (0.00M, Peak 360.38M) | Time:00:00.26 | Compositing | Determining resolution Fra:0 Mem:186.83M (0.00M, Peak 360.38M) | Time:00:00.26 | Compositing | Initializing execution Fra:0 Mem:187.27M (0.00M, Peak 360.38M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:0 Mem:187.27M (0.00M, Peak 360.38M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:0 Mem:187.27M (0.00M, Peak 360.38M) | Time:00:00.26 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/0.exr Fra:0 Mem:187.19M (0.00M, Peak 360.38M) | Time:00:00.26 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.27 (Saving: 00:00.00) Fra:1 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:1 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:1 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:1 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.10 | Raytree.. building Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Raytree finished Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Creating Environment maps Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Caching Point Densities Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Loading voxel datasets Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Volume preprocessing Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:1 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:1 Mem:350.16M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 6-6 Fra:1 Mem:350.08M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 5-6 Fra:1 Mem:349.59M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 3-6 Fra:1 Mem:349.29M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 4-6 Fra:1 Mem:348.74M (0.00M, Peak 367.46M) | Time:00:00.24 | Scene, Part 2-6 Fra:1 Mem:348.46M (0.00M, Peak 367.46M) | Time:00:00.25 | Scene, Part 1-6 Fra:1 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing Fra:1 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Determining resolution Fra:1 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Initializing execution Fra:1 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:1 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:1 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/1.exr Fra:1 Mem:187.19M (0.00M, Peak 367.46M) | Time:00:00.26 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.26 (Saving: 00:00.00) Fra:2 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:2 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:2 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:2 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.10 | Raytree.. building Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Raytree finished Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Creating Environment maps Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Caching Point Densities Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Loading voxel datasets Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Volume preprocessing Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:2 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:2 Mem:350.16M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 6-6 Fra:2 Mem:350.08M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 5-6 Fra:2 Mem:349.59M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 3-6 Fra:2 Mem:349.29M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 4-6 Fra:2 Mem:348.86M (0.00M, Peak 367.46M) | Time:00:00.24 | Scene, Part 1-6 Fra:2 Mem:348.44M (0.00M, Peak 367.46M) | Time:00:00.25 | Scene, Part 2-6 Fra:2 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing Fra:2 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Determining resolution Fra:2 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Initializing execution Fra:2 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:2 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:2 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/2.exr Fra:2 Mem:187.19M (0.00M, Peak 367.46M) | Time:00:00.26 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.26 (Saving: 00:00.00) Fra:3 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:3 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:3 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:3 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.10 | Raytree.. building Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Raytree finished Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Creating Environment maps Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Caching Point Densities Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Loading voxel datasets Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Volume preprocessing Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:3 Mem:348.24M (0.00M, Peak 367.47M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:3 Mem:350.79M (0.00M, Peak 367.47M) | Time:00:00.22 | Scene, Part 6-6 Fra:3 Mem:350.77M (0.00M, Peak 367.47M) | Time:00:00.22 | Scene, Part 5-6 Fra:3 Mem:349.43M (0.00M, Peak 367.47M) | Time:00:00.22 | Scene, Part 3-6 Fra:3 Mem:349.27M (0.00M, Peak 367.47M) | Time:00:00.22 | Scene, Part 4-6 Fra:3 Mem:348.87M (0.00M, Peak 367.47M) | Time:00:00.24 | Scene, Part 2-6 Fra:3 Mem:348.44M (0.00M, Peak 367.47M) | Time:00:00.25 | Scene, Part 1-6 Fra:3 Mem:186.83M (0.00M, Peak 367.47M) | Time:00:00.27 | Compositing Fra:3 Mem:186.83M (0.00M, Peak 367.47M) | Time:00:00.27 | Compositing | Determining resolution Fra:3 Mem:186.83M (0.00M, Peak 367.47M) | Time:00:00.27 | Compositing | Initializing execution Fra:3 Mem:187.27M (0.00M, Peak 367.47M) | Time:00:00.27 | Compositing | Tile 1-1 Fra:3 Mem:187.27M (0.00M, Peak 367.47M) | Time:00:00.27 | Compositing | Tile 1-1 Fra:3 Mem:187.27M (0.00M, Peak 367.47M) | Time:00:00.27 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/3.exr Fra:3 Mem:187.19M (0.00M, Peak 367.47M) | Time:00:00.27 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.27 (Saving: 00:00.00) Fra:4 Mem:187.03M (0.00M, Peak 187.04M) | Time:00:00.00 | Preparing Scene data Fra:4 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:4 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:4 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:4 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.10 | Raytree.. building Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Raytree finished Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Creating Environment maps Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Caching Point Densities Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Loading voxel datasets Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Volume preprocessing Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:4 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:4 Mem:350.16M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 6-6 Fra:4 Mem:350.08M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 5-6 Fra:4 Mem:349.71M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 3-6 Fra:4 Mem:349.17M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 4-6 Fra:4 Mem:348.89M (0.00M, Peak 367.46M) | Time:00:00.24 | Scene, Part 2-6 Fra:4 Mem:348.46M (0.00M, Peak 367.46M) | Time:00:00.25 | Scene, Part 1-6 Fra:4 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing Fra:4 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Determining resolution Fra:4 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Initializing execution Fra:4 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Tile 1-1 Fra:4 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Tile 1-1 Fra:4 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/4.exr Fra:4 Mem:187.19M (0.00M, Peak 367.46M) | Time:00:00.27 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.27 (Saving: 00:00.00) Fra:5 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:5 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:5 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:5 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.09 | Raytree.. building Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Raytree finished Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Creating Environment maps Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Caching Point Densities Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Loading voxel datasets Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Volume preprocessing Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:5 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:5 Mem:350.16M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 6-6 Fra:5 Mem:350.08M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 5-6 Fra:5 Mem:349.71M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 3-6 Fra:5 Mem:349.17M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 4-6 Fra:5 Mem:348.89M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 2-6 Fra:5 Mem:348.46M (0.00M, Peak 367.46M) | Time:00:00.25 | Scene, Part 1-6 Fra:5 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing Fra:5 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Determining resolution Fra:5 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Initializing execution Fra:5 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:5 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:5 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/5.exr Fra:5 Mem:187.19M (0.00M, Peak 367.46M) | Time:00:00.26 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.27 (Saving: 00:00.00) Fra:6 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:6 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:6 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:6 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.10 | Raytree.. building Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Raytree finished Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Creating Environment maps Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Caching Point Densities Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Loading voxel datasets Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Volume preprocessing Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:6 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:6 Mem:350.16M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 6-6 Fra:6 Mem:350.08M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 5-6 Fra:6 Mem:349.71M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 3-6 Fra:6 Mem:349.29M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 4-6 Fra:6 Mem:348.89M (0.00M, Peak 367.46M) | Time:00:00.24 | Scene, Part 2-6 Fra:6 Mem:348.46M (0.00M, Peak 367.46M) | Time:00:00.25 | Scene, Part 1-6 Fra:6 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing Fra:6 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Determining resolution Fra:6 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Initializing execution Fra:6 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Tile 1-1 Fra:6 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | Tile 1-1 Fra:6 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.27 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/6.exr Fra:6 Mem:187.19M (0.00M, Peak 367.46M) | Time:00:00.27 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.27 (Saving: 00:00.00) Fra:7 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Preparing Scene data Fra:7 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Creating Shadowbuffers Fra:7 Mem:336.58M (0.00M, Peak 336.95M) | Time:00:00.09 | Raytree.. preparing Fra:7 Mem:348.90M (0.00M, Peak 348.90M) | Time:00:00.10 | Raytree.. building Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Raytree finished Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Creating Environment maps Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Caching Point Densities Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Loading voxel datasets Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Volume preprocessing Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:7 Mem:348.22M (0.00M, Peak 367.46M) | Time:00:00.22 | Sce: Scene Ve:93605 Fa:89708 La:1 Fra:7 Mem:350.16M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 6-6 Fra:7 Mem:350.08M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 5-6 Fra:7 Mem:349.71M (0.00M, Peak 367.46M) | Time:00:00.22 | Scene, Part 3-6 Fra:7 Mem:349.29M (0.00M, Peak 367.46M) | Time:00:00.23 | Scene, Part 4-6 Fra:7 Mem:348.87M (0.00M, Peak 367.46M) | Time:00:00.24 | Scene, Part 2-6 Fra:7 Mem:348.46M (0.00M, Peak 367.46M) | Time:00:00.25 | Scene, Part 1-6 Fra:7 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing Fra:7 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Determining resolution Fra:7 Mem:186.83M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Initializing execution Fra:7 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:7 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | Tile 1-1 Fra:7 Mem:187.27M (0.00M, Peak 367.46M) | Time:00:00.26 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/167ec61fc29df46460593c98e3e63028/7.exr Fra:7 Mem:187.19M (0.00M, Peak 367.46M) | Time:00:00.26 | Sce: Scene Ve:93605 Fa:89708 La:1 Saved: 'buffer.png' Time: 00:00.26 (Saving: 00:00.00) ved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/6d7e8fa77d384c07e4d0922154a19a3f/7.exr Fra:7 Mem:203.64M (0.00M, Peak 569.00M) | Time:00:00.42 | Sce: Scene Ve:150090 Fa:132950 La:1 Saved: 'buffer.png' Time: 00:00.42 (Saving: 00:00.00) k 1088.52M) | Time:00:02.09 | Compositing | Tile 1-1 Fra:7 Mem:365.11M (0.00M, Peak 1088.52M) | Time:00:02.09 | Compositing | Tile 1-1 Fra:7 Mem:365.11M (0.00M, Peak 1088.52M) | Time:00:02.09 | Compositing | De-initializing execution Saved: /home/rico/Workspace/Dataset/partials/partial21/exr/02958343/b8dd449dd857e7f19b58a6529594c9d/7.exr Fra:7 Mem:365.03M (0.00M, Peak 1088.52M) | Time:00:02.09 | Sce: Scene Ve:621636 Fa:702574 La:1 Saved: 'buffer.png' Time: 00:02.09 (Saving: 00:00.00) ================================================ FILE: render/partial.sh ================================================ #!/bin/bash echo "Begin to generate exr files" for ((i=1; i<=21; i++)); do blender -b -P render_depth.py "/media/rico/BACKUP/Dataset/ShapeNetForPCN" "../dataset/car_split/split${i}.list" "/home/rico/Workspace/Dataset/partials/partial${i}" 8 done echo "Done" ================================================ FILE: render/process_exr.py ================================================ ''' MIT License Copyright (c) 2018 Wentao Yuan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import Imath import OpenEXR import argparse import array import numpy as np import os from open3d import * def read_exr(exr_path, height, width): file = OpenEXR.InputFile(exr_path) depth_arr = array.array('f', file.channel('R', Imath.PixelType(Imath.PixelType.FLOAT))) depth = np.array(depth_arr).reshape((height, width)) depth[depth < 0] = 0 depth[np.isinf(depth)] = 0 return depth def depth2pcd(depth, intrinsics, pose): inv_K = np.linalg.inv(intrinsics) inv_K[2, 2] = -1 depth = np.flipud(depth) y, x = np.where(depth > 0) # image coordinates -> camera coordinates points = np.dot(inv_K, np.stack([x, y, np.ones_like(x)] * depth[y, x], 0)) # camera coordinates -> world coordinates points = np.dot(pose, np.concatenate([points, np.ones((1, points.shape[1]))], 0)).T[:, :3] return points if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('list_file') parser.add_argument('intrinsics_file') parser.add_argument('output_dir') parser.add_argument('num_scans', type=int) args = parser.parse_args() with open(args.list_file) as file: model_list = file.read().splitlines() intrinsics = np.loadtxt(args.intrinsics_file) width = int(intrinsics[0, 2] * 2) height = int(intrinsics[1, 2] * 2) for model_id in model_list: depth_dir = os.path.join(args.output_dir, 'depth', model_id) pcd_dir = os.path.join(args.output_dir, 'pcd', model_id) os.makedirs(depth_dir, exist_ok=True) os.makedirs(pcd_dir, exist_ok=True) for i in range(args.num_scans): exr_path = os.path.join(args.output_dir, 'exr', model_id, '%d.exr' % i) pose_path = os.path.join(args.output_dir, 'pose', model_id, '%d.txt' % i) depth = read_exr(exr_path, height, width) depth_img = Image(np.uint16(depth * 1000)) write_image(os.path.join(depth_dir, '%d.png' % i), depth_img) pose = np.loadtxt(pose_path) points = depth2pcd(depth, intrinsics, pose) pcd = PointCloud() pcd.points = Vector3dVector(points) write_point_cloud(os.path.join(pcd_dir, '%d.pcd' % i), pcd) ================================================ FILE: render/render_depth.py ================================================ ''' MIT License Copyright (c) 2018 Wentao Yuan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import bpy import mathutils import numpy as np import os import sys import time def random_pose(): angle_x = np.random.uniform() * 2 * np.pi angle_y = np.random.uniform() * 2 * np.pi angle_z = np.random.uniform() * 2 * np.pi Rx = np.array([[1, 0, 0], [0, np.cos(angle_x), -np.sin(angle_x)], [0, np.sin(angle_x), np.cos(angle_x)]]) Ry = np.array([[np.cos(angle_y), 0, np.sin(angle_y)], [0, 1, 0], [-np.sin(angle_y), 0, np.cos(angle_y)]]) Rz = np.array([[np.cos(angle_z), -np.sin(angle_z), 0], [np.sin(angle_z), np.cos(angle_z), 0], [0, 0, 1]]) R = np.dot(Rz, np.dot(Ry, Rx)) # Set camera pointing to the origin and 1 unit away from the origin t = np.expand_dims(R[:, 2], 1) pose = np.concatenate([np.concatenate([R, t], 1), [[0, 0, 0, 1]]], 0) return pose def setup_blender(width, height, focal_length): # camera camera = bpy.data.objects['Camera'] camera.data.angle = np.arctan(width / 2 / focal_length) * 2 # render layer scene = bpy.context.scene scene.render.filepath = 'buffer' scene.render.image_settings.color_depth = '16' scene.render.resolution_percentage = 100 scene.render.resolution_x = width scene.render.resolution_y = height # compositor nodes scene.use_nodes = True tree = scene.node_tree rl = tree.nodes.new('CompositorNodeRLayers') output = tree.nodes.new('CompositorNodeOutputFile') output.base_path = '' output.format.file_format = 'OPEN_EXR' tree.links.new(rl.outputs['Depth'], output.inputs[0]) # remove default cube bpy.data.objects['Cube'].select = True bpy.ops.object.delete() return scene, camera, output if __name__ == '__main__': model_dir = sys.argv[-4] list_path = sys.argv[-3] output_dir = sys.argv[-2] num_scans = int(sys.argv[-1]) width = 160 height = 120 focal = 100 scene, camera, output = setup_blender(width, height, focal) intrinsics = np.array([[focal, 0, width / 2], [0, focal, height / 2], [0, 0, 1]]) with open(os.path.join(list_path)) as file: model_list = [line.strip() for line in file] open('blender.log', 'w+').close() os.system('rm -rf %s' % output_dir) os.makedirs(output_dir) np.savetxt(os.path.join(output_dir, 'intrinsics.txt'), intrinsics, '%f') for model_id in model_list: start = time.time() exr_dir = os.path.join(output_dir, 'exr', model_id) pose_dir = os.path.join(output_dir, 'pose', model_id) os.makedirs(exr_dir) os.makedirs(pose_dir) # Redirect output to log file old_os_out = os.dup(1) os.close(1) os.open('blender.log', os.O_WRONLY) # Import mesh model model_path = os.path.join(model_dir, model_id, 'model.obj') bpy.ops.import_scene.obj(filepath=model_path) # Rotate model by 90 degrees around x-axis (z-up => y-up) to match ShapeNet's coordinates bpy.ops.transform.rotate(value=-np.pi / 2, axis=(1, 0, 0)) # Render for i in range(num_scans): scene.frame_set(i) pose = random_pose() camera.matrix_world = mathutils.Matrix(pose) output.file_slots[0].path = os.path.join(exr_dir, '#.exr') bpy.ops.render.render(write_still=True) np.savetxt(os.path.join(pose_dir, '%d.txt' % i), pose, '%f') # Clean up bpy.ops.object.delete() for m in bpy.data.meshes: bpy.data.meshes.remove(m) for m in bpy.data.materials: m.user_clear() bpy.data.materials.remove(m) # Show time os.close(1) os.dup(old_os_out) os.close(old_os_out) print('%s done, time=%.4f sec' % (model_id, time.time() - start)) ================================================ FILE: requirements.txt ================================================ open3d matplotlib tensorboardX ================================================ FILE: sample/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8 FATAL_ERROR) project(sample) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable (mesh_sampling mesh_sampling.cpp) target_link_libraries (mesh_sampling ${PCL_LIBRARIES}) ================================================ FILE: sample/README.md ================================================ # Sample `mesh_sampling.cpp` is used to sample point clouds uniformly from CAD model. In order to compile it, you have to install: * CMake * PCL * VTK ## CMake Use this command to install CMake: ```bash sudo apt-get udpate sudo apt-get install cmake ``` ## PCL The version I used is the latest version, you can use these commands to install: ```bash sudo apt-get update sudo apt-get install git build-essential linux-libc-dev sudo apt-get install cmake cmake-gui sudo apt-get install libusb-1.0-0-dev libusb-dev libudev-dev sudo apt-get install mpi-default-dev openmpi-bin openmpi-common sudo apt-get install libflann1.9 libflann-dev sudo apt-get install libeigen3-dev sudo apt-get install libboost-all-dev sudo apt-get install libqhull* libgtest-dev sudo apt-get install freeglut3-dev pkg-config sudo apt-get install libxmu-dev libxi-dev sudo apt-get install mono-complete sudo apt-get install openjdk-8-jdk openjdk-8-jre git clone https://github.com/PointCloudLibrary/pcl.git cd pcl mkdir build && cd build cmake .. make -j4 sudo make install ``` ## VTK The version of the VTK is `8.2.0`. You can download it from the [website](https://vtk.org/download/) and use the commands blew to install: ```bash tar -xzvf VTK-8.2.0.zip cd VTK-8.2.0/ ``` Before compiling, you need to edit the file `IO/Geometry/vtkOBJReader.cxx`. In line 859, add the following code: ```C++ // Here we turn off texturing and/or normals if (n_tcoord_pts == 0) { hasTCoords = false; } if (n_normal_pts == 0) { hasNormals = false; } ``` Continue to build: ```bash mkdir build && cd build cmake .. make -j4 sudo make install ``` ## Compile In order to use the script, you need to compile it: ```bash cd sample mkdir build && cd build cmake .. make ``` And you can get a exectuable file `mesh_sampling` in the `build` directory. You can use `mesh_sampling -h` for help. I've provided the `mesh_sampling`. But there are some problems with the options of the command. The option `-n_samples` seems cannot work. ## Example CAD model and sampled point cloud : ================================================ FILE: sample/mesh_sampling.cpp ================================================ /* * Software License Agreement (BSD License) * * Point Cloud Library (PCL) - www.pointclouds.org * Copyright (c) 2010-2011, Willow Garage, Inc. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the copyright holder(s) nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Modified by Wentao Yuan (wyuan1@cs.cmu.edu) 05/31/2018 */ #include #include #include #include #include #include #include #include #include #include #include #include #include inline double uniform_deviate(int seed) { double ran = seed * (1.0 / (RAND_MAX + 1.0)); return ran; } inline void randomPointTriangle(float a1, float a2, float a3, float b1, float b2, float b3, float c1, float c2, float c3, Eigen::Vector4f &p) { float r1 = static_cast(uniform_deviate(rand())); float r2 = static_cast(uniform_deviate(rand())); float r1sqr = std::sqrt(r1); float OneMinR1Sqr = (1 - r1sqr); float OneMinR2 = (1 - r2); a1 *= OneMinR1Sqr; a2 *= OneMinR1Sqr; a3 *= OneMinR1Sqr; b1 *= OneMinR2; b2 *= OneMinR2; b3 *= OneMinR2; c1 = r1sqr * (r2 * c1 + b1) + a1; c2 = r1sqr * (r2 * c2 + b2) + a2; c3 = r1sqr * (r2 * c3 + b3) + a3; p[0] = c1; p[1] = c2; p[2] = c3; p[3] = 0; } inline void randPSurface(vtkPolyData *polydata, std::vector *cumulativeAreas, double totalArea, Eigen::Vector4f &p, bool calcNormal, Eigen::Vector3f &n) { float r = static_cast(uniform_deviate(rand()) * totalArea); std::vector::iterator low = std::lower_bound(cumulativeAreas->begin(), cumulativeAreas->end(), r); vtkIdType el = vtkIdType(low - cumulativeAreas->begin()); double A[3], B[3], C[3]; vtkIdType npts = 0; vtkIdType *ptIds = NULL; polydata->GetCellPoints(el, npts, ptIds); polydata->GetPoint(ptIds[0], A); polydata->GetPoint(ptIds[1], B); polydata->GetPoint(ptIds[2], C); if (calcNormal) { // OBJ: Vertices are stored in a counter-clockwise order by default Eigen::Vector3f v1 = Eigen::Vector3f(A[0], A[1], A[2]) - Eigen::Vector3f(C[0], C[1], C[2]); Eigen::Vector3f v2 = Eigen::Vector3f(B[0], B[1], B[2]) - Eigen::Vector3f(C[0], C[1], C[2]); n = v1.cross(v2); n.normalize(); } randomPointTriangle(float(A[0]), float(A[1]), float(A[2]), float(B[0]), float(B[1]), float(B[2]), float(C[0]), float(C[1]), float(C[2]), p); } void uniform_sampling(vtkSmartPointer polydata, size_t n_samples, bool calc_normal, pcl::PointCloud &cloud_out) { polydata->BuildCells(); vtkSmartPointer cells = polydata->GetPolys(); double p1[3], p2[3], p3[3], totalArea = 0; std::vector cumulativeAreas(cells->GetNumberOfCells(), 0); size_t i = 0; vtkIdType npts = 0, *ptIds = NULL; for (cells->InitTraversal(); cells->GetNextCell(npts, ptIds); i++) { polydata->GetPoint(ptIds[0], p1); polydata->GetPoint(ptIds[1], p2); polydata->GetPoint(ptIds[2], p3); totalArea += vtkTriangle::TriangleArea(p1, p2, p3); cumulativeAreas[i] = totalArea; } cloud_out.points.resize(n_samples); cloud_out.width = static_cast(n_samples); cloud_out.height = 1; for (i = 0; i < n_samples; i++) { Eigen::Vector4f p; Eigen::Vector3f n; randPSurface(polydata, &cumulativeAreas, totalArea, p, calc_normal, n); cloud_out.points[i].x = p[0]; cloud_out.points[i].y = p[1]; cloud_out.points[i].z = p[2]; if (calc_normal) { cloud_out.points[i].normal_x = n[0]; cloud_out.points[i].normal_y = n[1]; cloud_out.points[i].normal_z = n[2]; } } } using namespace pcl; using namespace pcl::io; using namespace pcl::console; const int default_number_samples = 100000; const float default_leaf_size = 0.01f; void printHelp(int, char **argv) { print_error("Syntax is: %s input.{ply,obj} output.pcd \n", argv[0]); print_info(" where options are:\n"); print_info(" -n_samples X = number of samples (default: "); print_value("%d", default_number_samples); print_info(")\n"); print_info( " -leaf_size X = the XYZ leaf size for the VoxelGrid -- for data reduction (default: "); print_value("%f", default_leaf_size); print_info(" m)\n"); print_info(" -write_normals = flag to write normals to the output pcd\n"); print_info( " -no_vis_result = flag to stop visualizing the generated pcd\n"); print_info( " -no_vox_filter = flag to stop downsampling the generated pcd\n"); } /* ---[ */ int main(int argc, char **argv) { if (argc < 3) { printHelp(argc, argv); return (-1); } // Parse command line arguments int SAMPLE_POINTS_ = default_number_samples; parse_argument(argc, argv, "-n_samples", SAMPLE_POINTS_); float leaf_size = default_leaf_size; parse_argument(argc, argv, "-leaf_size", leaf_size); bool vis_result = !find_switch(argc, argv, "-no_vis_result"); bool vox_filter = !find_switch(argc, argv, "-no_vox_filter"); const bool write_normals = find_switch(argc, argv, "-write_normals"); std::vector pcd_file_indices = parse_file_extension_argument(argc, argv, ".pcd"); std::vector ply_file_indices = parse_file_extension_argument(argc, argv, ".ply"); std::vector obj_file_indices = parse_file_extension_argument(argc, argv, ".obj"); if (pcd_file_indices.size() != 1) { print_error("Need a single output PCD file to continue.\n"); return (-1); } if (ply_file_indices.size() != 1 && obj_file_indices.size() != 1) { print_error("Need a single input PLY/OBJ file to continue.\n"); return (-1); } vtkSmartPointer polydata1 = vtkSmartPointer::New(); if (ply_file_indices.size() == 1) { pcl::PolygonMesh mesh; pcl::io::loadPolygonFilePLY(argv[ply_file_indices[0]], mesh); pcl::io::mesh2vtk(mesh, polydata1); } else if (obj_file_indices.size() == 1) { print_info("Convert %s to a point cloud using uniform sampling.\n", argv[obj_file_indices[0]]); vtkSmartPointer readerQuery = vtkSmartPointer::New(); readerQuery->SetFileName(argv[obj_file_indices[0]]); readerQuery->Update(); polydata1 = readerQuery->GetOutput(); } //make sure that the polygons are triangles! vtkSmartPointer triangleFilter = vtkSmartPointer::New(); #if VTK_MAJOR_VERSION < 6 triangleFilter->SetInput(polydata1); #else triangleFilter->SetInputData(polydata1); #endif triangleFilter->Update(); vtkSmartPointer triangleMapper = vtkSmartPointer::New(); triangleMapper->SetInputConnection(triangleFilter->GetOutputPort()); triangleMapper->Update(); polydata1 = triangleMapper->GetInput(); bool INTER_VIS = false; if (INTER_VIS) { visualization::PCLVisualizer vis; vis.addModelFromPolyData(polydata1, "mesh1", 0); vis.setRepresentationToSurfaceForAllActors(); vis.spin(); } pcl::PointCloud::Ptr cloud_1(new pcl::PointCloud); uniform_sampling(polydata1, SAMPLE_POINTS_, write_normals, *cloud_1); if (INTER_VIS) { visualization::PCLVisualizer vis_sampled; vis_sampled.addPointCloud(cloud_1); if (write_normals) vis_sampled.addPointCloudNormals(cloud_1, 1, 0.02f, "cloud_normals"); vis_sampled.spin(); } pcl::PointCloud::Ptr cloud(new pcl::PointCloud); // Voxelgrid if (vox_filter) { VoxelGrid grid_; grid_.setInputCloud(cloud_1); grid_.setLeafSize(leaf_size, leaf_size, leaf_size); grid_.filter(*cloud); } else { *cloud = *cloud_1; } if (vis_result) { visualization::PCLVisualizer vis3("VOXELIZED SAMPLES CLOUD"); vis3.addPointCloud(cloud); if (write_normals) vis3.addPointCloudNormals(cloud, 1, 0.02f, "cloud_normals"); vis3.spin(); } if (!write_normals) { pcl::PointCloud::Ptr cloud_xyz(new pcl::PointCloud); // Strip uninitialized normals from cloud: pcl::copyPointCloud(*cloud, *cloud_xyz); savePCDFileASCII(argv[pcd_file_indices[0]], *cloud_xyz); } else { savePCDFileASCII(argv[pcd_file_indices[0]], *cloud); } } ================================================ FILE: test.py ================================================ import os import argparse import numpy as np import open3d as o3d import torch import torch.utils.data as Data from models import PCN from dataset import ShapeNet from visualization import plot_pcd_one_view from metrics.metric import l1_cd, l2_cd, emd, f_score CATEGORIES_PCN = ['airplane', 'cabinet', 'car', 'chair', 'lamp', 'sofa', 'table', 'vessel'] CATEGORIES_PCN_NOVEL = ['bus', 'bed', 'bookshelf', 'bench', 'guitar', 'motorbike', 'skateboard', 'pistol'] def make_dir(dir_path): if not os.path.exists(dir_path): os.makedirs(dir_path) def export_ply(filename, points): pc = o3d.geometry.PointCloud() pc.points = o3d.utility.Vector3dVector(points) o3d.io.write_point_cloud(filename, pc, write_ascii=True) def test_single_category(category, model, params, save=True): if save: cat_dir = os.path.join(params.result_dir, category) image_dir = os.path.join(cat_dir, 'image') output_dir = os.path.join(cat_dir, 'output') make_dir(cat_dir) make_dir(image_dir) make_dir(output_dir) test_dataset = ShapeNet('/media/server/new/datasets/PCN', 'test_novel' if params.novel else 'test', category) test_dataloader = Data.DataLoader(test_dataset, batch_size=params.batch_size, shuffle=False) index = 1 total_l1_cd, total_l2_cd, total_f_score = 0.0, 0.0, 0.0 with torch.no_grad(): for p, c in test_dataloader: p = p.to(params.device) c = c.to(params.device) _, c_ = model(p) total_l1_cd += l1_cd(c_, c).item() total_l2_cd += l2_cd(c_, c).item() for i in range(len(c)): input_pc = p[i].detach().cpu().numpy() output_pc = c_[i].detach().cpu().numpy() gt_pc = c[i].detach().cpu().numpy() total_f_score += f_score(output_pc, gt_pc) if save: plot_pcd_one_view(os.path.join(image_dir, '{:03d}.png'.format(index)), [input_pc, output_pc, gt_pc], ['Input', 'Output', 'GT'], xlim=(-0.35, 0.35), ylim=(-0.35, 0.35), zlim=(-0.35, 0.35)) export_ply(os.path.join(output_dir, '{:03d}.ply'.format(index)), output_pc) index += 1 avg_l1_cd = total_l1_cd / len(test_dataset) avg_l2_cd = total_l2_cd / len(test_dataset) avg_f_score = total_f_score / len(test_dataset) return avg_l1_cd, avg_l2_cd, avg_f_score def test(params, save=False): if save: make_dir(params.result_dir) print(params.exp_name) # load pretrained model model = PCN(16384, 1024, 4).to(params.device) model.load_state_dict(torch.load(params.ckpt_path)) model.eval() print('\033[33m{:20s}{:20s}{:20s}{:20s}\033[0m'.format('Category', 'L1_CD(1e-3)', 'L2_CD(1e-4)', 'FScore-0.01(%)')) print('\033[33m{:20s}{:20s}{:20s}{:20s}\033[0m'.format('--------', '-----------', '-----------', '--------------')) if params.category == 'all': if params.novel: categories = CATEGORIES_PCN_NOVEL else: categories = CATEGORIES_PCN l1_cds, l2_cds, fscores = list(), list(), list() for category in categories: avg_l1_cd, avg_l2_cd, avg_f_score = test_single_category(category, model, params, save) print('{:20s}{:<20.4f}{:<20.4f}{:<20.4f}'.format(category.title(), 1e3 * avg_l1_cd, 1e4 * avg_l2_cd, 1e2 * avg_f_score)) l1_cds.append(avg_l1_cd) l2_cds.append(avg_l2_cd) fscores.append(avg_f_score) print('\033[33m{:20s}{:20s}{:20s}{:20s}\033[0m'.format('--------', '-----------', '-----------', '--------------')) print('\033[32m{:20s}{:<20.4f}{:<20.4f}{:<20.4f}\033[0m'.format('Average', np.mean(l1_cds) * 1e3, np.mean(l2_cds) * 1e4, np.mean(fscores) * 1e2)) else: avg_l1_cd, avg_l2_cd, avg_f_score = test_single_category(params.category, model, params, save) print('{:20s}{:<20.4f}{:<20.4f}{:<20.4f}'.format(params.category.title(), 1e3 * avg_l1_cd, 1e4 * avg_l2_cd, 1e2 * avg_f_score)) def test_single_category_emd(category, model, params): test_dataset = ShapeNet('/media/server/new/datasets/PCN', 'test_novel' if params.novel else 'test', category) test_dataloader = Data.DataLoader(test_dataset, batch_size=params.batch_size, shuffle=False) total_emd = 0.0 with torch.no_grad(): for p, c in test_dataloader: p = p.to(params.device) c = c.to(params.device) _, c_ = model(p) total_emd += emd(c_, c).item() avg_emd = total_emd / len(test_dataset) / c_.shape[1] return avg_emd def test_emd(params): print(params.exp_name) # load pretrained model model = PCN(16384, 1024, 4).to(params.device) model.load_state_dict(torch.load(params.ckpt_path)) model.eval() print('\033[33m{:20s}{:20s}\033[0m'.format('Category', 'EMD(1e-3)')) print('\033[33m{:20s}{:20s}\033[0m'.format('--------', '---------')) if params.category == 'all': if params.novel: categories = CATEGORIES_PCN_NOVEL else: categories = CATEGORIES_PCN emds = list() for category in categories: avg_emd = test_single_category_emd(category, model, params) print('{:20s}{:<20.4f}'.format(category.title(), 1e3 * avg_emd)) emds.append(avg_emd) print('\033[33m{:20s}{:20s}\033[0m'.format('--------', '---------')) print('\033[32m{:20s}{:<20.4f}\033[0m'.format('Average', np.mean(emds) * 1e3)) else: avg_emd = test_single_category_emd(params.category, model, params) print('{:20s}{:<20.4f}'.format(params.category.title(), 1e3 * avg_emd)) if __name__ == '__main__': parser = argparse.ArgumentParser('Point Cloud Completion Testing') parser.add_argument('--exp_name', type=str, help='Tag of experiment') parser.add_argument('--result_dir', type=str, default='results', help='Results directory') parser.add_argument('--ckpt_path', type=str, help='The path of pretrained model.') parser.add_argument('--category', type=str, default='all', help='Category of point clouds') parser.add_argument('--batch_size', type=int, default=1, help='Batch size for data loader') parser.add_argument('--num_workers', type=int, default=6, help='Num workers for data loader') parser.add_argument('--device', type=str, default='cuda:0', help='Device for testing') parser.add_argument('--save', type=bool, default=False, help='Saving test result') parser.add_argument('--novel', type=bool, default=False, help='unseen categories for testing') parser.add_argument('--emd', type=bool, default=False, help='Whether evaluate emd') params = parser.parse_args() if not params.emd: test(params, params.save) else: test_emd(params) ================================================ FILE: train.py ================================================ import argparse import os import datetime import random import torch import torch.optim as Optim from torch.utils.data.dataloader import DataLoader from tensorboardX import SummaryWriter from dataset import ShapeNet from models import PCN from metrics.metric import l1_cd from metrics.loss import cd_loss_L1, emd_loss from visualization import plot_pcd_one_view def make_dir(dir_path): if not os.path.exists(dir_path): os.makedirs(dir_path) def log(fd, message, time=True): if time: message = ' ==> '.join([datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), message]) fd.write(message + '\n') fd.flush() print(message) def prepare_logger(params): # prepare logger directory make_dir(params.log_dir) make_dir(os.path.join(params.log_dir, params.exp_name)) logger_path = os.path.join(params.log_dir, params.exp_name, params.category) ckpt_dir = os.path.join(params.log_dir, params.exp_name, params.category, 'checkpoints') epochs_dir = os.path.join(params.log_dir, params.exp_name, params.category, 'epochs') make_dir(logger_path) make_dir(ckpt_dir) make_dir(epochs_dir) logger_file = os.path.join(params.log_dir, params.exp_name, params.category, 'logger.log') log_fd = open(logger_file, 'a') log(log_fd, "Experiment: {}".format(params.exp_name), False) log(log_fd, "Logger directory: {}".format(logger_path), False) log(log_fd, str(params), False) train_writer = SummaryWriter(os.path.join(logger_path, 'train')) val_writer = SummaryWriter(os.path.join(logger_path, 'val')) return ckpt_dir, epochs_dir, log_fd, train_writer, val_writer def train(params): torch.backends.cudnn.benchmark = True ckpt_dir, epochs_dir, log_fd, train_writer, val_writer = prepare_logger(params) log(log_fd, 'Loading Data...') train_dataset = ShapeNet('data/PCN', 'train', params.category) val_dataset = ShapeNet('data/PCN', 'valid', params.category) train_dataloader = DataLoader(train_dataset, batch_size=params.batch_size, shuffle=True, num_workers=params.num_workers) val_dataloader = DataLoader(val_dataset, batch_size=params.batch_size, shuffle=False, num_workers=params.num_workers) log(log_fd, "Dataset loaded!") # model model = PCN(num_dense=16384, latent_dim=1024, grid_size=4).to(params.device) # optimizer optimizer = Optim.Adam(model.parameters(), lr=params.lr, betas=(0.9, 0.999)) lr_schedual = Optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.7) step = len(train_dataloader) // params.log_frequency # load pretrained model and optimizer if params.ckpt_path is not None: model.load_state_dict(torch.load(params.ckpt_path)) # training best_cd_l1 = 1e8 best_epoch_l1 = -1 train_step, val_step = 0, 0 for epoch in range(1, params.epochs + 1): # hyperparameter alpha if train_step < 10000: alpha = 0.01 elif train_step < 20000: alpha = 0.1 elif train_step < 50000: alpha = 0.5 else: alpha = 1.0 # training model.train() for i, (p, c) in enumerate(train_dataloader): p, c = p.to(params.device), c.to(params.device) optimizer.zero_grad() # forward propagation coarse_pred, dense_pred = model(p) # loss function if params.coarse_loss == 'cd': loss1 = cd_loss_L1(coarse_pred, c) elif params.coarse_loss == 'emd': coarse_c = c[:, :1024, :] loss1 = emd_loss(coarse_pred, coarse_c) else: raise ValueError('Not implemented loss {}'.format(params.coarse_loss)) loss2 = cd_loss_L1(dense_pred, c) loss = loss1 + alpha * loss2 # back propagation loss.backward() optimizer.step() if (i + 1) % step == 0: log(log_fd, "Training Epoch [{:03d}/{:03d}] - Iteration [{:03d}/{:03d}]: coarse loss = {:.6f}, dense l1 cd = {:.6f}, total loss = {:.6f}" .format(epoch, params.epochs, i + 1, len(train_dataloader), loss1.item() * 1e3, loss2.item() * 1e3, loss.item() * 1e3)) train_writer.add_scalar('coarse', loss1.item(), train_step) train_writer.add_scalar('dense', loss2.item(), train_step) train_writer.add_scalar('total', loss.item(), train_step) train_step += 1 lr_schedual.step() # evaluation model.eval() total_cd_l1 = 0.0 with torch.no_grad(): rand_iter = random.randint(0, len(val_dataloader) - 1) # for visualization for i, (p, c) in enumerate(val_dataloader): p, c = p.to(params.device), c.to(params.device) coarse_pred, dense_pred = model(p) total_cd_l1 += l1_cd(dense_pred, c).item() # save into image if rand_iter == i: index = random.randint(0, dense_pred.shape[0] - 1) plot_pcd_one_view(os.path.join(epochs_dir, 'epoch_{:03d}.png'.format(epoch)), [p[index].detach().cpu().numpy(), coarse_pred[index].detach().cpu().numpy(), dense_pred[index].detach().cpu().numpy(), c[index].detach().cpu().numpy()], ['Input', 'Coarse', 'Dense', 'Ground Truth'], xlim=(-0.35, 0.35), ylim=(-0.35, 0.35), zlim=(-0.35, 0.35)) total_cd_l1 /= len(val_dataset) val_writer.add_scalar('l1_cd', total_cd_l1, val_step) val_step += 1 log(log_fd, "Validate Epoch [{:03d}/{:03d}]: L1 Chamfer Distance = {:.6f}".format(epoch, params.epochs, total_cd_l1 * 1e3)) if total_cd_l1 < best_cd_l1: best_epoch_l1 = epoch best_cd_l1 = total_cd_l1 torch.save(model.state_dict(), os.path.join(ckpt_dir, 'best_l1_cd.pth')) log(log_fd, 'Best l1 cd model in epoch {}, the minimum l1 cd is {}'.format(best_epoch_l1, best_cd_l1 * 1e3)) log_fd.close() if __name__ == '__main__': parser = argparse.ArgumentParser('PCN') parser.add_argument('--exp_name', type=str, help='Tag of experiment') parser.add_argument('--log_dir', type=str, default='log', help='Logger directory') parser.add_argument('--ckpt_path', type=str, default=None, help='The path of pretrained model') parser.add_argument('--lr', type=float, default=0.0001, help='Learning rate') parser.add_argument('--category', type=str, default='all', help='Category of point clouds') parser.add_argument('--epochs', type=int, default=200, help='Epochs of training') parser.add_argument('--batch_size', type=int, default=32, help='Batch size for data loader') parser.add_argument('--coarse_loss', type=str, default='cd', help='loss function for coarse point cloud') parser.add_argument('--num_workers', type=int, default=6, help='num_workers for data loader') parser.add_argument('--device', type=str, default='cuda:0', help='device for training') parser.add_argument('--log_frequency', type=int, default=10, help='Logger frequency in every epoch') parser.add_argument('--save_frequency', type=int, default=10, help='Model saving frequency') params = parser.parse_args() train(params) ================================================ FILE: visualization/__init__.py ================================================ from visualization.visualization import plot_pcd_one_view, o3d_visualize_pc ================================================ FILE: visualization/visualization.py ================================================ import open3d as o3d import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D def o3d_visualize_pc(pc): point_cloud = o3d.geometry.PointCloud() point_cloud.points = o3d.utility.Vector3dVector(pc) o3d.visualization.draw_geometries([point_cloud]) def plot_pcd_one_view(filename, pcds, titles, suptitle='', sizes=None, cmap='Reds', zdir='y', xlim=(-0.5, 0.5), ylim=(-0.5, 0.5), zlim=(-0.5, 0.5)): if sizes is None: sizes = [0.5 for i in range(len(pcds))] fig = plt.figure(figsize=(len(pcds) * 3 * 1.4, 3 * 1.4)) elev = 30 # 水平倾斜 azim = -45 # 旋转 for j, (pcd, size) in enumerate(zip(pcds, sizes)): color = pcd[:, 0] ax = fig.add_subplot(1, len(pcds), j + 1, projection='3d') ax.view_init(elev, azim) ax.scatter(pcd[:, 0], pcd[:, 1], pcd[:, 2], zdir=zdir, c=color, s=size, cmap=cmap, vmin=-1.0, vmax=0.5) ax.set_title(titles[j]) ax.set_axis_off() ax.set_xlim(xlim) ax.set_ylim(ylim) ax.set_zlim(zlim) plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.9, wspace=0.1, hspace=0.1) plt.suptitle(suptitle) fig.savefig(filename) plt.close(fig)