Фаза 04 · урок 13
3D-зрение — облака точек и NeRF
Цель урока: Камера создаёт 2D-изображение. LIDAR создаёт набор 3D-точек без порядка. Конвейер structure-from-motion создаёт разреженное облако 3D-ключевых точек. NeRF реконструирует целую 3D-сцену по нескольким изображениям с известными позами. Всё…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Облака точек
- Архитектура PointNet
- Поля нейронного излучения (NeRF)
- Позиционное кодирование в NeRF
- Объёмный рендеринг
- Чем заменили NeRF
- Наборы данных и бенчмарки
- Соберите
- Шаг 1: классификатор PointNet
- Шаг 2: позиционное кодирование
- Шаг 3: небольшой NeRF MLP
- Шаг 4: объёмный рендеринг вдоль луча
- Используйте
- Поставьте
- Упражнения
- Ключевые термины
- Дополнительные материалы
3D-зрение бывает двух видов. Облака точек — необработанный выход датчика. NeRF — обученное объёмное поле. Оба отвечают на вопрос «что и где находится в пространстве».
Тип: Изучите + соберите Языки: Python Предварительные требования: Фаза 4, урок 03 (CNN), фаза 1, урок 12 (Операции с тензорами) Время: ~45 минут
Цели обучения
- Различать явные (облако точек, сетка, воксель) и неявные (поле знакового расстояния, NeRF) 3D-представления и случаи их применения
- Понимать приём PointNet с симметричной функцией, который делает нейронную сеть перестановочно-инвариантной относительно неупорядоченного набора точек
- Прослеживать прямой проход NeRF: трассировка лучей, объёмный рендеринг, позиционное кодирование, MLP-головка плотности и цвета
- Использовать
nerfstudioилиinstant-ngpдля предобученной 3D-реконструкции по небольшому набору изображений с известными позами
Проблема
Камера создаёт 2D-изображение. LIDAR создаёт набор 3D-точек без порядка. Конвейер structure-from-motion создаёт разреженное облако 3D-ключевых точек. NeRF реконструирует целую 3D-сцену по нескольким изображениям с известными позами. Всё это относится к «зрению», но ничто из этого не похоже на плотный тензор, который ожидает CNN.
3D-зрение важно, потому что почти каждая ценная задача робота выполняется в 3D: захват предметов, обход препятствий, навигация, перекрытие объектов в AR, захват 3D-контента. Инженер по компьютерному зрению, который понимает только 2D-изображения, отрезан от наиболее быстро растущей части области (AR/VR-контент, робототехника, стеки автономного вождения, 3D-реконструкция на основе NeRF для недвижимости или строительства).
Два представления доминируют по разным причинам. Облака точек — это то, что датчики дают вам бесплатно. NeRF и их последователи (3D Gaussian splatting, нейронные SDF) — это то, что получается, когда вы просите нейронную сеть изучить сцену.
Концепция
Облака точек
Облако точек — это неупорядоченный набор из N точек в R^3, каждая из которых при необходимости имеет признаки (цвет, интенсивность, нормаль).
cloud = [
(x1, y1, z1, r1, g1, b1),
(x2, y2, z2, r2, g2, b2),
...
(xN, yN, zN, rN, gN, bN),
]
Нет ни сетки, ни связности. Для нейронных сетей это усложняют два свойства:
- Инвариантность к перестановкам — выход не должен зависеть от порядка точек.
- Переменное N — одна модель должна обрабатывать облака разных размеров.
PointNet (Qi et al., 2017) решил обе задачи одной идеей: применить общий MLP к каждой точке, затем агрегировать симметричной функцией (max pool). В результате получается вектор фиксированного размера, не зависящий от порядка.
f(P) = max_{p in P} MLP(p)
Это вся основа PointNet. Более глубокие варианты (PointNet++, Point Transformer) добавляют иерархическую выборку и локальную агрегацию, но приём с симметричной функцией остаётся неизменным.
Архитектура PointNet
«Общий MLP» означает, что один и тот же MLP независимо запускается на каждой точке. Для эффективности он реализуется как свёртка 1x1 по измерению точек.
Поля нейронного излучения (NeRF)
NeRF (Mildenhall et al., 2020) взяли вопрос «можем ли мы реконструировать 3D-сцену по N фотографиям?» и ответили нейронной сетью, которая и является сценой. Сеть отображает (x, y, z, viewing_direction) в (density, colour). Рендеринг нового вида — это цикл трассировки луча по этой сети.
NeRF MLP: (x, y, z, theta, phi) -> (sigma, r, g, b)
To render a pixel (u, v) of a new view:
1. Cast a ray from the camera through pixel (u, v)
2. Sample points along the ray at distances t_1, t_2, ..., t_N
3. Query the MLP at each point
4. Composite the colours weighted by (1 - exp(-sigma * dt))
5. The sum is the rendered pixel colour
Функция потерь сравнивает отрендеренный пиксель с пикселем ground truth на обучающих фотографиях. Обратное распространение через шаг рендеринга обновляет MLP. Нет ни 3D-ground truth, ни явной геометрии — сцена хранится в весах MLP.
Позиционное кодирование в NeRF
Обычный MLP на (x, y, z) не может представлять высокочастотные детали, поскольку MLP спектрально смещены к низким частотам. NeRF исправляет это, кодируя каждую координату в вектор признаков Фурье перед MLP:
gamma(p) = (sin(2^0 pi p), cos(2^0 pi p), sin(2^1 pi p), cos(2^1 pi p), ...)
Используется до L=10 частотных уровней. Это тот же приём, который трансформеры используют для позиций, и он снова появляется в условии времени диффузии (урок 10). Без него NeRF выглядят размытыми.
Объёмный рендеринг
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 — пропускание: сколько света доходит до точки i. (1 - exp(-sigma_i * delta_i)) — непрозрачность в точке i. c_i — цвет. Финальный пиксель — взвешенная сумма вдоль луча.
Чем заменили NeRF
Чистые NeRF медленно обучаются (часами) и медленно рендерят (секунды на изображение). Последующая линия развития:
- Instant-NGP (2022) — hash-grid-кодирование заменяет позиционный вход MLP; обучается за секунды.
- Mip-NeRF 360 — обрабатывает неограниченные сцены и сглаживание.
- 3D Gaussian Splatting (2023) — заменяет объёмное поле миллионами 3D-гауссиан; обучается за минуты, рендерит в реальном времени. Текущий стандарт для production.
Почти каждый реальный NeRF-продукт в 2026 году на самом деле использует 3D Gaussian splatting. Но ментальная модель всё ещё остаётся NeRF.
Наборы данных и бенчмарки
- ShapeNet — классификация и сегментация 3D CAD-моделей как облаков точек.
- ScanNet — реальные сканы помещений для сегментации.
- KITTI — наружные LIDAR-облака точек для автономного вождения.
- NeRF Synthetic / Blended MVS — наборы данных с изображениями в позах для синтеза видов.
- Набор данных Mip-NeRF 360 — неограниченные реальные сцены.
Соберите
Шаг 1: классификатор 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()):,}")
Около 1,6 млн параметров. Работает с 1 024 точками на облако.
Шаг 2: позиционное кодирование
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)")
Умножение на 2^l * pi даёт последовательно более высокие частоты.
Шаг 3: небольшой NeRF MLP
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}")
Она мала по сравнению с оригинальным NeRF (у которого есть 2 MLP-ствола глубины 8). Этого достаточно, чтобы продемонстрировать архитектуру.
Шаг 4: объёмный рендеринг вдоль луча
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}")
Один луч, 64 выборки, компонуются в один RGB-пиксель и значение глубины.
Используйте
Для реальной работы:
nerfstudio(Tancik et al.) — текущая эталонная библиотека для NeRF / Instant-NGP / Gaussian Splatting. Командная строка плюс web viewer.pytorch3d(Meta) — дифференцируемый рендеринг, утилиты для облаков точек, операции с сетками.open3d— обработка облаков точек, регистрация, визуализация.
Для развёртывания 3D Gaussian splatting в значительной степени заменил чистые NeRF, потому что рендерит в 100 раз быстрее. Качество реконструкции сопоставимо.
Поставьте
Этот урок создаёт:
outputs/prompt-3d-task-router.md— промпт, который направляет задачу к правильному 3D-представлению (облако точек, сетка, воксель, NeRF, Gaussian splat) на основе задачи и входных данных.outputs/skill-point-cloud-loader.md— навык, который пишет PyTorchDatasetдля файлов .ply / .pcd / .xyz с корректными нормализацией, центрированием и выборкой точек.
Упражнения
- (Легко) Покажите, что PointNet инвариантен к перестановкам: дважды пропустите через него одно и то же облако, один раз с перемешанными точками. Убедитесь, что выходы идентичны с точностью до шума плавающей точки.
- (Средне) Реализуйте минимальную функцию генерации лучей, которая при заданных внутренних параметрах и позе камеры создаёт начала и направления лучей для каждого пикселя изображения H x W.
- (Сложно) Обучите TinyNeRF на синтетическом наборе отрендеренных видов цветного куба (сгенерированном дифференцируемым рендерингом или простым трассировщиком лучей). Сообщите потерю рендеринга на эпохах 1, 10 и 100. На какой эпохе модель создаёт узнаваемые виды?
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| Облако точек | «3D-точки от LIDAR» | Неупорядоченный набор (x, y, z) + необязательные признаки для каждой точки |
| PointNet | «Первая нейронная сеть для облаков точек» | Общий MLP на точку + симметричный (max) pool; перестановочно-инвариантна по построению |
| NeRF | «MLP, которая является сценой» | Сеть, отображающая (x, y, z, dir) в (плотность, цвет); рендерится трассировкой лучей |
| Позиционное кодирование | «Признаки Фурье» | Кодирует каждую координату в sin/cos на нескольких частотах, чтобы преодолеть низкочастотное смещение MLP |
| Объёмный рендеринг | «Интегрирование по лучу» | Компонуирует выборки вдоль луча в один пиксель с помощью пропускания и alpha |
| Instant-NGP | «Hash-grid NeRF» | Заменяет координатный MLP NeRF многомасштабной hash grid; в 100–1000 раз быстрее |
| 3D Gaussian splatting | «Миллионы гауссиан» | Сцена = набор 3D-гауссиан; рендерится в реальном времени, обучается за минуты |
| SDF | «Поле знакового расстояния» | Функция, возвращающая знаковое расстояние до ближайшей поверхности; ещё одно неявное представление |
Дополнительные материалы
- PointNet (Qi et al., 2017) — перестановочно-инвариантный классификатор
- NeRF (Mildenhall et al., 2020) — статья, сделавшая 3D-реконструкцию по фотографиям задачей нейронной сети
- Instant-NGP (Müller et al., 2022) — hash grid, ускорение в 1000 раз
- 3D Gaussian Splatting (Kerbl et al., 2023) — архитектура, заменившая NeRF в production
Источник: 3D Vision — Point Clouds & NeRFs 04.12 — Понимание видео — временное моделирование · Фаза 04 — Компьютерное зрение · 04.14 — Трансформеры для зрения (ViT) · Полный каталог