一句话总结

AnyRecon 用视频扩散模型将稀疏、无序的多视角图像重建为完整 3D 场景——核心突破是把「生成」与「重建」深度耦合:用显式 3D 几何记忆指导生成,用生成结果反哺重建。

为什么这个问题重要?

随手拍几张照片就能重建出完整 3D 场景,是机器人、AR/VR、数字孪生共同追求的能力。但这件事比听起来难得多。

现有方法的困境:

  • NeRF / 3DGS:需要密集视角(几十到几百张),稀疏情况下严重退化
  • Zero123、One-2-3-45:基于扩散生成,但只能条件化 1-2 帧,大场景泛化差
  • CAT3D、ReconX:改进了多帧条件化,但对视角数量和顺序仍有较强假设

AnyRecon 的核心创新:支持任意数量、任意顺序的稀疏输入,同时保证几何一致性——通过「持久化场景记忆 + 几何感知检索 + 高效稀疏注意力」三件套解决这个问题。

论文链接:AnyRecon: Arbitrary-View 3D Reconstruction with Video Diffusion Model

背景知识

稀疏视角重建的三条路

纯重建路线:NeRF/3DGS ──── 需要密集输入,稀疏时退化严重
生成先验路线:Zero123 ───── 单图→多视角,但帧间几何不一致
生成+重建耦合:AnyRecon ─── 两者互相约束,本文方向

时序压缩:视频扩散用于 3D 的核心障碍

视频扩散模型(如 SVD)把时序帧之间的一致性建模得很好。如果把「时序帧」换成「多视角渲染帧」,模型自然能学到视角间的几何一致性——这是用视频扩散做 3D 的核心动机。

但标准视频模型为了省内存,会在时间维度下采样(如把 16 帧压缩成 4 个 latent 帧)。对自然视频无妨,但对 3D 重建来说,这破坏了像素级帧间对应关系,直接导致几何错位。AnyRecon 的关键修改之一就是移除这个压缩。

核心方法

直觉解释

想象你在博物馆随手拍了 6 张展品照片,角度各异、顺序随机。AnyRecon 的推理流程如下:

稀疏输入(6张) ──→ 建立持久化场景记忆(所有视角入库)
                           ↓
              几何感知检索(目标视角 → 找最相关的 k 个条件帧)
                           ↓
              无时序压缩的视频扩散(生成新视角)
                           ↓
              3D 重建(NeRF/3DGS 拟合生成结果)

三个核心创新

创新 1:持久化捕获视角缓存(Capture View Cache)

所有捕获视角存入全局记忆,在注意力机制中被「前置」为 prefix token,生成任何新视角时都可访问——不再受限于「只能看最近 2 帧」。

创新 2:去除时序压缩

条件帧(捕获视角)不经过时序 VAE 压缩,以帧级别分辨率直接参与注意力计算,保证像素级几何对应。

创新 3:几何感知视角检索

不用全部缓存视角(太多,放不进上下文窗口),而是根据 3D 几何重叠度动态检索最相关的 $k$ 个。

数学细节

设捕获视角集合为 $\mathcal{C} = {(I_i, \mathbf{P}i)}{i=1}^N$,$\mathbf{P}_i \in \mathbb{R}^{4 \times 4}$ 是相机位姿矩阵。

几何相关性得分(用于视角检索):

\[s(q, c) = \lambda_d \cdot \exp\!\left(-\frac{\|\mathbf{t}_q - \mathbf{t}_c\|_2}{\sigma}\right) + \lambda_\theta \cdot \frac{\mathbf{d}_q \cdot \mathbf{d}_c + 1}{2}\]

其中 $\mathbf{t}$ 为相机位置,$\mathbf{d}$ 为相机朝向(Z 轴方向),两项分别衡量空间距离和朝向相似度。

稀疏注意力复杂度(局部窗口 + 全局条件帧):

\[\mathcal{O}\left(N_c \cdot T + T \cdot W\right) \quad \text{vs} \quad \mathcal{O}(T^2) \text{(全注意力)}\]

其中 $N_c$ 是条件帧数,$T$ 是生成帧数,$W$ 是窗口大小。条件帧全局可见,生成帧只看局部窗口。

4 步蒸馏:通过一致性蒸馏将扩散步数从 50 步降至 4 步,推理提速约 10×。

实现

持久化场景记忆

import torch
import numpy as np

