bdck commited on
Commit
498e43a
Β·
verified Β·
1 Parent(s): 6afcbbc

Upload README.md

Browse files
Files changed (1) hide show
  1. README.md +246 -0
README.md ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NKSR Wrapper β€” Neural Kernel Surface Reconstruction
2
+
3
+ [![Paper](https://img.shields.io/badge/Paper-CVPR%202023-blue)](https://arxiv.org/abs/2305.19590)
4
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5
+
6
+ A **clean, high-level Python wrapper** around **Neural Kernel Surface Reconstruction (NKSR)** by Huang et al. (CVPR 2023, Highlight).
7
+ Drop a `.ply` or `.pcd` point cloud in β†’ get a watertight, high-quality triangle mesh out.
8
+
9
+ ---
10
+
11
+ ## πŸ“¦ What is NKSR?
12
+
13
+ **Neural Kernel Surface Reconstruction** is a deep-learning method that turns a raw, sparse, and potentially noisy point cloud into a smooth, watertight 3D mesh. Unlike classical methods (Poisson, Alpha shapes) it learns a **continuous implicit surface** from data, and unlike vanilla neural fields (NeRF, DeepSDF) it scales to **millions of points** and **generalises across objects, rooms, and outdoor scenes** without per-scene training.
14
+
15
+ ### The three key innovations
16
+
17
+ | Innovation | Why it matters |
18
+ |-----------|----------------|
19
+ | **Compactly-supported kernel functions** | The implicit field is built from local kernel basis functions that have finite support. This makes the linear system **sparse**, so it can be solved with fast sparse PCG solvers instead of dense matrix inversion. Result: room-scale reconstruction in seconds. |
20
+ | **Gradient fitting solve** | Instead of only fitting point positions (SDF β‰ˆ 0), NKSR also fits **surface normals** as gradients of the field. This makes the reconstruction dramatically more robust to noise and outliers. |
21
+ | **Minimal training, maximum generalisation** | The model is trained once on a mixture of synthetic and real data (the "kitchen-sink" config) and then works out-of-the-box on new scans **without any fine-tuning**. |
22
+
23
+ ---
24
+
25
+ ## πŸš€ Quick start
26
+
27
+ ### 1. Install dependencies
28
+
29
+ NKSR itself contains custom CUDA kernels, so you need a working PyTorch + CUDA environment.
30
+
31
+ ```bash
32
+ # 1. Clone the original NKSR repo and install it
33
+ # (see https://github.com/nv-tlabs/NKSR for the latest instructions)
34
+ git clone https://github.com/nv-tlabs/NKSR.git
35
+ cd NKSR
36
+ pip install -r requirements.txt
37
+ pip install --no-build-isolation package/
38
+
39
+ # 2. Install this wrapper
40
+ pip install -e .
41
+ ```
42
+
43
+ ### 2. One-liner reconstruction
44
+
45
+ ```python
46
+ from nksr_wrapper import NKSRMeshReconstructor, load_point_cloud, save_mesh
47
+
48
+ points, normals = load_point_cloud("scan.ply")
49
+ recon = NKSRMeshReconstructor(device="cuda:0")
50
+ mesh = recon.reconstruct(points, normals, detail_level=1.0)
51
+ save_mesh("mesh.ply", mesh.vertices, mesh.faces)
52
+ ```
53
+
54
+ Or use the CLI:
55
+
56
+ ```bash
57
+ python scripts/reconstruct.py scan.ply mesh.ply --detail 1.0 --mise-iter 1
58
+ ```
59
+
60
+ ---
61
+
62
+ ## πŸ”¬ How it works (the full pipeline)
63
+
64
+ If you want to understand what is happening under the hood, here is the step-by-step pipeline that NKSR executes every time you call `reconstruct()`.
65
+
66
+ ### Step 0 β€” Input
67
+ You provide:
68
+ * `xyz` β€” (N, 3) point positions
69
+ * `normal` β€” (N, 3) **oriented** normals (optional but strongly recommended)
70
+ * `sensor` β€” (N, 3) sensor/camera positions (optional; used for normal orientation when normals are missing)
71
+
72
+ ### Step 1 β€” Voxelisation (Sparse Feature Hierarchy)
73
+ The input points are splatted into a **sparse voxel grid** at multiple resolutions (a quad-/octree-like structure called the *Sparse Feature Hierarchy*, SVH). Instead of a dense 3D array, only occupied voxels are stored. This is what lets NKSR handle millions of points without exploding memory.
74
+
75
+ *Key parameter:* `voxel_size` (default β‰ˆ 0.1 in the pretrained config). One voxel_size unit = one spatial unit in your point-cloud coordinate system.
76
+
77
+ ### Step 2 β€” Feature encoding (PointNet β†’ Sparse 3D U-Net)
78
+ 1. **PointEncoder** β€” A small PointNet-style ResNet processes the raw points inside each voxel and produces a 32-dim feature vector per voxel.
79
+ 2. **SparseStructureNet** β€” A sparse 3D convolutional U-Net with skip connections processes these voxel features across multiple scales. It also predicts an *adaptive structure*: if a region is empty, the network stops subdividing early, saving computation.
80
+
81
+ ### Step 3 β€” Geometry field (Kernel Field)
82
+ This is the heart of NKSR.
83
+
84
+ The network outputs **kernel basis parameters** at each voxel. At any 3D query point `x`, the implicit function is evaluated as a weighted sum of compact kernel functions centred on nearby voxels:
85
+
86
+ ```
87
+ f(x) = Ξ£_i w_i Β· Ο†_i(x)
88
+ ```
89
+
90
+ where `Ο†_i` is a compact kernel (e.g. Wendland or similar) and `w_i` are learned weights. Because the kernels have finite support, the sum only involves neighbours within a small radius β†’ **sparse linear system**.
91
+
92
+ ### Step 4 β€” Sparse linear solve (PCG)
93
+ NKSR now solves for the weights `w` by fitting two constraints:
94
+
95
+ 1. **Position constraint:** `f(x_j) β‰ˆ 0` on every input point (the surface is the zero level-set).
96
+ 2. **Normal constraint:** `βˆ‡f(x_j) β‰ˆ n_j` on voxel centres (the gradient of the field matches the surface normal).
97
+
98
+ These constraints are assembled into a large but **sparse** linear system and solved with a **preconditioned conjugate-gradient (PCG)** solver. The normal constraint is the secret sauce: it anchors the *gradient* of the field, making the reconstruction much less sensitive to noise than methods that only fit positions.
99
+
100
+ ### Step 5 β€” Mask / trimming field (optional)
101
+ A secondary field (either a learned UDF β€” Unsigned Distance Field β€” or a simple layer field) identifies regions that are *outside* the true surface. This trims away spurious floaters and fills small holes, producing a clean watertight boundary.
102
+
103
+ ### Step 6 β€” Mesh extraction (Dual Marching Cubes + MISE)
104
+ Finally, the zero level-set of the implicit field is turned into triangles:
105
+
106
+ 1. **Dual Marching Cubes (DMC)** is run on the dual graph of the sparse voxel hierarchy. DMC produces nicer topology than standard Marching Cubes (fewer skinny triangles, better sharp-feature preservation).
107
+ 2. **MISE** (Multi-resolution IsoSurface Extraction) adaptively subdivides cells that straddle the zero crossing. Each `mise_iter` doubles the effective resolution in those cells, giving you a crisp mesh without wasting polygons on empty space.
108
+
109
+ *Result:* `mesh.v` (VΓ—3 vertices) and `mesh.f` (FΓ—3 face indices).
110
+
111
+ ---
112
+
113
+ ## 🧰 API Reference
114
+
115
+ ### `NKSRMeshReconstructor`
116
+
117
+ ```python
118
+ class NKSRMeshReconstructor(
119
+ device="cuda:0",
120
+ config="ks",
121
+ chunk_tmp_device="cpu",
122
+ )
123
+ ```
124
+
125
+ * `device` β€” PyTorch device (CUDA strongly recommended).
126
+ * `config` β€” Pretrained model name:
127
+ * `"ks"` β€” **Kitchen-sink** (recommended default). Trained on a mixture of synthetic and real scans; generalises to objects, indoor rooms, and outdoor scenes.
128
+ * `"snet"` β€” ShapeNet objects with normals.
129
+ * `"snet-wonormal"` β€” ShapeNet objects without normals.
130
+ * `chunk_tmp_device` β€” Where to stash finished chunks when reconstructing huge scenes. `"cpu"` offloads to system RAM.
131
+
132
+ #### `.reconstruct(...)`
133
+
134
+ ```python
135
+ mesh = recon.reconstruct(
136
+ points, # (N, 3) required
137
+ normals=None, # (N, 3) optional, strongly recommended
138
+ sensor_positions=None, # (N, 3) optional, helps orient normals
139
+ colors=None, # (N, 3) optional, for colored mesh output
140
+
141
+ # Quality / resolution
142
+ detail_level=1.0, # 0.0 = smooth, 1.0 = max detail
143
+ voxel_size=None, # override resolution explicitly
144
+ mise_iter=1, # 0 = base, 1 = 2Γ— in subdivided cells, 2 = 4Γ—
145
+
146
+ # Large-scene settings
147
+ chunk_size=-1.0, # >0 enables out-of-core chunking
148
+ overlap_ratio=0.05,
149
+
150
+ # Solver tuning
151
+ solver_max_iter=2000,
152
+ solver_tol=1e-5,
153
+ approx_kernel_grad=False,
154
+
155
+ # Normal estimation fallback
156
+ estimate_normals_if_missing=True,
157
+ normal_knn=64,
158
+ normal_drop_threshold_deg=85.0,
159
+ )
160
+ ```
161
+
162
+ Returns a `MeshResult` dataclass with:
163
+ * `.vertices` β€” (V, 3) float array
164
+ * `.faces` β€” (F, 3) int array
165
+ * `.vertex_colors` β€” (V, 3) float array, if `colors` was provided
166
+ * `.save(path)` β€” convenience method to write PLY/OBJ/GLB via Trimesh
167
+
168
+ ---
169
+
170
+ ## πŸ“‚ Repository layout
171
+
172
+ ```
173
+ nksr-wrapper/
174
+ β”œβ”€β”€ nksr_wrapper/
175
+ β”‚ β”œβ”€β”€ __init__.py # public API
176
+ β”‚ β”œβ”€β”€ reconstructor.py # NKSRMeshReconstructor + MeshResult
177
+ β”‚ └── io.py # load_point_cloud, save_mesh
178
+ β”œβ”€β”€ scripts/
179
+ β”‚ └── reconstruct.py # CLI entry point
180
+ β”œβ”€β”€ examples/
181
+ β”‚ β”œβ”€β”€ quickstart.py # minimal script
182
+ β”‚ └── chunked_reconstruction.py # large-scene example
183
+ β”œβ”€β”€ setup.py
184
+ β”œβ”€β”€ requirements.txt
185
+ └── README.md
186
+ ```
187
+
188
+ ---
189
+
190
+ ## πŸ–₯️ CLI Usage
191
+
192
+ ```bash
193
+ # Basic reconstruction
194
+ python scripts/reconstruct.py scan.ply mesh.ply --detail 1.0
195
+
196
+ # Large scene (chunked)
197
+ python scripts/reconstruct.py huge_scan.ply mesh.ply --chunk-size 50.0
198
+
199
+ # No normals in file β€” estimate on-the-fly
200
+ python scripts/reconstruct.py scan.ply mesh.ply --estimate-normals
201
+
202
+ # With per-point colors β†’ colored mesh
203
+ python scripts/reconstruct.py scan.ply mesh.ply --colors colors.npy --mise-iter 2
204
+ ```
205
+
206
+ ---
207
+
208
+ ## 🎯 Tips & Troubleshooting
209
+
210
+ | Problem | Solution |
211
+ |---------|----------|
212
+ | Mesh is too noisy / has spikes | Lower `detail_level` (try `0.3`) or increase `voxel_size` |
213
+ | Mesh is too smooth / missing fine detail | Raise `detail_level` (try `1.0`) or set `mise_iter=2` |
214
+ | Out-of-memory on large scans | Use `chunk_size=50.0` and `chunk_tmp_device="cpu"` |
215
+ | Mesh is inside-out | Normals are unoriented. Provide `sensor_positions` or pre-orient normals with Open3D |
216
+ | Reconstruction is very slow | You are probably on CPU. NKSR requires CUDA for the custom sparse kernels. |
217
+ | PLY file has no normals | Use `--estimate-normals` or pass `sensor_positions` to the reconstructor |
218
+
219
+ ---
220
+
221
+ ## πŸ“š Citation
222
+
223
+ If you use NKSR in your research, please cite the original paper:
224
+
225
+ ```bibtex
226
+ @inproceedings{huang2023nksr,
227
+ title={Neural Kernel Surface Reconstruction},
228
+ author={Huang, Jiahui and Gojcic, Zan and Atzmon, Matan and
229
+ Litany, Or and Fidler, Sanja and Williams, Francis},
230
+ booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
231
+ year={2023}
232
+ }
233
+ ```
234
+
235
+ Original code: [https://github.com/nv-tlabs/NKSR](https://github.com/nv-tlabs/NKSR)
236
+ Pretrained weights: [https://huggingface.co/heiwang1997/nksr-checkpoints](https://huggingface.co/heiwang1997/nksr-checkpoints)
237
+
238
+ ---
239
+
240
+ ## πŸ“„ License
241
+
242
+ This wrapper is released under the MIT License. NKSR itself is under its own license (see the original repository).
243
+
244
+ ---
245
+
246
+ *Built with ❀️ on top of NVIDIA t-labs' NKSR.*