Faza 04 · lecția 13
Viziune 3D — nori de puncte și NeRF-uri
Scopul lecției: Viziunea 3D are două forme principale. Norii de puncte sunt ieșirea brută a senzorului. NeRF-urile sunt câmpul volumetric învățat. Ambele răspund la întrebarea „ce se află unde în spațiu”.
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Obiective de învățare
- Problema
- Conceptul
- Norii de puncte
- Arhitectura PointNet
- Câmpuri neuronale de radianță (NeRF-uri)
- Codarea pozițională în NeRF
- Redare volumetrică
- Ce a înlocuit NeRF-urile
- Seturi de date și repere de evaluare
- Construiți
- Pasul 1: Clasificator PointNet
- Pasul 2: Codarea pozițională
- Pasul 3: MLP NeRF mic
- Pasul 4: Redare volumetrică de-a lungul unei raze
- Utilizați
- Livrați
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
Viziunea 3D are două forme principale. Norii de puncte sunt ieșirea brută a senzorului. NeRF-urile sunt câmpul volumetric învățat. Ambele răspund la întrebarea „ce se află unde în spațiu”.
Tip: Învățați + construiți Limbaje: Python Cerințe prealabile: Faza 4 Lecția 03 (CNN-uri), Faza 1 Lecția 12 (Operații pe tensori) Timp: ~45 de minute
Obiective de învățare
- Deosebiți reprezentările 3D explicite (nor de puncte, plasă, voxel) de cele implicite (câmp de distanță cu semn, NeRF) și momentele în care este folosită fiecare
- Înțelegeți trucul funcției simetrice din PointNet, care face o rețea neuronală invariantă la permutări pe un set neordonat de puncte
- Urmăriți o trecere înainte NeRF: lansarea razelor, redarea volumetrică, codarea pozițională și capul MLP pentru densitate și culoare
- Utilizați
nerfstudiosauinstant-ngppentru reconstrucție 3D preantrenată dintr-un număr mic de imagini cu poziții cunoscute
Problema
O cameră produce o imagine 2D. Un LiDAR produce un set de puncte 3D fără ordine. Un flux de procesare structure-from-motion produce un nor rar de puncte-cheie 3D. Un NeRF reconstruiește o întreagă scenă 3D dintr-un număr mic de imagini cu poziții cunoscute. Toate acestea sunt „viziune”, însă niciuna nu seamănă cu tensorul dens dorit de un CNN.
Viziunea 3D contează deoarece aproape orice sarcină robotică cu valoare mare rulează în 3D: apucare, evitare a obstacolelor, navigație, ocluzie AR și capturare de conținut 3D. Un inginer de viziune care înțelege numai imaginile 2D nu poate participa la segmentul cu cea mai rapidă creștere din domeniu: conținut AR/VR, robotică, sisteme de conducere autonomă și reconstrucție 3D pe bază de NeRF pentru imobiliare sau construcții.
Cele două reprezentări domină din motive diferite. Norii de puncte sunt ceea ce oferă senzorii fără procesare suplimentară. NeRF-urile și succesoarele lor (3D Gaussian splatting, SDF-uri neuronale) sunt ceea ce obțineți când cereți unei rețele neuronale să învețe o scenă.
Conceptul
Norii de puncte
Un nor de puncte este un set neordonat de N puncte din R^3, fiecare putând avea opțional caracteristici precum culoarea, intensitatea sau normala.
cloud = [
(x1, y1, z1, r1, g1, b1),
(x2, y2, z2, r2, g2, b2),
...
(xN, yN, zN, rN, gN, bN),
]
Fără grilă, fără conectivitate. Două proprietăți îngreunează folosirea lor în rețele neuronale:
- Invarianța la permutare — ieșirea nu trebuie să depindă de ordinea punctelor.
- N variabil — un singur model trebuie să gestioneze nori de dimensiuni diferite.
PointNet (Qi et al., 2017) le-a rezolvat pe amândouă cu o singură idee: aplicați un MLP partajat fiecărui punct, apoi agregați cu o funcție simetrică (max pooling). Rezultatul este un vector cu dimensiune fixă, care nu depinde de ordine.
f(P) = max_{p in P} MLP(p)
Acesta este întregul nucleu al PointNet. Variantele mai profunde (PointNet++, Point Transformer) adaugă eșantionare ierarhică și agregare locală, dar trucul funcției simetrice rămâne neschimbat.
Notă tehnică a traducerii: PointNet++ aplică PointNet recursiv pe partiții imbricate pentru a învăța caracteristici locale la mai multe scări. Point Transformer folosește straturi de autoatenție pentru nori de puncte. Prin urmare, nu este corect ca agregarea locală a acestor variante să fie descrisă drept același truc de max-pooling „neschimbat”.
Arhitectura PointNet
„MLP partajat” înseamnă că același MLP rulează independent pe fiecare punct. Pentru eficiență, este implementat ca o convoluție 1x1 pe dimensiunea punctelor.
Câmpuri neuronale de radianță (NeRF-uri)
NeRF-urile (Mildenhall et al., 2020) au luat întrebarea „putem reconstrui o scenă 3D din N fotografii?” și au răspuns cu o rețea neuronală care este scena. Rețeaua mapează (x, y, z, viewing_direction) la (density, colour). Redarea unei vederi noi este o buclă de lansare a razelor prin această rețea.
MLP NeRF: (x, y, z, theta, phi) -> (sigma, r, g, b)
Pentru a reda pixelul (u, v) al unei vederi noi:
1. Lansați o rază din cameră prin pixelul (u, v)
2. Eșantionați puncte de-a lungul razei la distanțele t_1, t_2, ..., t_N
3. Interogați MLP-ul în fiecare punct
4. Compuneți culorile ponderate cu (1 - exp(-sigma * dt))
5. Suma este culoarea pixelului redat
O funcție de pierdere compară pixelul redat cu pixelul de adevăr de referință din fotografiile de antrenare. Retropropagarea prin pasul de redare actualizează MLP-ul. Fără adevăr de referință 3D, fără geometrie explicită — scena este stocată în ponderile MLP-ului.
Notă tehnică a traducerii: NeRF-ul clasic se antrenează din imagini cu poziții de cameră cunoscute; în practică, parametrii intrinseci și pozițiile extrinseci trebuie calibrați sau estimați, de obicei prin structure-from-motion. În arhitectura originală, densitatea depinde numai de poziția spațială, iar radianța sau culoarea dependentă de vedere depinde de poziție și de direcția de observare; notația compactă din lecție ascunde această distincție.
Codarea pozițională în NeRF
Un MLP obișnuit pe (x, y, z) nu poate reprezenta detalii de frecvență înaltă deoarece MLP-urile au o părtinire spectrală către frecvențe joase. NeRF rezolvă aceasta prin codarea fiecărei coordonate într-un vector de caracteristici Fourier înainte de MLP:
gamma(p) = (sin(2^0 pi p), cos(2^0 pi p), sin(2^1 pi p), cos(2^1 pi p), ...)
Până la L=10 niveluri de frecvență. Este același truc folosit de Transformere pentru poziții și apare din nou în condiționarea temporală a difuziei (Lecția 10). Fără el, NeRF-urile arată neclar.
Notă tehnică a traducerii: Afirmația că un MLP obișnuit „nu poate reprezenta” frecvențe înalte este prea absolută: în configurațiile studiate, un MLP standard are o părtinire spectrală și le învață dificil, iar caracteristicile Fourier schimbă problema de învățare. Codarea NeRF și codarea pozițională sinusoidală din Transformer folosesc motivul
sin/cos, dar au parametrizări și roluri diferite: prima codează coordonate spațiale continue, iar a doua poziții de tokenuri într-o secvență. Condiționarea temporală din difuzie este un tipar înrudit, nu aceeași reprezentare exactă.
Redare volumetrică
C(r) = sum_i T_i * (1 - exp(-sigma_i * delta_i)) * c_i
T_i = exp(- sum_{j<i} sigma_j * delta_j)
delta_i = t_{i+1} - t_i
T_i este transmisia — câtă lumină supraviețuiește până la punctul i. (1 - exp(-sigma_i * delta_i)) este opacitatea în punctul i. c_i este culoarea. Pixelul final este o sumă ponderată de-a lungul razei.
Ce a înlocuit NeRF-urile
NeRF-urile pure se antrenează lent (ore) și se redau lent (secunde per imagine). Evoluția ulterioară:
- Instant-NGP (2022) — codarea cu grilă hash înlocuiește intrarea pozițională a MLP-ului; se antrenează în secunde.
- Mip-NeRF 360 — gestionează scene neîncadrate și anti-aliasing.
- 3D Gaussian Splatting (2023) — înlocuiește câmpul volumetric cu milioane de gaussiene 3D; se antrenează în minute și redă în timp real. Reprezintă opțiunea implicită curentă pentru producție.
Aproape fiecare produs NeRF real din 2026 este, de fapt, 3D Gaussian splatting. Modelul mental rămâne totuși NeRF-ul.
Notă tehnică a traducerii: Afirmațiile despre o opțiune implicită universală pentru producție, viteze de 100x și calitate comparabilă nu sunt garantate. Performanța, fidelitatea și alegerea reprezentării depind de scenă, captură, hardware, buget și cerințele aplicației; lucrarea 3D Gaussian Splatting raportează rezultate pentru protocolul său experimental.
Seturi de date și repere de evaluare
- ShapeNet — clasificarea și segmentarea modelelor CAD 3D ca nori de puncte.
- ScanNet — scanări interioare reale pentru segmentare.
- KITTI — nori de puncte LiDAR exteriori pentru conducere autonomă.
- NeRF Synthetic / Blended MVS — seturi de date cu imagini poziționate pentru sinteza de vederi.
- Setul de date Mip-NeRF 360 — scene reale neîncadrate.
Construiți
Pasul 1: Clasificator PointNet
import torch
import torch.nn as nn
class PointNet(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.mlp1 = nn.Sequential(
nn.Conv1d(3, 64, 1), nn.BatchNorm1d(64), nn.ReLU(inplace=True),
nn.Conv1d(64, 64, 1), nn.BatchNorm1d(64), nn.ReLU(inplace=True),
)
self.mlp2 = nn.Sequential(
nn.Conv1d(64, 128, 1), nn.BatchNorm1d(128), nn.ReLU(inplace=True),
nn.Conv1d(128, 1024, 1), nn.BatchNorm1d(1024), nn.ReLU(inplace=True),
)
self.head = nn.Sequential(
nn.Linear(1024, 512), nn.BatchNorm1d(512), nn.ReLU(inplace=True),
nn.Dropout(0.3),
nn.Linear(512, 256), nn.BatchNorm1d(256), nn.ReLU(inplace=True),
nn.Dropout(0.3),
nn.Linear(256, num_classes),
)
def forward(self, x):
# x: (N, 3, num_points) — transposed for Conv1d
x = self.mlp1(x)
x = self.mlp2(x)
x = torch.max(x, dim=-1)[0] # (N, 1024)
return self.head(x)
pts = torch.randn(4, 3, 1024)
net = PointNet(num_classes=10)
print(f"output: {net(pts).shape}")
print(f"params: {sum(p.numel() for p in net.parameters()):,}")
Aproximativ 1,6M parametri. Rulează pe 1.024 de puncte per nor.
Notă tehnică a traducerii: Pentru
num_classes=10, codul de mai sus are exact 807.626 parametri antrenabili, incluzând parametrii afini ai straturilor BatchNorm; afirmația „1,6M” este incorectă. Numărul se schimbă odată cunum_classes, iar buffer-ele BatchNorm nu sunt numărate deparameters().
Pasul 2: Codarea pozițională
def positional_encoding(x, L=10):
"""
x: (..., D) -> (..., D * 2 * L)
"""
freqs = 2.0 ** torch.arange(L, dtype=x.dtype, device=x.device)
args = x.unsqueeze(-1) * freqs * 3.141592653589793
sinc = torch.cat([args.sin(), args.cos()], dim=-1)
return sinc.reshape(*x.shape[:-1], -1)
x = torch.randn(5, 3)
y = positional_encoding(x, L=10)
print(f"input: {x.shape}")
print(f"encoded: {y.shape} # (5, 60)")
Înmulțirea cu 2^l * pi produce frecvențe progresiv mai înalte.
Pasul 3: MLP NeRF mic
class TinyNeRF(nn.Module):
def __init__(self, L_pos=10, L_dir=4, hidden=128):
super().__init__()
self.L_pos = L_pos
self.L_dir = L_dir
pos_dim = 3 * 2 * L_pos
dir_dim = 3 * 2 * L_dir
self.trunk = nn.Sequential(
nn.Linear(pos_dim, hidden), nn.ReLU(inplace=True),
nn.Linear(hidden, hidden), nn.ReLU(inplace=True),
nn.Linear(hidden, hidden), nn.ReLU(inplace=True),
nn.Linear(hidden, hidden), nn.ReLU(inplace=True),
)
self.sigma = nn.Linear(hidden, 1)
self.color = nn.Sequential(
nn.Linear(hidden + dir_dim, hidden // 2), nn.ReLU(inplace=True),
nn.Linear(hidden // 2, 3), nn.Sigmoid(),
)
def forward(self, x, d):
x_enc = positional_encoding(x, self.L_pos)
d_enc = positional_encoding(d, self.L_dir)
h = self.trunk(x_enc)
sigma = torch.relu(self.sigma(h)).squeeze(-1)
rgb = self.color(torch.cat([h, d_enc], dim=-1))
return sigma, rgb
nerf = TinyNeRF()
x = torch.randn(128, 3)
d = torch.randn(128, 3)
s, c = nerf(x, d)
print(f"sigma: {s.shape} rgb: {c.shape}")
Este mic comparativ cu NeRF-ul original (care are 2 trunchiuri MLP de adâncime 8). Este suficient pentru a demonstra arhitectura.
Notă tehnică a traducerii: NeRF-ul original folosește două rețele de același tip, una grosieră și una fină, pentru eșantionare ierarhică; fiecare are un trunchi MLP de opt straturi pentru poziție, urmat de o ramură condiționată de direcție pentru culoare. Prin urmare, formularea „două trunchiuri MLP cu adâncimea 8” nu descrie cu precizie arhitectura unei singure rețele.
Pasul 4: Redare volumetrică de-a lungul unei raze
def volumetric_render(sigma, rgb, t_vals):
"""
sigma: (..., N_samples)
rgb: (..., N_samples, 3)
t_vals: (N_samples,) distances along the ray
"""
delta = torch.cat([t_vals[1:] - t_vals[:-1], torch.full_like(t_vals[:1], 1e10)])
alpha = 1.0 - torch.exp(-sigma * delta)
trans = torch.cumprod(torch.cat([torch.ones_like(alpha[..., :1]), 1.0 - alpha + 1e-10], dim=-1), dim=-1)[..., :-1]
weights = alpha * trans
rendered = (weights.unsqueeze(-1) * rgb).sum(dim=-2)
depth = (weights * t_vals).sum(dim=-1)
return rendered, depth, weights
N = 64
t_vals = torch.linspace(2.0, 6.0, N)
sigma = torch.rand(N) * 0.5
rgb = torch.rand(N, 3)
rendered, depth, weights = volumetric_render(sigma, rgb, t_vals)
print(f"rendered colour: {rendered.tolist()}")
print(f"depth: {depth.item():.2f}")
O rază, 64 de eșantioane, compunere într-un singur pixel RGB și o adâncime.
Utilizați
Pentru lucru real:
nerfstudio(Tancik et al.) — biblioteca de referință actuală pentru NeRF / Instant-NGP / Gaussian Splatting. Include linie de comandă și un vizualizator web.pytorch3d(Meta) — redare diferențiabilă, utilitare pentru nori de puncte și operații pe plase.open3d— procesarea, alinierea și vizualizarea norilor de puncte.
Pentru implementare, 3D Gaussian splatting a înlocuit în mare măsură NeRF-urile pure deoarece redă de 100x mai rapid. Calitatea reconstrucției este comparabilă.
Livrați
Această lecție produce:
outputs/prompt-3d-task-router.md— un prompt care rutează către reprezentarea 3D corectă (nor de puncte, plasă, voxel, NeRF, gaussian splat) pe baza sarcinii și a datelor de intrare.outputs/skill-point-cloud-loader.md— o aptitudine care scrie un PyTorchDatasetpentru fișiere .ply / .pcd / .xyz cu normalizare, centrare și eșantionare a punctelor corecte.
Exerciții
- (Ușor) Arătați că PointNet este invariant la permutări: rulați același nor de două ori, o dată cu punctele amestecate. Verificați că ieșirile sunt identice până la zgomotul de virgulă mobilă.
Notă tehnică a traducerii: Înainte de comparație apelați
net.eval(). În modul de antrenare, Dropout eșantionează măști aleatoare, astfel încât două treceri pot produce ieșiri diferite chiar pentru același nor;torch.no_grad()elimină doar urmărirea gradientului și nu schimbă comportamentul Dropout.
- (Mediu) Implementați o funcție minimală de generare a razelor care, având parametrii intrinseci și poziția camerei, produce originile și direcțiile razelor pentru fiecare pixel al unei imagini H x W.
- (Dificil) Antrenați un TinyNeRF pe un set de date sintetic cu vederi redate ale unui cub colorat (generat prin redare diferențiabilă sau un ray tracer simplu). Raportați pierderea de redare la epocile 1, 10 și 100. La ce epocă produce modelul vederi recognoscibile?
Termeni-cheie
| Termen | Cum este numit în practică | Ce înseamnă de fapt |
|---|---|---|
| Nor de puncte | „Puncte 3D de la LiDAR” | Set neordonat de (x, y, z) + caracteristici opționale per punct |
| PointNet | „Prima rețea neuronală pentru nori de puncte” | MLP partajat per punct + max pool simetric; invariantă la permutări prin construcție |
| NeRF | „MLP-ul care este scena” | Rețea care mapează (x, y, z, dir) la (densitate, culoare); redată prin lansarea razelor |
| Codare pozițională | „Caracteristici Fourier” | Codează fiecare coordonată în sin/cos la frecvențe multiple pentru a depăși părtinirea MLP către frecvențe joase |
| Redare volumetrică | „Integrare de-a lungul razei” | Compune eșantioane de-a lungul unei raze într-un singur pixel folosind transmisia și alpha |
| Instant-NGP | „NeRF cu grilă hash” | Înlocuiește MLP-ul de coordonate NeRF cu o grilă hash multirezoluție; de 100–1000x mai rapid |
| 3D Gaussian splatting | „Milioane de gaussiene” | Scena = colecție de gaussiene 3D; redă în timp real și se antrenează în minute |
| SDF | „Câmp de distanță cu semn” | Funcție care întoarce distanța cu semn până la cea mai apropiată suprafață; o altă reprezentare implicită |
Notă tehnică a traducerii: Instant-NGP nu înlocuiește MLP-ul NeRF cu grila hash; grila hash multirezoluție înlocuiește codarea pozițională clasică, iar un MLP mic rămâne în model. Vitezele, redarea în timp real și duratele de antrenare sunt rezultate dependente de implementare, scenă și hardware, nu proprietăți universale.
Lecturi suplimentare
- PointNet (Qi et al., 2017) — clasificatorul invariant la permutări
- NeRF (Mildenhall et al., 2020) — lucrarea care a transformat reconstrucția 3D din fotografii într-o problemă de rețele neuronale
- Instant-NGP (Müller et al., 2022) — grile hash și accelerare de 1000x
- 3D Gaussian Splatting (Kerbl et al., 2023) — arhitectura care a înlocuit NeRF-urile în producție
Sursă: Originalul în limba engleză
Navigare: ← Lecția 04.12 — Înțelegerea videoclipurilor — modelare temporală · Faza 4 — Viziune computerizată · Lecția 04.14 — Transformere vizuale (ViT) → · Catalog complet