class CaptureViewCache:
    """持久化全局场景记忆,存储所有捕获视角"""

    def __init__(self, max_views=64, intrinsics=None):
        self.views = []
        self.max_views = max_views
        # (fx, fy, cx, cy)
        self.K = intrinsics or (500.0, 500.0, 320.0, 240.0)

    def add_view(self, image: torch.Tensor, pose: torch.Tensor,
                 depth: torch.Tensor = None):
        if len(self.views) >= self.max_views:
            self._evict_redundant()
        entry = {'image': image, 'pose': pose}
        if depth is not None:
            entry['points'] = self._unproject(pose, depth)
        self.views.append(entry)

    def _unproject(self, pose: torch.Tensor, depth: torch.Tensor):
        """深度图反投影到世界坐标系点云"""
        H, W = depth.shape
        fx, fy, cx, cy = self.K
        u = torch.arange(W, dtype=torch.float32)
        v = torch.arange(H, dtype=torch.float32)
        uu, vv = torch.meshgrid(u, v, indexing='xy')  # (H, W) 各自

        x_cam = (uu - cx) * depth / fx
        y_cam = (vv - cy) * depth / fy
        # 齐次坐标拼接,变换到世界系
        pts_cam = torch.stack([x_cam, y_cam, depth,
                               torch.ones_like(depth)], dim=-1)  # (H,W,4)
        pts_world = (pose @ pts_cam.reshape(-1, 4).T).T        # (H*W, 4)
        return pts_world[:, :3].reshape(H, W, 3)

    def _evict_redundant(self):
        """简化策略:移除最旧的视角(生产中应换为几何感知淘汰)"""
        self.views.pop(0)

    def __len__(self):
        return len(self.views)

几何感知视角检索

def retrieve_views(query_pose: torch.Tensor,
                   cache: CaptureViewCache,
                   k: int = 4,
                   w_dist: float = 0.5,
                   sigma: float = 2.0) -> list:
    """
    为目标视角检索最相关的 k 个条件帧。
    核心假设:位置近 + 朝向相似 ≈ 3D 重叠度高 ≈ 更好的几何约束。
    """
    t_q = query_pose[:3, 3]   # 查询相机位置
    d_q = query_pose[:3, 2]   # 查询相机朝向(Z轴)

    scored = []
    for idx, view in enumerate(cache.views):
        t_c = view['pose'][:3, 3]
        d_c = view['pose'][:3, 2]

        # 空间距离得分(高斯衰减)
        s_dist = torch.exp(-torch.norm(t_q - t_c) / sigma)
        # 朝向相似度(余弦相似度归一化到 [0, 1])
        s_angle = (d_q @ d_c + 1.0) / 2.0

        score = w_dist * s_dist + (1.0 - w_dist) * s_angle
        scored.append((score.item(), idx))

    scored.sort(reverse=True)
    return [cache.views[idx] for _, idx in scored[:k]]

无时序压缩的稀疏注意力

import torch

class CaptureViewCache:
    """持久化全局场景记忆,存储所有捕获视角"""

    def __init__(self, max_views=64, intrinsics=None):
        self.views = []
        self.max_views = max_views
        self.K = intrinsics or (500.0, 500.0, 320.0, 240.0)  # (fx, fy, cx, cy)

    def add_view(self, image: torch.Tensor, pose: torch.Tensor, depth: torch.Tensor = None):
        if len(self.views) >= self.max_views:
            self.views.pop(0)  # 简化淘汰策略
        entry = {'image': image, 'pose': pose}
        if depth is not None:
            entry['points'] = self._unproject(pose, depth)
        self.views.append(entry)

    def _unproject(self, pose: torch.Tensor, depth: torch.Tensor):
        """深度图反投影到世界坐标系点云"""
        H, W = depth.shape
        fx, fy, cx, cy = self.K
        # ... (像素网格生成代码省略)
        pts_cam = torch.stack([...], dim=-1)           # (H,W,4) 齐次坐标
        pts_world = (pose @ pts_cam.reshape(-1, 4).T).T  # 变换到世界系
        return pts_world[:, :3].reshape(H, W, 3)

端到端 Demo

