PyTorch3D 点云光栅化完全指南:rasterize_points 参数、原理与实战
【免费下载链接】pytorch3dPyTorch3D is FAIR's library of reusable components for deep learning with 3D data项目地址: https://gitcode.com/gh_mirrors/py/pytorch3d
导读
本文围绕 PyTorch3D 渲染管线中的核心算子pytorch3d.renderer.points.rasterize_points展开,系统讲解点云光栅化的输入约定、五个核心参数、三类返回张量以及"朴素—分箱(coarse-to-fine)—纯 Python"三种实现路径。读完本文,你将能独立完成任意点云的光栅化调用、正确设置半径与points_per_pixel、处理非方形图像的高宽比,并理解其可微反向传播与上层PointsRasterizer/PointsRenderer的组装方式。
一、模块定位:点云渲染管线中的光栅化环节
rasterize_points是 PyTorch3D 点云渲染三阶段管线中的核心阶段。以 pytorch3d/renderer/points/renderer.py 中的PointsRenderer为例,一次完整的点云渲染被拆解为:
- 变换(transform):由
PointsRasterizer.transform将世界坐标点变换到 NDC 空间(见 pytorch3d/renderer/points/rasterizer.py); - 光栅化(rasterize):调用
rasterize_points把每个点绘制为屏幕上的圆盘,并为每个像素找出最近的 K 个点,输出idx / zbuf / dists三类片段(fragments)数据; - 合成(composite):
AlphaCompositor或NormWeightedCompositor依据距离权重混合颜色(见 pytorch3d/renderer/points/compositor.py 与底层实现 pytorch3d/renderer/compositing.py)。
模块的公开 API 在 pytorch3d/renderer/points/init.py 中统一导出,本文关联文档 docs/modules/renderer/points/rasterize_points.rst 即通过automodule自动提取rasterize_points.py的完整文档字符串。
PyTorch3D 点云光栅化到非方形矩形图像(1024x512)的渲染结果
二、函数签名与核心参数
rasterize_points定义于 pytorch3d/renderer/points/rasterize_points.py,签名如下:
def rasterize_points( pointclouds, image_size: Union[int, List[int], Tuple[int, int]] = 256, radius: Union[float, List, Tuple, torch.Tensor] = 0.01, points_per_pixel: int = 8, bin_size: Optional[int] = None, max_points_per_bin: Optional[int] = None, ):每个点云会被光栅化到一张独立图像上:当image_size为整数时输出(image_size, image_size)的方形图像,为(H, W)元组时输出(H, W)的矩形图像。
2.1pointclouds:输入的点云批次
- 类型为
Pointclouds对象(见 pytorch3d/structures/pointclouds.py),代表一批 N 个点云; - 批内每个点云可以有不同数量的点,点的坐标为
(x, y, z); - 坐标必须位于NDC(归一化设备坐标)空间,即
[-1, 1]^3,相机位于原点(0, 0, 0); - 相机坐标系约定:x 轴从右指向左、y 轴从下指向上、z 轴从后指向前(即常见的"右/上/后向观察者"约定)。
函数内部通过pointclouds.points_packed()、cloud_to_packed_first_idx()与num_points_per_cloud()将异构点云摊平为紧凑(packed)张量参与计算(rasterize_points.py)。
2.2image_size:输出图像尺寸与高宽比
- 支持整数(方形)或
(H, W)元组(非方形); - 非方形图像涉及两个高宽比:像素高宽比与输出图像高宽比。光栅化器假设像素为正方形,但允许图像本身为矩形;
- 文档明确建议:多数场景下应将相机的像素高宽比设为
1.0,仅通过image_size调整输出图像尺寸; - NDC 范围在非方形图像下会被缩放以保持高宽比:较小维度保持
[-1, 1],另一维度按H:W比例缩放。例如(H, W) = (64, 128)时,高度 NDC 范围为[-1, 1],宽度范围为[-2, 2]。像素到 NDC 的换算由 pytorch3d/renderer/mesh/rasterize_meshes.py 的pix_to_non_square_ndc实现,CUDA 内核中的PixToNonSquareNdc与之对应(pytorch3d/csrc/rasterize_points/rasterization_utils.cuh); - 在
H != W时,代码使用max_image_size = max(H, W)推导bin_size,以适配分箱光栅化器中"每维度 bin 数"的约束(rasterize_points.py)。
2.3radius:圆盘半径(NDC 单位)
- 默认
0.01,即每个点被光栅化为 NDC 空间下半径为0.01的圆盘; - 支持逐点半径:传入 shape 为
(N, P)的torch.Tensor,其中 N 为批大小、P 为每个点云的填充后点数,即可为批内每个点指定不同半径; - 内部经
_format_radius(rasterize_points.py)统一格式化为 shape(P_packed,)的紧凑张量:float 被广播为全同值张量,list/tuple被转为张量,(N, P_padded)张量则通过padded_to_packed_idx()索引映射为 packed 形式;形状不符时抛出ValueError; - 该参数直接影响渲染的"软边界"权重:
PointsRenderer.forward中权重定义为1 - dists2 / (r * r)(renderer.py),用于软化硬决策边界、保证可微性。
2.4points_per_pixel:每像素保留的点数
- 默认
8,即每个像素沿 z 轴保留最近的 K 个点,并按 z 升序排列; - 该值即返回张量的第 4 维长度
K; - 内核中硬上限为
kMaxPointsPerPixel = 150(pytorch3d/csrc/rasterize_points/rasterization_utils.cuh),超出会报错 "Must have points_per_pixel <= 150"(rasterize_points.cu)。
2.5bin_size与max_points_per_bin:coarse-to-fine 分箱
bin_size=0:使用朴素光栅化(naive),逐像素遍历该点云的全部点;bin_size=None:启发式自动选择。CPU 上强制回退为0("Binned CPU rasterization not fully implemented",分箱 CPU 光栅化未完整实现);CUDA 上取2 ** max(ceil(log2(max_image_size)) - 4, 4)(rasterize_points.py);bin_size > 0:启用分箱粗光栅化(_C._rasterize_points_coarse),先把每个点按空间位置分配到 bin 网格,再由细光栅化内核只检查本 bin 内的点,不改变输出值,只影响前向速度;- 分箱模式下每维 bin 数为
1 + (max_image_size - 1) // bin_size,受全局上限kMaxPointsPerBin = 22约束(rasterize_points.py),过小会抛出"bin_size too small, number of points per bin must be less than 22"(对应测试见 tests/test_rasterize_points.py); max_points_per_bin:仅分箱模式生效,表示每个 bin 允许的最大点数,不影响输出值,只影响前向内存占用;默认取max(10000, _P / 5)(rasterize_points.py)。
三、返回值:idx / zbuf / dists2 三元组
函数返回(idx, zbuf, dists2)三元素元组,image_size为(H, W)时三个张量的空间维度均为(N, H, W, ...):
| 返回张量 | 形状 | 含义 |
|---|---|---|
idx | (N, H, W, points_per_pixel),int32 | 每个像素处最近点的索引,按 z 升序。idx[n, y, x, k] = p表示 packed 点points[p]是像素(y, x)沿 z 方向第 k 近的点;未被points_per_pixel个点覆盖的像素用-1填充 |
zbuf | (N, H, W, points_per_pixel),float32 | 对应点的z 坐标,排序方式与idx一致;未覆盖处填充-1 |
dists2 | (N, H, W, points_per_pixel),float32 | 像素中心与该点在x/y 平面的欧氏距离平方(NDC 单位);未覆盖处填充-1 |
3.1 可微性与自定义 autograd Function
_RasterizePoints继承torch.autograd.Function(rasterize_points.py):
- 前向:调用
_C.rasterize_points,通过ctx.save_for_backward(points, idx)保存输入,并用ctx.mark_non_differentiable(idx)标记idx不可微; - 反向:调用
_C.rasterize_points_backward,仅对zbuf与dists2的梯度回传到点坐标; - CUDA 反向内核按
(N, H, W, K)逐像素并行,对grad_points使用atomicAdd累加(rasterize_points.cu),因此该算子被标记为alertNotDeterministic(非确定性)。
3.2 三种实现路径与一致性保证
| 实现 | 入口 | 适用平台 | 说明 |
|---|---|---|---|
| 朴素 CUDA/CPU | rasterize_points(..., bin_size=0) | GPU / CPU | 每个像素一个线程(CUDA),遍历全部点 |
| 分箱 CUDA | rasterize_points(..., bin_size>0) | GPU | 先粗分箱后细光栅化,加速不改变结果 |
| 纯 Python | rasterize_points_python | CPU | 教学/对照用,三层 for 循环逐像素检查 |
纯 Python 版本rasterize_points_python(rasterize_points.py)逻辑完全对应朴素内核:对每个点云、每行每列像素,跳过pz < 0(相机后的点),计算dist2 < radius2则插入 top-k 列表并排序截断。测试 tests/test_rasterize_points.py 的test_cpp_vs_naive_vs_binned对 CPU 朴素 / CUDA 朴素 / CUDA 分箱三条路径做了严格的输出与梯度一致性校验(idx、zbuf完全相等,dists与梯度误差在1e-6/5e-6量级内)。
四、结合源码看内核实现
4.1 像素判点逻辑CheckPixelInsidePoint
朴素与细光栅化内核共享同一设备端判定函数(rasterize_points.cu):
- 读取点的
(px, py, pz)与该点半径p_radius,计算radius2 = p_radius²; - 若
pz < 0直接返回——不渲染相机后方的点; - 计算像素中心与点的平面距离平方
dist2 = dx² + dy²,当dist2 < radius2时该点覆盖此像素; - 用数组维护最多 K 个
Pix{z, idx, dist2},当满 K 个后若新点 z 更小则覆盖当前最大 z 的槽位并重新扫描最大值——由于K <= 150且通常很小,这是比 STL 堆更轻量、低发散的选择; - 最终用
BubbleSort按 z 升序输出。
4.2 细光栅化与分箱协同
RasterizePointsFineCudaKernel(rasterize_points.cu)从粗光栅化产出的bin_points(shape(N, BH, BW, T),-1为哨兵值)中只读取当前 bin 的点,从而把每个像素的候选点数从"全点云"降到"本 bin 内点数"。像素 id 的排布让相邻线程落入同一 bin,以获得合并访存(coalesced memory reads)。粗光栅化的 CPU 参考实现见 pytorch3d/csrc/rasterize_points/rasterize_points_cpu.cpp,其中同样对超过max_points_per_bin的点进行了截断处理。
4.3 坐标轴翻转约定
CUDA 内核在计算输出索引时对 x/y 做了翻转(yi = H - 1 - pix_idx / W、xi = W - 1 - pix_idx % W,见 rasterize_points.cu),这是因为相机坐标假设 +Y 向上、+X 向左,而图像行索引自上而下——与纯 Python 实现中yfix = H - 1 - yi的翻转逻辑一致。
五、上层封装:PointsRasterizationSettings 与 PointsRasterizer
rasterize_points通常不直接调用,而是通过高层封装使用:
from pytorch3d.renderer import ( PointsRasterizationSettings, PointsRasterizer, PointsRenderer, AlphaCompositor, PerspectiveCameras, ) from pytorch3d.structures import Pointclouds raster_settings = PointsRasterizationSettings( image_size=512, radius=0.01, points_per_pixel=8, bin_size=None, # 启发式;0 为朴素模式 max_points_per_bin=None, ) rasterizer = PointsRasterizer(cameras=cameras, raster_settings=raster_settings) renderer = PointsRenderer(rasterizer=rasterizer, compositor=AlphaCompositor()) images = renderer(pointclouds)PointsRasterizationSettings(rasterizer.py)是以 dataclass 形式存储的参数字典,五个字段与rasterize_points一一对应,默认值完全一致(image_size=256, radius=0.01, points_per_pixel=8, bin_size=None, max_points_per_bin=None);PointsRasterizer(rasterizer.py)的forward先经transform用相机把世界坐标变换到 NDC(保留 view 空间的 z 值),再展开raster_settings调用rasterize_points,返回PointFragments(idx, zbuf, dists)具名元组;相机既可在初始化时传入,也可在前向时以cameras=关键字覆盖;PointsRenderer依据radius将dists2转为[0,1]的软权重,交由AlphaCompositor(alpha 合成,cum_alpha_k = alpha_k * prod_{l<k}(1 - alpha_l))或NormWeightedCompositor(归一化加权和)完成着色,两个合成器均支持background_color背景填充(compositor.py)。
六、验证与参考
- 正确性测试:tests/test_rasterize_points.py 覆盖简单用例、相机后点剔除、变半径、三条实现路径一致性、
bin_size过小报错等场景; - 非方形图像测试:tests/test_rasterize_rectangle_images.py 中的
TestRasterizeRectangleImagesPointclouds对比方形与非方形图像下rasterize_points的片段输出与梯度; - 顶层 API 导出:pytorch3d/renderer/points/init.py;
- 文档索引:docs/modules/renderer/points/index.rst 收录了本文关联的 rasterize_points.rst 及其配套的 rasterizer、renderer、compositor 文档。
七、注意事项小结
- 输入必须是 NDC 空间坐标:世界坐标需先经相机变换,直接传入原始世界坐标会得到错误结果;
- CPU 上不要依赖分箱:
bin_size=None在 CPU 上会退化为朴素模式,加速只在 CUDA 上生效; bin_size与图像尺寸联动:图像很大而bin_size过小时会触发kMaxPointsPerBin=22的报错,可按报错提示增大bin_size;points_per_pixel有 150 的上限:需要更多候选点时需自行调整策略(如分多次光栅化);- 矩形图像注意高宽比:像素保持正方形假设,图像高宽比由 NDC 范围缩放吸收,相机侧请保持像素高宽比为 1.0;
- 反向传播非确定性:CUDA 反向使用
atomicAdd累加梯度,多次运行梯度可能略有差异,追求完全确定性时需注意此限制。
【免费下载链接】pytorch3dPyTorch3D is FAIR's library of reusable components for deep learning with 3D data项目地址: https://gitcode.com/gh_mirrors/py/pytorch3d
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考