اگر در دنیای یادگیری ماشین فقط با تصاویر دوبعدی کار کردهاید، ورود به فضای سهبعدی چالش متفاوتی برایتان به همراه دارد. دادههای خام حسگرها مثل لیدار (LIDAR) شبکهبندی منظمی ندارند و مجموعهای نامرتب از نقاط در فضا هستند که شبکههای عصبی پیچشی (CNN) استاندارد روی آنها کار نمیکنند. از سوی دیگر، بازسازی دقیق یک صحنهی واقعی از روی چند زاویهی عکس نیازمند بازنمایی پیوسته و هوشمندانهای از حجم و نور است.
این درس به سراغ دو پایهی اصلی بینایی سهبعدی مدرن میرود: بازنمایی صریح با ابرهای نقاط و مدل کلاسیک PointNet، و بازنمایی ضمنی با میدانهای تابش عصبی (NeRF). یادگیری این ساختارها برای توسعه در حوزههای پرتقاضایی مانند رباتیک، رانندگی خودران، واقعیت افزوده (AR/VR) و بازسازی دیجیتالی فضاها کاملاً ضروری است.
برای اجرای کدهای پایه و پیادهسازی گامبهگام PointNet و یک TinyNeRF سبک، پردازندههای معمولی (CPU) یا تراشههای شخصی کافی هستند و نیازی به کارتهای گرافیک سازمانی ندارید؛ هرچند برای آموزش مدلهای پیشرفتهتر مانند Gaussian Splatting دسترسی به کارت گرافیک قدرتمند روند کار را تسریع میکند.
بینایی سهبعدی به دو شکل اصلی ظاهر میشود: ابرهای نقاط که خروجی خام سنسورها هستند، و نرفها (NeRFs) که میدانهای حجمی یادگرفتهشده به حساب میآیند. هر دو به این پرسش پاسخ میدهند: «چه چیزی در کجای فضا قرار دارد؟»
اهداف یادگیری
- تشخیص بازنماییهای صریح (ابر نقاط، مِش، واکسل) و ضمنی (میدان فاصله علامتدار، نرف) در فضای سهبعدی و زمان کاربرد هرکدام
- درک ترفند تابع متقارن در PointNet که باعث ناوردایی نسبت به جایگشت (permutation-invariant) در یک شبکه عصبی روی مجموعهای نامرتب از نقاط میشود
- ردیابی یک گذر رو به جلو (forward pass) در NeRF: پرتاب پرتو (ray casting)، رندرینگ حجمی (volumetric rendering)، کدگذاری مکانی (positional encoding)، و سر MLP برای چگالی و رنگ
- استفاده از
nerfstudioیاinstant-ngpبرای بازسازی سهبعدی با مدلهای ازپیشآموزشدیده بر اساس مجموعهی کوچکی از تصاویرِ دارای زاویه و موقعیت مشخص (posed images)
صورت مسئله
یک دوربین، تصویر دوبعدی تولید میکند. یک لیدار (LIDAR) مجموعهای از نقاط سهبعدی بدون هیچ ترتیبی تحویل میدهد. پایپلاین ساختار حاصل از حرکت (structure-from-motion) یک ابر تنک از نقاط کلیدی سهبعدی میسازد. یک مدل NeRF کل صحنهی سهبعدی را از چند تصویر دارای موقعیت مکانی بازسازی میکند. همهی اینها مصداق «بینایی» هستند، اما هیچکدام شبیه تنسور چگالی که یک شبکه عصبی پیچشی (CNN) میخواهد، نیستند.
بینایی سهبعدی اهمیت بالایی دارد؛ زیرا تقریباً تمام وظایف پرارزش رباتیک در فضای سهبعدی اجرا میشوند: گرفتن اجسام (grasping)، اجتناب از موانع، ناوبری، پوشیدگی در واقعیت افزوده (AR occlusion) و ثبت محتوای سهبعدی. مهندس بینایی ماشینی که فقط تصاویر دوبعدی را بشناسد، از سریعترین بخش در حال رشد این حوزه (محتوای AR/VR، رباتیک، استکهای رانندگی خودران، و بازسازی سهبعدی مبتنی بر NeRF برای املاک یا ساختوساز) جا میماند.
این دو بازنمایی به دلایل متفاوتی غالب شدهاند. ابرهای نقاط خروجی آمادهای هستند که سنسورها بیدردسر تحویلتان میدهند. نرفها و جانشینان آنها (پاشش گاوسی سهبعدی یا 3D Gaussian splatting و میدانهای فاصله علامتدار عصبی یا neural SDFs) چیزی هستند که از آموزش یک شبکه عصبی برای یادگیری یک صحنه به دست میآورید.
مفاهیم پایه
ابرهای نقاط
ابر نقاط مجموعهای نامرتب از نقطه در است که هر نقطه میتواند به صورت اختیاری ویژگیهایی (رنگ، شدت نور، بردار نرمال) هم داشته باشد.
cloud = [
(x1, y1, z1, r1, g1, b1),
(x2, y2, z2, r2, g2, b2),
...
(xN, yN, zN, rN, gN, bN),
]
اینجا نه خبری از شبکهبندی منظم (grid) است و نه اتصالات هندسی. دو ویژگی، کار را برای شبکههای عصبی سخت میکنند:
- ناوردایی نسبت به جایگشت (Permutation invariance) — خروجی نباید به ترتیب نقاط وابسته باشد.
- اندازهی متغیر N — یک مدل واحد باید بتواند ابرهایی با اندازههای مختلف را پردازش کند.
مدل PointNet (کی و همکاران، 2017) هر دو مشکل را با یک ایده حل کرد: اعمال یک پرسپترون چندلایه اشتراکی (shared MLP) روی تکتک نقاط، و سپس تجمیع آنها با یک تابع متقارن (max pool). نتیجه، یک بردار با اندازهی ثابت است که به ترتیب نقاط بستگی ندارد.
کل هستهی اصلی PointNet همین است. نسخههای پیشرفتهتر (مثل PointNet++ و Point Transformer) نمونهبرداری سلسلهمراتبی و تجمیع محلی را اضافه میکنند، اما ترفند تابع متقارن در آنها دستنخورده باقی میماند.
معماری PointNet
منظور از «MLP اشتراکی» این است که همان پرسپترون چندلایه به صورت مستقل روی هر نقطه اجرا میشود. برای بهرهوری محاسباتی، این کار به صورت کانولوشن 1x1 روی بُعد نقاط پیادهسازی میشود.
میدانهای تابش عصبی (NeRFs)
مدلهای نرف (میلدنهال و همکاران، 2020) پرسش «آیا میتوانیم یک صحنه سهبعدی را از 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
یک تابع زیان (loss)، پیکسل رندرشده را با پیکسل واقعی در عکسهای آموزشی مقایسه میکند. پسانتشار از طریق مرحلهی رندرینگ، وزنهای MLP را بهروزرسانی میکند. در این روش هیچ دادهی مبنای سهبعدی (3D ground truth) یا هندسهی صریحی در کار نیست؛ کل صحنه در وزنهای MLP ذخیره میشود.
کدگذاری مکانی در NeRF
یک MLP معمولی روی مختصات (x, y, z) نمیتواند جزئیات با فرکانس بالا را بازنمایی کند، چون پرسپترونهای چندلایه سوگیری طیفی (spectral bias) به سمت فرکانسهای پایین دارند. NeRF این مشکل را با کدگذاری هر مختصات در یک بردار ویژگی فوریه، پیش از ورود به MLP حل میکند:
این فرایند تا سطح فرکانسی انجام میشود. این ترفند دقیقاً همان چیزی است که ترنسفورمرها برای موقعیتها استفاده میکنند و در شرطیسازی زمانیِ مدلهای دیفیوژن (درس 10) هم دوباره دیده میشود. بدون این کار، خروجی NeRF تار خواهد بود.
رندرینگ حجمی
پارامتر T_i همان ضریب عبور نور (transmittance) است؛ یعنی چه مقدار نور به نقطهی میرسد. عبارت (1 - exp(-sigma_i * delta_i)) میزان مات بودن (opacity) در نقطهی را نشان میدهد و c_i همان رنگ است. پیکسل نهایی، حاصل مجموع وزنیِ مقادیر در طول مسیر پرتو است.
چه چیزی جایگزین NeRF شد؟
آموزش نرفهای خالص کند است (ساعتها زمان میبرد) و رندر آنها هم طول میکشد (چند ثانیه برای هر تصویر). سیر تکاملی این معماری از آن زمان:
- Instant-NGP (سال 2022) — کدگذاری شبکهی هش (hash-grid encoding) جایگزین ورودی موقعیت به MLP میشود؛ زمان آموزش به چند ثانیه میرسد.
- Mip-NeRF 360 — صحنههای نامحدود را مدیریت کرده و با الیاسینگ (تداخل فرکانسی) مقابله میکند.
- 3D Gaussian Splatting (سال 2023) — میدان حجمی را با میلیونها گاوسی سهبعدی جایگزین میکند؛ آموزش در چند دقیقه تمام میشود و رندرینگ بیدرنگ (real-time) است؛ انتخاب پیشفرض فعلی در محیطهای پروداکشن.
تقریباً هر محصول واقعی مبتنی بر NeRF در سال 2026 در واقع از پاشش گاوسی سهبعدی استفاده میکند؛ هرچند مدل ذهنی همچنان همان NeRF است.
دیتاستها و بنچمارکها
- ShapeNet — دستهبندی و بخشبندی مدلهای CAD سهبعدی به صورت ابر نقاط.
- ScanNet — اسکنهای واقعی از فضاهای داخلی برای بخشبندی.
- KITTI — ابرهای نقاط لیدار در محیطهای بیرونی برای رانندگی خودران.
- 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) — ترانهادهشده برای 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: مدل کوچک Tiny 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(تانسیک و همکاران) — کتابخانهی مرجع فعلی برای NeRF / Instant-NGP / Gaussian Splatting، همراه با رابط خط فرمان و نمایشگر تحت وب.pytorch3d(توسعهدادهشده توسط Meta) — رندرینگ دیفرانسیلپذیر، ابزارهای کار با ابر نقاط، و عملیات روی مِش.open3d— پردازش ابر نقاط، انطباق (registration) و مصورسازی.
در محیط پروداکشن، پاشش گاوسی سهبعدی تا حد زیادی جای نرفهای خالص را گرفته است؛ چون 100 برابر سریعتر رندر میشود و کیفیت بازسازی صحنه همسطح آن است.
خروجی این درس
این درس خروجیهای زیر را میسازد:
outputs/prompt-3d-task-router.md— یک پرامپت که بر اساس تسک و نوع دادهی ورودی، کار را به بازنمایی سهبعدی مناسب (ابر نقاط، مِش، واکسل، نرف، یا پاشش گاوسی) هدایت میکند.outputs/skill-point-cloud-loader.md— مهارتی که یکDatasetدر PyTorch برای فایلهای ply. / .pcd / .xyz همراه با نرمالسازی صحیح، مرکزدهی و نمونهبرداری نقاط مینویسد.
تمرینها
- (ساده) نشان دهید PointNet نسبت به جایگشت ناوردا است: یک ابر نقاط را دو بار به مدل بدهید؛ بار دوم بعد از بههمریختن تصادفیِ ترتیب نقاط. تأیید کنید که خروجیها (به جز نویز ناچیز محاسبات ممیز شناور) کاملاً یکسان هستند.
- (متوسط) یک تابع حداقلی برای تولید پرتو پیاده کنید که با گرفتن مشخصات داخلی دوربین (intrinsics) و موقعیت و زاویهی آن (pose)، مبدأ و جهت پرتو را برای تکتک پیکسلهای یک تصویر H x W تولید کند.
- (سخت) یک مدل TinyNeRF را روی یک دیتاست ساختگی از تصاویر رندرشدهی یک مکعب رنگی آموزش دهید (این تصاویر را با رندرینگ دیفرانسیلپذیر یا یک پرتوپیمای ساده بسازید). تابع زیان (loss) رندرینگ را در ایپاکهای 1، 10 و 100 گزارش کنید. مدل از چه ایپاکی تصاویر قابل تشخیصی تولید میکند؟
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | معنای فنی و دقیق |
|---|---|---|
| ابر نقاط (Point cloud) | «نقاط سهبعدی لیدار» | مجموعهای نامرتب از مختصات (x, y, z) + ویژگیهای اختیاری برای هر نقطه |
| پوینتنت (PointNet) | «اولین شبکه عصبی روی ابر نقاط» | یک MLP اشتراکی برای هر نقطه + تجمیع متقارن (بیشینه)؛ ناوردا نسبت به جایگشت از پایه و بر اساس ساختار |
| نرف (NeRF) | «یک MLP که کل صحنه است» | شبکهای که (x, y, z, dir) را به (چگالی، رنگ) نگاشت میکند؛ رندر با پرتاب پرتو انجام میشود |
| کدگذاری مکانی (Positional encoding) | «ویژگیهای فوریه» | کدگذاری هر مختصات به سینوس/کسینوس در فرکانسهای متعدد برای غلبه بر سوگیری فرکانس پایین MLP |
| رندرینگ حجمی (Volumetric rendering) | «انتگرالگیری در طول پرتو» | ترکیب نمونههای گرفتهشده در امتداد یک پرتو در یک پیکسل واحد با استفاده از ضریب عبور نور و آلفا |
| اینستنت انجیپی (Instant-NGP) | «نرف مبتنی بر شبکهی هش» | جایگزینی MLP مختصات در NeRF با یک شبکهی هش چندرزولوشنی؛ 100 تا 1000 برابر سریعتر |
| پاشش گاوسی سهبعدی (3D Gaussian splatting) | «میلیونها گاوسی» | صحنه = مجموعهای از توابع گاوسی سهبعدی؛ رندر بیدرنگ و آموزش در چند دقیقه |
| اسدیاف (SDF) | «میدان فاصله علامتدار» | تابعی که فاصله علامتدار تا نزدیکترین سطح را برمیگرداند؛ نوع دیگری از بازنمایی ضمنی |
برای مطالعهی بیشتر
- PointNet (کی و همکاران، 2017) — دستهبند ناوردا نسبت به جایگشت
- NeRF (میلدنهال و همکاران، 2020) — مقالهای که بازسازی سهبعدی از روی عکس را به مسئلهای در شبکههای عصبی تبدیل کرد
- Instant-NGP (مولر و همکاران، 2022) — شبکههای هش و افزایش سرعت تا 1000 برابر
- 3D Gaussian Splatting (کربل و همکاران، 2023) — معماری جایگزین نرفها در سیستمهای عملیاتی و پروداکشن