def demo_anyrecon_pipeline():
    """模拟 AnyRecon 核心推理流程(用随机张量替代真实图像)"""
    torch.manual_seed(42)
    dim, B = 256, 1

    # 1. 构建场景记忆:6 张环绕拍摄的稀疏视角
    cache = CaptureViewCache(max_views=32)
    for i in range(6):
        angle = i * np.pi / 3          # 每60°一张
        pose = torch.eye(4)
        pose[0, 3] = np.cos(angle) * 2  # 绕圆心半径2m
        pose[1, 3] = np.sin(angle) * 2
        pose[2, 3] = 0.5
        cache.add_view(
            image=torch.randn(3, 240, 320),
            pose=pose,
            depth=torch.rand(240, 320) * 3 + 0.5
        )
    print(f"场景记忆:共 {len(cache)} 个捕获视角")

    # 2. 目标视角:45° 方向(介于已有视角之间)
    query_pose = torch.eye(4)
    query_pose[0, 3] = np.cos(np.pi / 4) * 2
    query_pose[1, 3] = np.sin(np.pi / 4) * 2

    # 3. 几何感知检索
    relevant = retrieve_views(query_pose, cache, k=4)
    print(f"检索到 {len(relevant)} 个相关条件帧")

    # 4. 稀疏注意力推理
    sparse_attn = GeometryAwareSparseAttention(dim=dim)
    gen_frames  = torch.randn(B, 8, dim)   # 8 个目标帧
    cond_frames = torch.randn(B, 4, dim)   # 4 个条件帧
    output = sparse_attn(gen_frames, cond_frames)

    print(f"输出形状: {output.shape}")    # → (1, 8, 256)
    return output

demo_anyrecon_pipeline()

3D 可视化:视角分布与检索结果

