1. 为什么"实时"和"动态"这两个词,把门槛抬高了不止十倍
1.1 先说实话:静态重建早就"内卷"了
我在这个方向上折腾了挺长时间,一开始接触的就是静态场景重建。那时候觉得这玩意儿真不算难:一台深度相机,绕着房间扫一圈,把所有深度帧拼到一个统一坐标系里,用TSDF体素把表面融合一下,最后走一遍marching cubes拉出网格,一个像模像样的三维模型就出来了。
这一套流程成熟到什么程度?很多开源工程随便改改就能跑,单张显卡就能达到每秒几十帧的处理速度。因为它本质上是在做一个"对齐+平均"的工作:相机位姿可以通过ICP或者特征匹配稳定估计,场景表面是刚性的,不随时间变化,所有的观测都可以放心地累积到同一个体素网格里,噪声也会在多次观测中互相抵消。所以我一度以为,三维重建这个领域已经没有太多新鲜东西可以挖了。
直到我开始做实时动态三维重建,才意识到自己之前的判断有多天真。
动态重建面对的完全不是"静态重建换了一个输入源"那么简单。场景里有人在走动,衣服在飘,手在比划,表情在变化——表面的几何每时每刻都在变。你不能再把新一帧深度图直接往一个全局坐标系里叠,因为叠加的前提是"同一个物理点在不同帧里位置不变",这个前提在动态场景下根本不成立。你得先把场景的运动估计出来,再把观测数据转换到某个统一的参考状态,然后才谈得上融合。
而且,一旦加上"实时"这个约束,问题就更麻烦了。实时动态三维重建要求的是:传感器以每秒30帧甚至更高的速度采集数据,算法必须在下一个数据帧到来之前完成这一帧的处理,同时还要保证重建结果能够持续地、平滑地更新。这句话写出来就两行,做起来你会发现处处是瓶颈。
1.2 "实时"到底意味着什么:帧率、延迟和算力账
我先给不熟悉的读者补一下背景。所谓实时,不是"跑得快一点"的意思,而是系统要在一个固定的时间预算内完成所有处理,这个预算通常由传感器帧周期决定。
深度相机一般按30fps工作,一帧的时间预算大约是33毫秒。也就是说,从深度图进入显存,到点云生成、位姿估计、变形场求解、体素融合、表面提取,这一个完整链路必须压缩在33毫秒里完成。如果你还想做到60fps,那预算直接砍半。如果应用是AR/VR或者远程操控,用户能感知到的"实时"要求更高。人眼对交互延迟的容忍上限大概在50毫秒左右,超过这个阈值,你转一下头、屏幕上重建出来的人物明显跟不上,立刻会有强烈的晕眩感。
算力账也要算清楚。一个标准的640x480深度图大概30万个点,做一次全场景的非刚性配准,要建立几万对对应关系,再迭代求解一个高维变形场。而一个体积化的动态场景,如果分辨率开到512的三次方格网,光存储一个float类型的TSDF值就是512MB,再加上颜色、权重、变形参数,显存轻松突破2GB。这还没算你把多帧历史数据放进缓存队列的量。
所以"实时"和"动态"放在一起,真正考验的是对整个系统每一毫秒、每一MB显存的规划能力。这不是一个单点算法问题,而是一个系统工程问题。
1.3 我判断一个系统算不算"实时"的土办法
学术论文里经常写"达到了实时性能",但我在实际项目里从来不直接信这个说法。我有自己的一套判断标准,非常简单粗暴:
第一,看源生帧率。传感器多少帧进来,算法就能多少帧出去,这是硬指标。如果一个系统在离线跑的时候每秒能处理10帧,但传感器是30帧输入,那它就谈不上实时,充其量是"快速离线处理"。
第二,看端到端延迟。我习惯在手持深度相机前面放一个秒表或者快速挥动手掌,观察屏幕上重建结果跟着变化的时间差。如果这个时间差超过两帧,说明系统里有隐藏的缓冲或者排队延迟,这在实时系统里是致命的。
第三,看最坏帧而非平均帧。动态场景里偶尔会来一帧特别复杂的输入,比如手部快速晃动导致深度大面积丢失、或者两片表面突然贴合在一起。这种情况下处理耗时可能飙升到几百毫秒。很多系统平均帧率很好看,但最坏帧直接卡死,体验一塌糊涂。我评估系统时,专门造这种"恶劣帧"去压它。
这套标准帮我挡掉了不少看起来很美的方案。
2. 动态场景里最刁钻的三个难点:运动、遮挡与拓扑突变
2.1 运动解耦:先把"自己动"和"被拍的人动"分开
动态三维重建第一个要解决的问题,是运动解耦。
你拿着深度相机拍一个正在跳舞的人,相机的位姿在变,人的姿态也在变。最终重建出来的模型应该是人的三维几何,而相机的运动只是观测视角的变化。这两个东西如果混在一起,整个重建就会发飘。
静态重建里,相机位姿可以用配准算法稳定解出来,因为场景表面是刚性的,帧与帧之间存在一个统一的刚体变换。动态场景不行:你没有办法用一个全局的变换矩阵把上一帧的整个深度图变换到当前帧,因为场景里只有一部分是"静"的,另一部分在"动"。如果你强行做全局ICP,系统会被平均出一个不伦不类的位姿。
常规做法是分层处理:先估计相机自身的刚性运动,再估计场景的非刚性变形。但这两个量是耦合的——你不知道场景变形,就很难精确估计相机运动;反过来也一样。这就是所谓的"鸡生蛋"问题。
我实际用过的一个策略是"先刚后柔":先用静态背景部分(比如地面、墙面)做刚性位姿估计,因为背景通常占比大且稳定;然后把相机位姿固定住,再对前景区域做非刚性变形求解。如果场景里背景很少,比如贴着人脸拍,那就比较麻烦,需要引入惯性传感器或者更多的先验信息来辅助运动分解。
2.2 遮挡与未观测区域:被挡住的部分怎么办
动态场景里遮挡问题比静态场景严重得多。人转个身,后背刚被融合进模型里,转回来又被身体自己挡住;一只手从脸前挥过,后面一大块区域瞬间从传感器视野里消失。
TSDF这类体积融合方法处理遮挡的方式是"等待观测覆盖"。被遮挡的区域暂时保留旧数据,等遮挡物移开、表面重新可见之后,再用新的深度观测去修正。这个策略在动态场景下有一个致命弱点:如果场景本身在变形,旧数据对应的表面已经变了位置,你等它"重见天日"的时候,它已经不再代表当前的真实几何。
我踩过的坑是:让一个穿着宽松衣服的人转身,他的衣服下摆在转身过程中会飘动,而背后区域的旧TSDF值没有及时清除,导致重建出来的表面出现"虚影"——好像衣服后面还拖着一片半透明的残留。
后来我采用的策略是:给每个体素加一个"置信度衰减"机制。如果某个体素被连续多帧深度观测判为"被遮挡"或者"与当前观测相差过大",它的权重会快速下降,而不是永续保留。这样当遮挡解除后,新观测可以更快地覆盖旧数据,虚影问题明显缓解。
2.3 拓扑变化:最"反实时"的难题
如果说运动和遮挡还算"难啃的硬骨头",那拓扑变化就是"整个方法体系都要换"级别的难题。
拓扑变化指的是场景中表面连接关系发生变化的情况:两根手指本来是分开的,捏在一起变成一根;两块面团揉成一团;一件衣服从桌上被拿起来,从"平铺"变成"悬挂"。这些操作都涉及表面拓扑结构的改变。
传统的动态重建方法多数建立在"局部变形场"的假设之上。我前面提到的方法——把场景表面划分成若干变形节点,每个节点带一个刚体变换,然后通过非刚性配准求解这些变换——这个方案的隐含前提是:整个表面的连接关系在变形过程中保持不变,你只改变节点位置,不改变节点之间的连接。这在处理人的手臂抬举、躯干旋转时非常好用,因为人体表面拓扑确实基本不变。但一旦遇到拓扑变化,变形场就失效了:两根手指合并的表面,无法通过"移动节点"来自然融合,因为表面网格的邻接关系不允许这样的融合。
处理拓扑变化最可靠的办法,其实是回到体素化表示:把表面离散到空间网格里,每个格子独立更新,表面连接关系由marching cubes实时重新生成,这样拓扑就不再是显式的约束,而是隐式的——格子更新了,表面自然跟着变。但体素网格的分辨率会限制你能表达的最小拓扑细节,比如两根手指之间的缝隙,如果网格分辨率不够细,缝隙就会被"焊死"。分辨率调上去,内存和算力又扛不住。这是一个绕不开的取舍。
3. 从静态到动态的技术路线演变:我重新理解了选型逻辑
3.1 深度相机+体素融合路线的上限与破解
先回顾一下我最早接触的那条路线:深度相机 + 全局TSDF体素融合。它之所以在动态场景下失效,核心原因是它假设"所有观测都在同一个静止坐标系里累加"。
后来出现的动态扩展思路,本质上是在这条老路线上加了一个"变形模块":把场景用一组稀疏节点包裹起来,每个节点有自己的变换,先把历史模型的所有节点变形到当前帧的姿态,再用当前帧的深度观测去更新体素。这样既保留了TSDF融合的平滑去噪能力,又能表达非刚性运动。我印象中这套思路在早期动态重建工作中效果相当惊艳,一台普通的深度相机,就能实时重建出一个人体的动态曲面,还能让衣服褶皱跟着动作变化。
这条路线的问题我之前说过:它在本质上假设表面拓扑不变,遇到手部抓取、物体分离合并这类情况就容易翻车。另外,它对快速运动非常敏感,因为变形场求解依赖相邻帧之间的对应关系,运动一快,对应关系就错,变形场就会发生局部扭曲。
但不可否认,它是目前工程落地最成熟的一条路。很多实时交互类应用,比如虚拟试衣、健身姿态演示、远程全息通信,核心算法框架仍然是这一套。它要求的算力不高,延迟可控,成熟代码也多。
3.2 隐式神经表示:质量天花板高,实时性地板低
近几年火起来的神经辐射场(NeRF)和它的动态变体,把重建质量推到了一个前所未有的高度。它能从一组多视角照片里重建出细腻的、带真实光照反射的完整表面,甚至可以捕捉头发丝、半透明物体这类传统方法几乎无解的场景。
但我在实际项目中评估过动态NeRF的实时性,结论很残酷:它完全不适合作为"实时动态三维重建"的在线算法。动态NeRF通常需要你先采集一段多视角视频,花几十分钟到几小时训练出一个隐式场,然后在新视角下生成渲染图。单帧渲染速度即便做了优化,也往往需要几十到几百毫秒,离33毫秒的实时预算还差一个数量级。
当然,有一种折中的用法:把NeRF当作离线"精修器"。系统先用实时方案快速生成一个粗糙的动态网格,下来再把这个网格作为输入,训练一个NeRF来补全细节、修正光照。这样一来,实时部分负责"看得见、跟得上",离线部分负责"看得细、渲染美"。我在一个模拟项目中用过这个组合拳,效果很好,就是工程链路长了不少。
3.3 三维高斯泼溅:实时渲染和可编辑性的新平衡点
三维高斯泼溅(3D Gaussian Splatting)是这两年绕不开的一个名词。它用一堆具有位置、协方差、颜色、不透明度的高斯核来显式地表示三维场景,好处是渲染速度极快,在训练完成之后可以做到实时的高质量渲染,而且每个高斯核是显式的、可编辑的,你可以单独移动、删除、旋转某一个高斯核,这让它在动态场景表达上有天然优势。
在动态场景里,我的实践做法是这样:场景被表示成一组基础高斯核,然后额外维护一个随时间变化的变形场——用一个小型网络或者一组空间节点,把每个基础高斯的坐标映射到当前时刻的位置。这样你可以用一套保持不变的高斯集合表达一个动态变化的场景,而且渲染速度仍然很快。
不过要泼一盆冷水:3DGS的训练过程远没有达到"一帧数据进,模型立刻更新"的程度。目前的实时应用多是"离线训练加在线渲染",或者"快速初始化一个粗略模型、随后用滑动窗口持续微调"。真要做到端到端的在线学习,显存和优化器的开销仍然是个大头。而且3DGS做几何重建不只是表面提取,它表达的是一个"可渲染的场",你从高斯核里提取连续网格,还得额外走一步密集化或表面提取的算法。
3.4 我的选型建议:按应用场景排优先级
说了这么多,我整理一下自己挑选技术路线的方法。先说结论:选型不是选"最先进",而是选"约束条件下最合适"。
- 如果你的场景是人体或者动物这类拓扑基本不变的动态对象,应用场景偏交互(比如虚拟试衣、远程协作、手势交互),我强烈建议优先考虑深度相机+变形场+体素融合路线。它是目前实时性最稳、工程成熟度最高、硬件要求最亲民的选择。
- 如果你的应用是离线制作高质量动态数字资产,不要求实时,但要求视觉细节炸裂,那动态NeRF或者3DGS+变形场是更好的选择,它的质量上限高得多。
- 如果你需要"实时感知+离线精修"兼顾,最稳妥的做法是分层架构:实时层用变形场+体素融合保证交互体验,离线层用3DGS或NeRF做细节增强,两层之间通过时间戳对齐。
这条思路我后来用了很多次,几乎成了我面对动态重建项目的标准解题框架。
4. 搭一个能跑的实时动态重建系统:我的步骤与调参记录
4.1 硬件与采集端:传感器选择和同步问题
先聊硬件。实时动态重建对传感器的要求比静态重建苛刻得多。
我这边主要用两类深度相机做过测试:结构光方案和ToF方案。结构光的优势是近距离精度高,深度数据在1米以内相当细腻,比如拍摄手部细节时,指缝间的微小深度差都能分辨出来。缺点是它对环境光敏感,阳光直射或者强灯照射下深度会大面积丢失。ToF方案的优势是帧率高、抗环境光能力相对强、远距离表现好,但近距离误差偏大,接收到的深度噪声在边缘处尤其明显。双目立体方案我也试过,成本低、不怕红外干扰,但需要丰富的纹理特征,纯色墙面或者深色衣服上很容易失配。
这些差异直接影响实时重建的上限。比如你要拍手部精细动作,我会优先选结构光;如果你要拍人全身运动,ToF的远距离表现更合适。
同步问题比选型更隐蔽。深度相机通常还配一个RGB相机,两个镜头之间存在视差和帧时间差。静态重建里这个偏差影响不大,就是你贴图的纹理有点"偏移",不细看看不出来。动态场景里,一个移动的手在RGB和深度图里可能差出几厘米甚至十几厘米的位置。你用RGB图做纹理,用深度图做几何,两者一结合,物体边缘全是彩色"镶边"。
我的解决办法是:选择硬件上支持深度图和RGB图时间戳对齐的传感器;如果不能对齐,就在驱动层做软件同步,用缓冲队列把最近时间戳的RGB和深度图配对。这一步放在整个系统的最前面,后面所有算法都依赖于这一对数据的空间和时间一致性。
4.2 前端:深度图预处理与相机位姿估计
拿到深度图之后的第一步,不是直接做重建,而是做数据清理。
深度图里的噪声通常有两种:一是离散的飞点,主要表现为孤立像素上出现过远或者过近的深度值;二是边缘区域的测量退化,物体边缘的深度值经常是背景和前景的混合。我一般先用一个简单的邻域统计滤波把飞点去掉,再用双边滤波保边缘平滑。这里要注意,常规的高斯平滑会磨掉手指这种细小结构的几何细节,双边滤波相对友好。
位姿估计这块,前面提到过"先刚后柔"的思路。对于背景里的静态结构,我用基于点云的ICP——把当前帧的深度点云与模型中的静态部分配准,解出6自由度相机位姿。这里有一个工程小技巧:在每帧配准之前,用一个运动概率场把"可能属于动态区域"的像素点先剔除掉,只用背景点做ICP,能极大提升位姿估计的稳定性。运动概率场怎么来?简单做法是计算当前帧深度图和上一帧变形后的预测深度图之间的残差,残差大的像素标记为动态候选。
4.3 中间层:场景表示与变形场的建立
这一层是动态重建的核心,也是最容易出问题的部分。
我的场景表示选择了稀疏体素哈希+局部TSDF。也就是说,把空间划分成一个个小的体素块,用一个哈希表维护"表面上附近"的体素块,而不是为整个空间都分配显存。这样即便重建体积比较大,也能把显存压在可控范围内。
变形场我用的是稀疏节点图。把场景表面覆盖到一个图结构上,每个节点带一个三维位置和一个小范围的刚体变换。表面上的任意一个点在变形时可以从它的邻近节点插值得到变换。这有点像给模型蒙了一层"顶点动画的代理骨骼",只不过节点不是人为预定义的,而是从深度数据里自动生成的。
每一帧的处理流程是这样的:
- 用上一帧的变形场,把当前估计的模型表面变形到当前帧的预期姿态;
- 把当前帧深度图转成点云,和变形后的模型做非刚性ICP,求解每个节点的变换更新;
- 更新完节点的变换后,再把模型表面全部变换到当前帧的姿态,同时用当前帧深度观测更新TSDF体素;
- 检查哪些区域出现了新的几何(比如原来没扫到的范围被拍到了),在这些区域自动补充新的变形节点。
这个过程里最耗时的步骤是非刚性ICP。它要求建立一个由几万对对应点组成的优化问题,正则化项保证相邻节点的变换不会差异太大,最后用一个稀疏线性求解器解出来。我实测下来,这一步在CPU上做大概要15到25毫秒,偶尔会到30毫秒以上,是整个系统的性能瓶颈。后来我用了一个分层求解策略:先把所有节点分块,每块内部先粗解一次,再用粗解结果初始化全局求解,整体耗时能压到12毫秒左右。
4.4 后端:网格提取与渲染
对大部分应用来说,光有体素和变形节点还不够,用户要的是能看的网格。这一步我用两个出口:一个走实时渲染管道,一个用于导出。
实时渲染管道里,我用光线投射直接从TSDF体素里生成深度图和法线图,再做着色渲染。这样就不必每帧都提取完整网格——光线投射只沿着射线找表面点,速度非常快,几毫秒就能得到一张平滑的深度渲染图。
离线导出时,才走marching cubes把体素场转化成三角网格。因为动态场景每一帧网格都在变,所以导出的网格也带时间戳,可以按帧号记录成序列,便于后续做动画编辑或者重光照。
我记录过一组实测数据。分辨率开512x512深度输入、重建体积约2米见方、表面附近体素块大约8000个,整条链路在单张中高端显卡上的耗时分布大致是这样的:深度预处理2毫秒、位姿估计3毫秒、非刚性ICP 12毫秒、TSDF更新5毫秒、光线投射渲染3毫秒,合计25毫秒左右,正好把一个30fps的实时预算花完。这个数据在不同场景里会上下浮动,但大体可以给你一个心理预期:在这个配置下,实时动态重建是能跑起来的,但余量不大。
5. 工程落地中踩过的坑与实测感悟
5.1 多深度相机的互干扰问题
为了减少遮挡,我一度在系统里同时挂了两台深度相机,从两个角度拍同一个动态场景。结果一开机,两个相机的深度图像都出现了大规模的条纹状噪声,尤其是两台设备的空间距离比较近、视野重叠较多的时候,几乎没法用。
原因是结构光和ToF设备都对外发射主动红外光,两台设备波长相近,接收端分不清自己发射回来的还是对方发射过来的信号。静态重建里你还能通过标定避开这个时间段,动态场景实时在线,没法做交替采集。
解决的办法有两个:一是选支持"动态调制"的ToF传感器,这类设备可以在不同通道上以不同的调制频率运行,互相干扰可以大幅降低;二是物理上把两台设备的探测距离错开,让发射功率和接收增益互为补充。如果条件都不允许,另一个笨办法就是接受遮挡,用单机方案+多视角运动补偿去补全。
5.2 快速运动的"残影"和深度洞
动态场景里最怕出现的就是"人唰地一下抬手"这种动作。画面里手和脸重叠的部分,深度传感器很容易把前景和背景的深度值混淆在一片区域里,产生一种类似运动模糊的"深度洞"。
运动导致的深度洞有几类成因。一类是传感器曝光时间内对象的位移导致深度测量混叠,这在你把曝光时间调大以适配暗光环境时特别明显。另一类是对象离传感器太近,超过了设备的最近测距范围,比如你拍手的时候手离镜头只有十几厘米,结构光方案基本就要爆了。
我的建议是把曝光时间尽量往低压,优先保证帧率和深度干净度,牺牲一点暗光环境下的完整性。要知道动态场景重建最忌讳的就是"花一帧时间去测准一个深度,结果物体早就不在那个位置了"。实时性永远优先于单帧质量。
对应深度洞,我用的是时间和空间两个方向上的补洞策略。空间上用一个快速深度补全网络把空洞粗略填上,时间上则让旧帧的变形模型"承包"空洞区域——如果当前帧某个像素没测到深度,就用上一帧模型变形过来的预测深度顶上。
5.3 变形场在极端动作下的"翻转"
这是我在动态重建项目里被折磨得最惨的一个问题。
当人的手臂快速从身体一侧挥到另一侧,或者一个人在做下蹲起身的循环动作时,非刚性ICP求解容易陷入局部极小值——变形场里某几个节点完全反了方向,导致模型出现类似"肌肉反向扭曲"的不自然形状。这种错误一旦出现在关键部位,就会在体素融合阶段被当成真实观测累积进去,之后好几帧都恢复不过来。
我排查后发现,根因是初始对应关系给得太差。非刚性ICP本质上依赖"上一帧和这一帧的点离得足够近"这个前提,当运动速度超过一定阈值,上一帧的手部节点和这一帧的手部点云已经不在一个局部搜索半径里了,配准自然失败。
我的解决方案是分层变形估计:先在一组非常稀疏的大节点上做粗配准,这些节点覆盖的范围大,即便局部特征错配也能大致抓住全局运动方向;然后用粗配准的结果初始化密集小节点的配准。这个由粗到细的过程把快速运动下的失败率降低了很多,代价是多花3到5毫秒的求解时间,我觉得完全值得。
5.4 暗环境、黑色物体与玻璃:深度传感器的"天敌"
最后聊一个很多论文里很少提、但实际项目一定会遇到的问题:真实场景里总有传感器拍不到的东西。
纯黑衣服、深色头发在结构光和红外ToF传感器眼里几乎是"隐形"的,因为它们吸收了大部分红外光,回传信号太弱,深度值直接归零或者乱跳。很多演示里穿黑T恤的人,重建出来的躯干是一个不规则的凹洞。解决办法很粗暴但有效:换装,或者在重建目标选择上避开纯黑材质;如果必须拍黑衣服,我用过一种办法,就是在衣服上贴一些反光标记点,人为给传感器创造可观测特征。
玻璃和透明物体则是另一个层次的问题。深度传感器测到的玻璃表面深度通常是穿过去的——它打到玻璃面上透了过去,反射给传感器的信号是玻璃后面的墙壁。这让重建结果里玻璃所在的位置出现一个"错误的深坑",而且视角切换时这个坑还会飘。对于实时交互应用,我的建议是直接做个语义掩码,把透明区域从重建范围里抠掉,不做这个区域的几何重建。
6. 最后再分享一点我这边的体会
做了这么多动态重建的实践,我最大的感触是:实时动态三维重建的体验核心,不在于"重建出一张完美的静态网格",而在于"过程即反馈"。用户挥一下手,重建结果立刻跟着变化,那种即时的、自然的互动感,才是这个技术方向区别于离线重建的真正价值所在。
所以我后来在团队做项目评审时,定的第一条验收标准永远不是模型有多精细,而是延迟有多低、交互跟不跟手。只要跟手,粗糙一点用户能接受;一旦延迟上来,Rebuild得再精细,用户也会晕。
另外我想强调一点:不要让工具和算法决定你对场景的理解。很多人一上来就想跑最新最花的算法,结果被硬件同步、显存爆掉、遮挡处理这些底层问题困住了几个月。真正做实时系统,基本功比想象力重要得多。先把深度图的时间对齐、飞点滤波、传感器选型这些"脏活累活"整明白,再谈花式算法,才是正路。
如果你也打算从零开始搞一个实时动态重建的小项目,我的建议是:别一上来就挑战人全身、多遮挡、复杂拓扑这种高难度场景。先从单手臂、单物体这种简单约束的demo做起,等整个链路稳定了,再一步步放开约束。这样你踩的每一个坑都能定位清楚,修复起来也有据可循。
最后给大家一个具体的起步配置参考:一台支持深度和彩色时间戳对齐的ToF深度相机、一张显存不低于8GB的显卡、一个1.5米见方的重建空间。空间里只放一个静态桌子加一个动的人体上半身模型,先把30fps跑通,再逐渐加入动态背景、多目标、大范围变形的场景。这条路,值得慢慢走。