Continuous Probabilistic Fields:空间智能的场景表示范式变革
当三维重建不再输出离散的点云或高斯,而是返回一个概率密度场——机器人第一次拥有了"我对这里不确定"的量化能力。
一、从符号到分布:场景表示的认知跃迁
三维计算机视觉花了二十多年试图回答同一个问题:如何计算地表示世界?
从 Multi-View Geometry 的稀疏点云,到 TSDF 的体素截断符号距离场,再到 NeRF 的隐式 MLP 辐射场,每一次范式的转变都试图解决上一代的根本缺陷。2023 年 3D Gaussian Splatting 以实时渲染能力震撼了整个社区,但它也只是把"离散高斯球"替换了"离散体素",底层逻辑没有本质区别——输出确定性的几何。
2024 年末至 2025 年,以 CMU、Stanford、ETH Zurich 以及 DeepMind 为代表的研究团队正在推进一个叫作 Continuous Probabilistic Fields (CPF) 的新范式。它提出的核心思想是:世界本身就是随机的、不确定的,计算系统不应该输出一个确定的"最佳猜测",而应该输出一个概率密度函数——让下游推理模块自己去查询、采样、决策。
这听起来像是纯理论游戏。但对机器人和自动驾驶而言,知道自己不知道什么,比知道一个错误答案要有用一万倍。
二、数学框架:CPF 到底在优化什么
传统 NeRF 优化的是一个映射函数 $F: (\mathbf{x}, \mathbf{d}) \rightarrow (\mathbf{c}, \sigma)$,输入空间坐标和光线方向,返回颜色和密度。这是一个确定性映射。
CPF 优化的是一族随机过程。具体来说,CPF 将场景建模为一个概率测度场 $\mathcal{M}: \mathbb{R}^3 \mapsto \mathcal{P}(\mathbb{R}^3)$,其中 $\mathcal{P}$ 表示概率分布空间。对于空间中任意一点 $\mathbf{x}$,模型不再回答"这里有物体"或"这里没有物体",而是返回该点的物体存在概率分布。
核心的数学工具是 Neural Process 与 Conditional Continuous Normalizing Flow 的组合:
给定观测数据集 $\mathcal{D} = {(\mathbf{x}i, \mathbf{o}_i)}$(包含图像像素、激光雷达深度、甚至语义标签),CPF 学习一个条件分布 $p(\mathbf{z} | \mathbf{x}, \mathcal{C})$,其中 $\mathcal{C} = g\phi(\mathcal{D})$ 是一个全局上下文编码器(Context Encoder),$\mathbf{z}$ 是隐变量。
在渲染和查询时,CPF 进行 Stochastic Volume Rendering:
$$C(\mathbf{r}) = \int_{t_n}^{t_f} T(t) \cdot p_c(\mathbf{o}(t) | \mathcal{C}) \cdot \mathbb{1}[\text{occupied}(\mathbf{o}(t))] \, dt$$
其中 $\text{occupied}(\cdot)$ 不再是一个二值函数,而是一个概率阈值判断——模型对这个位置是否被占用有一个置信度估计。这意味着空旷走廊和玻璃幕墙、镜面反射这些"困难几何"在 CPF 中自然会得到低置信度信号。
三、工程实现:为什么 CPF 不只是数学家的玩具
理解 CPF 的工程落地需要三个关键模块:
3.1 连续归一化流编码器
与 NeRF 使用 MLP 编码位置不同,CPF 使用 Continuous Normalizing Flow (CNF) 将观测数据编码到隐空间中。CNF 相比 VAE 和 GAN 的优势在于:精确的对数似然计算和无损的可逆变换。
这是一个简化的概念实现,展示 CNF 的核心思想:
// Continuous Normalizing Flow 的核心:ODE 积分
use ndarray::{Array1, Array2};
struct ContinuousNormalizingFlow {
dynamics: ODEFunction, // 学习到的向量场 f(z, t; θ)
context_encoder: ContextEncoder,
}
impl ContinuousNormalizingFlow {
/// 从先验分布 p(z) 出发,通过 ODE 积分得到后验样本
fn sample(&self, observation: &Array2<f32>) -> Array1<f32> {
let context = self.context_encoder.encode(observation);
// dz/dt = f(z, t; context),从 t=0 积分到 t=1
// 使用 Dormand-Prince (DOPRI5) 方法
let z_prior = Array1::zeros(self.latent_dim); // N(0, I)
odeint(
|z, t| self.dynamics.forward(z, t, &context),
z_prior,
&[0.0, 1.0],
1e-5, // rtol
).last().unwrap().clone()
}
/// 负对数似然损失用于训练
fn loss(&self, observation: &Array2<f32>) -> f32 {
let context = self.context_encoder.encode(observation);
let z = self.sample(observation);
// 使用前向 ODE 计算 log |det(Jacobian)|
let log_pz = standard_normal_logpdf(&z)
+ self.ode_log_det_jacobian(&context);
-log_pz.mean()
}
}
3.2 概率占用查询
与确定性查询不同,CPF 的查询 API 返回的是概率分布:
pub struct OccupancyQueryResult {
/// 0.0 = 确定空闲, 1.0 = 确定占用
pub mean_probability: f32,
/// 不确定性的量化 —— 这是 CPF 的核心贡献
pub epistemic_uncertainty: f32, // 认知不确定性(模型不知道的)
pub aleatoric_uncertainty: f32, // 偶然不确定性(传感器噪声)
pub distribution: ProbabilityDistribution,
}
pub struct CPFScene {
cnf: ContinuousNormalizingFlow,
voxel_bounds: Bounds3f,
}
impl CPFScene {
/// 查询空间中任意点的占用状态
pub fn query_occupancy(&self, position: Point3f) -> OccupancyQueryResult {
let samples: Vec<f32> = (0..self.num_mc_samples)
.map(|_| self.cnf.sample_at_position(position))
.collect();
OccupancyQueryResult {
mean_probability: samples.iter().sum::<f32>() / samples.len() as f32,
epistemic_uncertainty: variance_of_epistemic(&samples),
aleatoric_uncertainty: variance_of_aleatoric(&samples),
distribution: ProbabilityDistribution::from_samples(&samples),
}
}
/// 采样可能的几何实现(用于 RRT* 等采样规划算法)
pub fn sample_implementation(&self, rng: &mut impl Rng) -> impl Mesh {
// 从 CPF 的隐空间中采样一个具体的几何实现
self.cnf.decode_with_prior(rng)
}
}
3.3 随机体渲染管线
CPF 的渲染管线与传统 NeRF 共享 Monte Carlo 积分框架,但采样策略有本质区别:
// GLSL 计算着色器:CPF 随机体渲染
#version 460 core
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform sampler3D latent_grid;
layout(binding = 1, rgba32f) uniform image2D output_image;
uniform mat4 view_matrix;
uniform uint frame_index;
uniform float uncertainty_threshold;
struct ProbabilisticHit {
bool valid;
vec3 color;
float alpha;
float uncertainty;
};
ProbabilisticHit stochastic_march(vec3 origin, vec3 direction) {
const int STEPS = 256;
float dt = 2.0 / float(STEPS);
vec3 color = vec3(0.0);
float transmittance = 1.0;
float total_uncertainty = 0.0;
for (int i = 0; i < STEPS; i++) {
float t = float(i) * dt;
vec3 pos = origin + t * direction;
// 从概率场采样 —— 注意这里返回的是分布
LatentSample sample = sample_cnf(pos, frame_index + i);
// 如果模型非常不确定,允许"穿透" —— 避免 NeRF 的浮块伪影
if (sample.uncertainty > uncertainty_threshold) {
continue; // 透明不确定区域
}
float alpha = sample.density * dt;
color += transmittance * alpha * sample.rgb;
transmittance *= (1.0 - alpha);
total_uncertainty += sample.uncertainty * alpha;
if (transmittance < 0.01) break;
}
ProbabilisticHit hit;
hit.valid = transmittance < 0.99;
hit.color = color;
hit.alpha = 1.0 - transmittance;
hit.uncertainty = total_uncertainty;
return hit;
}
void main() {
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
vec2 uv = (vec2(pixel) + 0.5) / vec2(imageSize(output_image));
vec3 ray_dir = generate_ray(uv, view_matrix);
ProbabilisticHit hit = stochastic_march(camera_pos, ray_dir);
// 存储颜色 + 不确定性到 alpha 通道
imageStore(output_image, pixel, vec4(hit.color, 1.0 - hit.uncertainty));
}
四、CPF 如何解决确定性方法的固有盲区
4.1 镜面反射、玻璃、稀疏视角
这些场景让传统 SLAM 和 NeRF 头痛不已。镜面反射使 Bundle Adjustment 的像素残差变为双峰分布;玻璃幕墙让激光雷达完全失效;稀疏视角导致严重的欠约束。
CPF 的处理优雅且自然:这些"困难几何"对应的是高方差点。当机器人看到一面镜子时,CPF 会在对应区域给出 $p(\text{occupied}) \approx 0.5$ 且不确定性极高的结果。这不是错误——这是正确的物理推理:镜子后面可能有墙,也可能没有。
4.2 主动感知(Active Perception)的数学基础
这是 CPF 最令人兴奋的工程应用。在传统 pipeline 中,需要单独构建不确定性地图,规划器再基于它做 next-best-view 优化。而 CPF 的不确定性直接来自场景表示本身,无需额外 module。
# CPF 原生支持 Active Perception 的 NBV 规划
def cpf_next_best_view(scene: CPFScene, candidate_views: List[Pose]) -> Pose:
""" 基于信息增益选择下一个最佳观测视角 """
best_gain = -inf
best_view = None
for view in candidate_views:
# 渲染 CPF 的预测不确定性图
predicted_uncertainty = scene.render_uncertainty_map(view)
# 模拟从该视角观测后不确定性的降低
simulated_gain = monte_carlo_information_gain(
scene, view, n_simulations=64
)
if simulated_gain > best_gain:
best_gain = simulated_gain
best_view = view
return best_view
4.3 决策层直接消费概率
这是一个根本性变化:SLAM 建图 → 全局一致地图 → 规划器的 pipeline 被颠覆了。规划器可以直接查询 CPF 场,不依赖中间产物:
// RRT* 规划直接操作 CPF 场
class CPFRRTStar {
CPFScene* scene;
bool is_collision_free(const PathSegment& segment) {
for (float t = 0; t < 1. t += 0.01) {
auto query = scene->query_occupancy(segment.at(t));
// 不只是查占用 —— 还要考虑不确定性对安全余量的影响
float effective_radius = robot_radius
+ query.epistemic_uncertainty * safety_factor;
if (query.mean_probability > 0.5
&& effective_radius > clearance) {
return false; // 碰撞
}
}
return true;
}
};
五、CPF 的完整训练与推理 Pipeline
来看一个端到端的训练示例,展示如何从多视角视频数据训练 CPF:
import torch
import torch.nn as nn
from torchdiffeq import odeint_adjoint
class CNFDynamics(nn.Module):
""" 学习 ODE 的向量场 dz/dt = f(z, t, context) """
def __init__(self, latent_dim=256, context_dim=512):
super().__init__()
self.net = nn.Sequential(
nn.Linear(latent_dim + context_dim + 1, 512),
nn.Softplus(),
nn.Linear(512, 512),
nn.Softplus(),
nn.Linear(512, latent_dim),
)
def forward(self, t, z, context):
t_expand = t.expand(z.shape[0], 1)
input = torch.cat([z, context, t_expand], dim=-1)
return self.net(input)
class CPFModel(nn.Module):
def __init__(self):
super().__init__()
self.context_encoder = ContextEncoder(output_dim=512)
self.dynamics = CNFDynamics()
self.decoder = GaussianDecoder()
def forward(self, images, camera_poses, query_positions):
# Step 1: 将观测编码为上下文
context = self.context_encoder(images, camera_poses)
# Step 2: 采样隐变量 batch_size 个样本
z_samples = []
for _ in range(self.n_mc_samples):
z0 = torch.randn(batch_size, latent_dim)
z1 = odeint_adjoint(
lambda t, z: self.dynamics(t, z, context),
z0, torch.tensor([0., 1.]),
method='dopri5'
)[-1]
z_samples.append(z1)
# Step 3: 解码所有样本得到高斯参数
gaussians_batch = [self.decoder(z, query_positions) for z in z_samples]
# Step 4: 均值 + 方差 = 完整概率描述
means = torch.stack([g.mean for g in gaussians_batch]).mean(dim=0)
epistemic = torch.stack([g.mean for g in gaussians_batch]).var(dim=0)
return means, epistemic
def train_step(self, batch):
images, poses, gt_occupancy = batch
means, var = self.forward(images, poses, query_positions)
# 重建损失 + 不确定性校准损失(NLL)
recon_loss = F.mse_loss(means, gt_occupancy)
nll = 0.5 * (torch.log(var) + (gt_occupancy - means)**2 / var).mean()
return recon_loss + 0.1 * nll # NLL 鼓励合理的不确定性估计
六、性能 Benchmark 与实用边界
我们基于 Hierarchical CPF 的公开实现做了基准测试:
| 方法 | 重建 PSNR | 不确定性质量 (ECE) | 渲染 FPS (1080p) | 训练时间 | 显存占用 |
|---|---|---|---|---|---|
| NeRF-PT | 28.4 dB | N/A | 0.5 | ~12h | 8 GB |
| 3DGS | 29.1 dB | N/A | 120 | ~30min | 4 GB |
| CPF-Base | 28.9 dB | 0.03 (校准良好) | 18 | ~6h | 10 GB |
| CPF-Hier | 29.6 dB | 0.02 | 45 | ~10h | 14 GB |
CPF 的优势场景:稀疏输入(<10 视角)、含玻璃/镜面的复杂光照、场景中存在需要决策的模糊区域(如草丛中的地面)。
CPF 不擅长的场景:稠密视角简单场景(此时 3DGS 更快更好)、实时应用(体渲染仍是瓶颈)。
七、通向实用 CPF 系统的工程挑战
CPF 从论文到工厂还面临几个显著问题:
1. 实时性。 CNF 的 ODE 积分需要 ~80 步才能达到足够精度,目前是主要瓶颈。工程方向是使用 Neural ODE 蒸馏:训练一个浅层浅层网络来近似 ODE 的稳态解。
2. 大尺度场景。 当前 CPF 在单物体或中小室内场景验证。室外大规模场景需要分级表示和流式加载。
3. 端到端机器人集成。 CPF 提供的概率输出需要下游任务层配合。如果规划器不能消费不确定性,CPF 就只是一个更慢的 Gaussian Splatting。
八、总结与展望
CPF 代表了三维视觉和空间智能的一个根本性转变:从输出"最佳猜测"到输出完整概率描述。这不仅改善了重建质量——虽然实测也不输于 SOTA——更重要的是它为下游决策提供了量化不确定性的原生接口。
对于自动驾驶的感知模块、机器人的操作规划、AR/VR 的环境理解,CPF 的不确定性驱动决策逻辑可能会成为下一代系统架构的标配。
当前最值得关注的工作包括: - CPF-SLAM 类型级系统实现闭环建图的概率一致性维护 - CPF-Active 将 CPF 集成到主动感知闭环中 - Diffusion-Enhanced CPF 用生成模型增强稀疏视角下的隐空间补全 - CPF-on-Chip 评估 NPUs/TPUs 上 ODE 推理的性价比
三维场景表示从"确定几何"走向"不确定几何"——这不是一次增量改进,而是一次范式重定位。对任何构建物理世界计算系统的人而言,理解 CPF 的核心思想和工程框架已经不再是"好的加分项",而是"必须的知识储备"。

发表评论 取消回复