class GeometryAwareSparseAttention(nn.Module):
    # 条件帧全局可见;生成帧仅关注局部窗口 ±W/2,复杂度 O(T·W)
    def __init__(self, dim, num_heads=8, window_size=8):
        super().__init__()
        self.W = window_size
        self.attn = nn.MultiheadAttention(dim, num_heads, batch_first=True)

    def forward(self, gen_frames, cond_frames):
        # gen_frames: (B, T, D)  cond_frames: (B, N, D)
        outputs = []
        for t in range(gen_frames.shape[1]):
            local = gen_frames[:, max(0, t-self.W//2):min(gen_frames.shape[1], t+self.W//2+1), :]
            kv = torch.cat([cond_frames, local], dim=1)  # 条件帧全局可见 → 跨距离几何约束
            out, _ = self.attn(gen_frames[:, t:t+1, :], kv, kv)
            outputs.append(out)
        return torch.cat(outputs, dim=1)  # (B, T, D)

预期效果:6 个蓝色箭头呈圆形排布,系统检索命中的 4 个最近视角变为红色,绿色星号标记目标生成位置。这张图是调试 AnyRecon pipeline 时最实用的工具。

实验

数据集说明

数据集 场景规模 视角数 重点测试
RealEstate10K 大型室内 100+ 长轨迹泛化
CO3D 单物体 50-100 多类别重建
DTU 小型物体 49/64 定量基准
Tanks & Temples 大型户外 数百 场景级别

RealEstate10K 是核心训练集,覆盖长轨迹和大视差变化,正好对应「任意视角」的泛化需求。

定量评估(论文报告的对比结果)

方法 PSNR ↑ SSIM ↑ LPIPS ↓ 最大条件帧数
Zero123++ 19.8 0.71 0.28 1
CAT3D 22.1 0.76 0.22 3
ReconX 23.0 0.78 0.20 2
AnyRecon 24.6 0.82 0.17 任意

关键点:其他方法受固定条件帧数限制,增加输入无法带来质量提升;AnyRecon 随输入视角增加,指标持续改善。

工程实践

几何感知视角淘汰(替换简单 LRU)

大场景下缓存不能无限增长,但朴素 LRU 会丢弃覆盖远处区域的早期视角。

def demo_anyrecon_pipeline():
    torch.manual_seed(42)
    dim, B = 256, 1

    # 1. 构建场景记忆:6 张环绕稀疏视角
    cache = CaptureViewCache(max_views=32)
    for i in range(6):
        angle = i * np.pi / 3
        pose = torch.eye(4)
        pose[0, 3], pose[1, 3] = np.cos(angle) * 2, np.sin(angle) * 2
        cache.add_view(image=torch.randn(3, 240, 320), pose=pose, depth=torch.rand(240, 320) * 3 + 0.5)

    # 2. 目标视角(45°,介于已有视角之间)
    query_pose = torch.eye(4)
    query_pose[0, 3], query_pose[1, 3] = np.cos(np.pi / 4) * 2, np.sin(np.pi / 4) * 2

    # 3. 几何感知检索
    relevant = retrieve_views(query_pose, cache, k=4)

    # 4. 稀疏注意力推理
    sparse_attn = GeometryAwareSparseAttention(dim=dim)
    output = sparse_attn(torch.randn(B, 8, dim), torch.randn(B, 4, dim))  # (生成帧, 条件帧)

    return output  # → (1, 8, 256)

无序输入的预排序

AnyRecon 支持无序输入,但贪心地按最大覆盖度排序后,几何记忆构建更稳定:

def sort_by_coverage(views: list) -> list:
    """贪心排序:每次选与已选视角集合平均距离最大的下一帧"""
    if not views:
        return views
    sorted_v, remaining = [views[0]], views[1:]
    while remaining:
        last_pos = sorted_v[-1]['pose'][:3, 3]
        dists = [torch.norm(v['pose'][:3, 3] - last_pos).item()
                 for v in remaining]
        best = int(np.argmax(dists))
        sorted_v.append(remaining.pop(best))
    return sorted_v

常见坑

坑 1:相机坐标系不统一

COLMAP 输出 OpenCV 约定(Z 轴朝前),OpenGL(Z 轴朝后),两者混用会让整个几何记忆乱掉:

# OpenGL 位姿 → OpenCV 位姿(翻转 Y 和 Z 轴)
def opengl_to_opencv(pose: torch.Tensor) -> torch.Tensor:
    flip = torch.diag(torch.tensor([1., -1., -1., 1.]))
    return pose @ flip

坑 2:稀疏视角下单目深度不可靠

几何感知检索依赖 3D 几何记忆,但稀疏输入时单目深度误差大(尤其是无纹理区域)。优先用多视角三角化的稀疏点云;实在不行,降低 $\lambda_\theta$ 权重,回退到纯空间距离检索。

坑 3:4 步蒸馏在大视角跨度下失效

视角变化超过 120° 时,4 步蒸馏模型偶尔产生几何伪影。此时需回退到完整 DDIM 采样(50 步),速度换质量,是个实际部署中需要根据场景调参的超参数。

什么时候用 / 不用?

适用场景 不适用场景
随手拍的稀疏照片(5-20 张) 需要实时重建(推理较慢)
静态场景、光照稳定 动态物体(人、树叶)
大视角跨度的宽基线采集 极端光照变化(高反光、HDR)
室内 / 小型户外场景 超大规模城市级场景
核心需求是新视角合成 只需要网格(用 MVS 更快更准)

与其他方法对比

方法 优点 缺点 适用场景
NeRF / 3DGS 几何精确,无幻觉 需密集输入,无泛化 受控密集采集
Zero123++ 单图驱动,泛化强 几何不一致,细节差 单物体快速草图
CAT3D 条件帧数固定,速度快 不支持任意输入 固定配置规范采集
AnyRecon 任意视角数和顺序,可扩展 推理慢,依赖位姿估计 野外稀疏随意拍摄

我的观点

AnyRecon 解决的问题方向是正确的:世界上大多数照片都是稀疏、无序、随意拍的,让重建系统适应真实的拍摄习惯,而不是强迫用户按规范采集,这才是走向实用化的路。

三个技术点里,「几何与生成的双向耦合」是最有意思的洞察。以前的方法要么纯生成(不管几何),要么纯重建(不借助生成先验),两者分离。AnyRecon 让生成结果反哺几何记忆、几何记忆再指导生成,形成正反馈——这个设计思路值得借鉴。

离实际应用还有多远?

主要障碍是速度。即使 4 步蒸馏,大场景推理时间仍以分钟计,无法满足实时 AR/VR。另一个门槛是位姿依赖——方法假设已知精确相机位姿,但「随手拍」的照片还得先跑 COLMAP,这在移动端是实实在在的工程负担。

值得关注的开放问题:

  1. 动态场景:场景记忆假设静态,运动物体会在生成结果中留下幻影
  2. 端到端位姿估计:把相机位姿估计融进 pipeline,才能做到真正「拍了就用」
  3. 更激进的蒸馏:1-2 步扩散是否可行?一致性模型(Consistency Models)的进展值得跟踪

这个方向的未来形态可能是:手机 App 随手拍几张,30 秒内在云端生成完整 3D 资产。AnyRecon 是朝这个方向走出的扎实一步,但还有相当长的路要走。