无监督学习如何从二维图像集合重建三维点云:原理、损失函数与实战技巧 1. 从二维图像集合到三维点云的底层逻辑第一次看到“从图像集合重建三维点云”这个方向时我脑子里冒出的第一个疑问是为什么非得用无监督学习毕竟在三维视觉领域有监督的方法已经能借助合成数据集比如大量带标注的CAD模型渲染图取得相当不错的精度。但真正上手做过几个项目之后我才理解这个问题的核心痛点——标注数据太贵了。你想想要训练一个三维重建网络如果走全监督路线你需要为每一张输入图像准备对应的三维真值也就是每个像素对应的空间坐标或者体素占用情况。这种数据在真实场景里几乎不可能大规模获取。激光扫描仪能扫出点云但设备成本高、采集流程繁琐而且扫描得到的点云和普通RGB图像之间还存在标定和对齐的问题。所以当论文标题里出现“无监督学习”四个字时它真正想解决的是只用一组普通的二维照片不依赖任何三维标注让网络自己学会推断场景的三维结构。这个任务在学术上通常被归类为“基于多视图几何的三维重建”与“自监督/无监督表示学习”的交叉地带。它的输入是一个图像集合——注意不是视频序列也不是经过精确标定的多相机阵列而是一堆从不同角度拍摄的、可能连相机参数都不知道的散乱图片。输出则是这些图片所描述场景的三维点云。点云可以理解为空间中一堆离散的、带有坐标的三维点每个点代表物体表面上的一个采样位置。和网格模型相比点云没有面片连接信息更轻量也更适合作为后续处理比如分类、分割、配准的中间表示。那么无监督学习在这里到底“无”的是什么严格来说它不需要人工标注的三维真值但通常需要利用图像之间的几何一致性作为监督信号。举个例子同一场景的两张不同视角图片它们对应的三维点投影到各自图像平面后应该满足极线约束或者光度一致性。网络如果重建得准确那么把重建出的三维点重新投影回任意视角渲染出的颜色应该和原始图像很接近。这种“可微渲染重投影误差”就构成了自监督信号。所以更准确的说法是“自监督”或“无三维标注监督”但论文标题里用了“无监督学习”这个更宽泛的术语我们也不必过于纠结字眼。这个方向适合谁呢如果你正在做三维重建、SLAM、神经渲染或者点云处理相关的项目但苦于没有带标注的数据那这套思路非常值得参考。即便你只是对“如何用深度学习从多张照片里恢复三维结构”感到好奇理解它的核心机制也能帮你建立对三维视觉任务的基本直觉。接下来我会从论文的核心设计出发拆解它的网络架构、损失函数、训练策略并补充我在复现过程中踩过的坑和总结的技巧。2. 无监督三维重建的网络架构拆解2.1 整体流程从图像集合到点云的三步走这篇论文的架构可以概括为三个主要阶段特征提取与匹配、深度与位姿联合估计、点云生成与优化。虽然不同论文的具体实现有差异但核心思想是相通的。我以最典型的无监督多视图重建框架为例来说明。第一阶段输入一个图像集合通常先用一个共享权重的卷积网络比如ResNet的前几层提取每张图片的特征图。这些特征图会被用来计算图像之间的匹配代价。如果相机位姿未知还需要同时估计每张图片的相机参数外参和内参。这一步的难点在于没有三维真值怎么知道估计的位姿是对的答案还是几何一致性——如果位姿估计准确那么根据估计的深度图将一张图片的像素投影到另一张图片时应该能找到颜色相近的对应点。第二阶段网络会为每张图片预测一张深度图。深度图是一张和原图尺寸相同的单通道图像每个像素值代表该点距离相机的远近。有了深度图和相机位姿就可以把每个像素反投影到三维空间形成一张局部的点云。把所有图片的局部点云根据估计的位姿变换到同一个世界坐标系下就得到了全局点云。第三阶段是优化。由于初始预测的深度图和位姿都有误差直接融合的点云会非常散乱。所以需要设计损失函数来联合优化深度网络和位姿估计。常见的损失包括光度一致性损失、深度平滑损失、点云倒角距离Chamfer Distance等。其中光度一致性是核心它衡量的是“把一张图扭曲到另一张图的视角后像素颜色差异有多大”。2.2 为什么选择隐式表示而不是直接回归点坐标很多早期的三维重建方法会直接让网络回归每个点的三维坐标比如用PointNet之类的架构。但这类方法有个致命问题点云是无序的。同一组点换一个排列顺序对网络来说就是完全不同的输出。虽然倒角距离等损失函数对顺序不敏感但训练起来仍然很不稳定。这篇论文的思路更偏向于基于深度图的显式表示。也就是说网络不直接输出点云而是输出每张图片的深度图再通过几何投影生成点云。这样做的好处有几个第一深度图是规则网格可以用标准的卷积网络处理训练更稳定第二深度图天然携带了每个像素的对应关系方便计算重投影误差第三通过多视图融合可以逐步剔除离群点得到更干净的点云。我实测下来这种“先深度后点云”的路线在无监督设定下确实比直接回归点坐标要稳得多。直接回归点坐标的方案在训练初期很容易陷入所有点都塌缩到空间某一个小区域的退化解。而深度图方案因为有光度一致性约束网络至少能学到一些有意义的表面结构。2.3 相机位姿估计的耦合问题无监督重建里最棘手的问题之一是深度和位姿是耦合的。如果深度估计偏大位姿估计也偏大两者相乘后投影误差可能仍然很小但实际的三维结构完全错了。这就是所谓的“尺度模糊”。在单目无监督深度估计中这个问题尤其严重因为单张图片无法确定绝对尺度。论文通常采用两种策略来缓解一是假设相机内参已知只估计外参旋转和平移这样至少能固定一部分自由度二是引入额外的约束比如深度图的平滑性先验、或者利用多视图之间的三角化关系来提供尺度信息。我在复现时发现如果完全不给任何尺度约束网络确实会收敛到一个“看起来重投影误差很小但点云尺度完全不对”的解。后来我加了一个简单的深度归一化步骤——每轮迭代后把预测的深度图除以当前batch的中值深度——训练就稳定多了。注意尺度模糊是无监督三维重建的固有难题。如果你的应用场景需要绝对尺度比如测量物体实际尺寸那无监督方法可能不够需要引入IMU、双目基线或者已知尺寸的参照物。3. 损失函数设计无监督信号从哪来3.1 光度一致性损失核心中的核心光度一致性损失的基本思想非常直观假设场景是朗伯体即表面反射不随视角变化那么同一个三维点在不同视角下的颜色应该是一样的。基于这个假设如果我把图像A的某个像素根据预测的深度和位姿投影到图像B的对应位置那么两个像素的颜色值应该接近。具体计算时通常会采样一个局部patch而不是单个像素因为单像素对噪声太敏感。比如取3x3或5x5的窗口计算归一化互相关NCC或者绝对差之和SAD。论文里常用的是SSIM结构相似性和L1损失的组合因为SSIM对亮度变化更鲁棒。这里有个实操细节遮挡区域的处理。如果图像A的某个像素在图像B中被遮挡了那强行计算光度一致性就会引入错误梯度。常见的做法是计算前向和后向的光度误差如果两者差异过大就认为该像素被遮挡将其损失权重置零。我在代码里实现了一个简单的遮挡掩码对于每个像素如果前向投影的误差大于后向投影误差的某个倍数就屏蔽掉。这个技巧对最终点云质量提升非常明显尤其是物体边缘区域。3.2 深度平滑损失让点云不那么“毛躁”只用光度一致性损失训练出来的深度图往往噪声很大因为纹理稀疏的区域比如白墙缺乏足够的约束。这时候需要加一个深度平滑损失鼓励相邻像素的深度值相近。最常用的是二阶平滑项也就是对深度图的梯度再做一次梯度惩罚深度的二阶导数。但平滑损失不能无脑加否则会把物体的边缘也抹平。所以论文里通常会根据图像的边缘强度来调整平滑权重在图像梯度大的地方比如物体轮廓降低平滑权重在图像平坦区域提高平滑权重。这个思路和图像去噪里的各向异性扩散很像。我试过几种平滑损失的变体最后发现基于图像边缘加权的L1平滑效果最稳。具体来说平滑项写成# 伪代码示意 depth_dx torch.abs(depth[:, :, :, :-1] - depth[:, :, :, 1:]) image_dx torch.abs(image[:, :, :, :-1] - image[:, :, :, 1:]) smoothness_x depth_dx * torch.exp(-image_dx)这样在图像边缘处exp(-image_dx)接近0平滑约束就放松了。这个技巧在KITTI等数据集上被广泛验证过复现时可以直接抄。3.3 点云倒角距离直接优化三维结构虽然光度一致性和深度平滑已经能work但它们都是在二维图像空间或者深度图空间计算的并没有直接约束最终的三维点云。为了进一步提升点云质量论文还会引入倒角距离Chamfer Distance作为辅助损失。倒角距离衡量的是两个点云之间的双向最近邻距离。假设预测点云为P真实点云为Q如果有的话倒角距离定义为CD(P, Q) sum_{p in P} min_{q in Q} ||p - q||^2 sum_{q in Q} min_{p in P} ||q - p||^2但在无监督设定下我们没有真实点云Q。那怎么用倒角距离呢一种做法是自一致性约束把预测的点云分别投影到两个不同视角得到两组深度图然后比较这两组深度图生成的点云是否一致。另一种做法是多视图融合一致性用不同子集的图像分别重建点云然后要求这些点云互相接近。我实际尝试过第一种方案效果一般因为投影过程本身就有误差容易形成“误差抵消”的退化解。第二种方案更靠谱但计算量翻倍。如果算力有限建议优先保证光度一致性和深度平滑倒角距离作为可选项。4. 训练策略与实操中的关键细节4.1 数据准备图像集合怎么选无监督方法对数据的要求比监督方法低但也不是随便什么图片都能用。根据我的经验图像集合需要满足几个条件视角覆盖足够如果所有图片都是从同一个角度拍的那三角化根本无从谈起。理想情况下相邻图片之间的视角变化在10到30度之间。太小了基线不够深度估计不准太大了匹配困难容易丢失对应关系。光照尽量一致虽然光度一致性损失对光照变化有一定容忍度但如果同一场景有的图片是白天拍的、有的是晚上拍的那颜色差异会直接破坏损失函数。如果无法避免可以考虑在输入网络前做颜色归一化或者用更鲁棒的损失比如SSIM。纹理丰富纯色墙面、玻璃幕墙、天空区域缺乏纹理光度一致性约束很弱重建出来的深度基本靠平滑先验猜。如果场景里这类区域占比很大建议先做纹理增强或者引入额外的几何先验。我在一个室内场景项目里用过大约80张手机拍摄的照片视角覆盖了房间的四个角落和中间区域。训练出来的点云在沙发、桌子这些纹理丰富的物体上表现不错但白墙区域就明显塌陷。后来我在白墙上贴了一些打印的纹理图案重建质量立刻上了一个台阶。这个土办法虽然笨但确实有效。4.2 训练流程分阶段优化更稳端到端训练无监督重建网络很容易陷入局部最优。我推荐采用分阶段训练策略第一阶段固定位姿只训深度网络。如果你能先用COLMAP或者类似的多视图几何工具估计出相机位姿那就先固定这些位姿让深度网络专心学习光度一致性。这个阶段通常需要10到20个epoch。第二阶段联合优化深度和位姿。在深度网络有一定基础后解冻位姿估计网络用较小的学习率联合训练。这时候光度一致性损失可能会先上升后下降属于正常现象。第三阶段加入点云一致性损失。最后几个epoch再加入倒角距离或者多视图融合一致性进一步细化点云。这个流程比直接端到端训练收敛得更快最终点云也更干净。我试过直接端到端训练了50个epoch点云还是有很多离群点。分阶段之后30个epoch左右就能得到可用的结果。4.3 学习率与批大小的选择无监督重建对学习率非常敏感。学习率太大光度一致性损失会震荡深度图出现大量噪点学习率太小收敛太慢而且容易卡在“所有深度都差不多”的平凡解。我的经验值是如果batch size在4到8之间初始学习率设在1e-4比较合适然后用余弦退火慢慢降到1e-6。位姿估计网络的学习率要比深度网络小一个数量级因为位姿的微小变化会导致投影位置的大幅偏移梯度更容易爆炸。批大小方面由于每张图片都需要和其他图片计算光度一致性显存占用和图片数量的平方成正比。如果显存有限可以每次只采样一对图片计算损失而不是所有图片两两组合。这样虽然损失估计的方差大一些但训练仍然能收敛。提示如果训练过程中发现损失突然变成NaN大概率是深度值出现了0或者负数。可以在深度网络输出后加一个Softplus激活函数保证深度恒为正。另外投影时的除法操作也要加一个极小值epsilon防止除零。5. 点云后处理从粗糙到可用的最后一公里5.1 离群点剔除统计滤波与半径滤波网络直接输出的点云通常包含大量离群点尤其是在物体边缘和纹理稀疏区域。这些离群点如果不处理会严重影响后续的可视化和分析。最常用的两种滤波方法是统计滤波对每个点计算它到最近的k个邻居的平均距离。如果这个距离超过全局平均距离加若干倍标准差就认为是离群点。k通常取20到50。半径滤波对每个点统计以它为中心、半径为r的球体内有多少个邻居。如果邻居数少于某个阈值就剔除该点。r需要根据点云的密度来定一般取点云平均间距的2到3倍。我一般先用统计滤波去掉明显的飞点再用半径滤波清理稀疏区域的噪声。两轮下来点云会干净很多。但要注意滤波太狠会把物体表面的细节也去掉所以参数需要根据具体场景调。我的习惯是先用保守参数跑一遍可视化看看效果再逐步收紧。5.2 点云融合多视图一致性的利用如果网络为每张图片都输出了深度图那么每张深度图都可以生成一个局部点云。把这些局部点云融合成全局点云时简单的做法是直接根据位姿变换后拼接。但这样会保留大量冗余点而且不同视图预测的深度在重叠区域可能不一致导致点云出现“重影”。更好的做法是基于可见性的一致性融合。对于空间中的每个三维点统计它在多少个视图里被观测到以及这些视图预测的深度是否一致。如果一致就保留并取平均如果不一致就剔除。这个思路和TSDF截断符号距离场融合很像只不过TSDF是在体素网格上做而这里可以直接在点云上做。我实现过一个简化版把全局空间划分成小体素比如1cm边长每个体素内只保留一个点该点的坐标取体素内所有点的均值。这样既能降采样又能平滑掉一些噪声。体素大小需要根据场景尺度和点云密度来定太小了起不到融合效果太大了会丢失细节。5.3 可视化与评估没有真值怎么判断好坏无监督重建最尴尬的地方是你没有三维真值怎么知道重建得好不好我的做法是多角度重投影检查。把重建的点云渲染成深度图然后和原始输入图片对比。如果渲染出的深度图在物体轮廓、平面区域都和原图吻合那说明重建质量不错。如果渲染出的深度图边缘模糊、平面扭曲那说明还有问题。另一个实用的评估方法是留出验证视图。从图像集合里拿出几张图片不参与训练只用它们来测试。把训练好的模型在验证视图上预测深度然后和训练视图的重建结果做一致性检查。如果验证视图的深度和训练视图融合后的点云投影深度差异很大说明网络过拟合了训练视图泛化能力不行。我还试过用COLMAP生成一个稀疏点云作为“伪真值”来评估。虽然COLMAP本身也有误差但它的多视图几何约束很强在纹理丰富区域的结果通常比无监督网络更准。把网络输出的点云和COLMAP点云做倒角距离对比可以大致判断重建的精度水平。当然这个方法只适合做相对评估不能当作绝对真值。6. 复现过程中踩过的坑与应对方案6.1 投影函数的梯度回传问题在实现光度一致性损失时需要根据深度和位姿把一张图片的像素坐标投影到另一张图片。这个投影操作涉及可微分的网格采样grid_sample。如果直接用PyTorch的grid_sample梯度会通过采样坐标回传到深度和位姿。但这里有个坑当投影坐标超出图像边界时grid_sample默认返回0梯度也会变成0。这会导致边界区域的像素得不到有效监督。我的解决方案是在计算损失时加一个有效的掩码只对投影后落在图像范围内的像素计算损失。同时对于边界附近的像素可以适当降低损失权重避免网络为了拟合边界而牺牲内部区域的质量。另外grid_sample的padding_mode建议设为border而不是zeros这样边界外的采样会复制边缘像素梯度更稳定。6.2 位姿参数化的选择相机位姿包括旋转和平移。旋转的表示方式有很多种欧拉角、四元数、旋转矩阵、轴角等。在无监督训练中轴角表示通常比欧拉角更稳定因为它没有万向节锁问题。但轴角在接近0度时梯度会变得很小所以有些论文会用四元数加归一化。我试过几种方案最后发现用6D旋转表示即用两个三维向量正交化后得到旋转矩阵效果最好。这种表示在训练初期梯度充足而且不需要额外的归一化约束。具体实现可以参考PyTorch3D里的rotation_6d_to_matrix函数。平移向量直接回归三个实数即可但要注意尺度问题——如果场景尺度变化很大最好对平移做归一化或者用对数尺度。6.3 训练不收敛的排查清单如果你复现时发现损失不下降或者点云完全乱掉可以按以下顺序排查检查数据加载图像是否归一化到相同范围相机内参是否正确如果内参错了投影关系全错网络不可能学到东西。检查深度输出深度值是否全为正有没有出现NaN可以在网络输出后加一个torch.clamp(min1e-3)。检查投影坐标投影后的坐标是否在合理范围内如果所有点都投影到图像外说明位姿或深度严重错误。检查损失权重光度一致性、深度平滑、点云一致性的权重是否平衡建议先用光度一致性单独训练确认能work后再加其他损失。检查学习率是不是太大了可以试着降到1e-5看看损失是否开始下降。我遇到过一次训练完全不收敛的情况排查了半天发现是数据加载时把图像的RGB通道顺序搞反了BGR当成了RGB。虽然颜色通道反了理论上光度一致性仍然成立因为两张图都反了但预训练特征提取网络是在RGB上训练的通道反了导致特征完全失效。这个坑很隐蔽建议在数据加载后可视化几张图片确认一下。6.4 显存优化的几个实用技巧无监督多视图重建的显存占用主要来自两个方面一是同时加载多张图片和对应的特征图二是计算两两之间的光度一致性。如果显存不够可以尝试降低输入分辨率从512x512降到256x256显存占用直接降为四分之一。虽然细节会损失一些但训练速度大幅提升适合前期调试。梯度累积用小batch训练但每累积几个batch才更新一次参数。这样等效于大batch但显存占用不变。混合精度训练用torch.cuda.amp自动混合精度显存占用可以减少30%到50%而且训练速度更快。不过要注意光度一致性损失里的SSIM计算对精度比较敏感可能需要保持float32。只采样部分视图对不需要每轮都计算所有图片两两之间的损失随机采样若干对即可。这样显存占用和图片数量成线性关系而不是平方关系。7. 这个方向还能怎么玩几个值得尝试的扩展思路7.1 结合神经辐射场做精细化无监督重建得到的点云通常比较粗糙边缘不够锐利。一个自然的想法是把点云作为初始化然后用神经辐射场NeRF做精细化。NeRF通过体渲染可以生成非常逼真的新视角图像而且它的密度场天然编码了三维结构。你可以先用无监督方法快速得到一个粗略的点云然后用这些点云初始化NeRF的采样空间这样NeRF训练会快很多而且不容易陷入局部最优。我试过这个组合效果确实比单独用NeRF好。NeRF单独训练时在纹理稀疏区域容易产生“雾状”伪影而有了点云提供的几何先验后这些伪影明显减少。反过来NeRF优化后的密度场也可以提取出更精细的点云形成一个迭代优化的闭环。7.2 动态场景的扩展这篇论文讨论的是静态场景。如果场景中有运动物体光度一致性假设就被破坏了。一个可能的扩展方向是引入运动分割把动态物体和静态背景分开处理。静态背景用常规的无监督重建动态物体则单独估计其运动轨迹和三维结构。这个方向在自动驾驶和机器人领域很有应用前景但难度也大很多因为运动分割本身就是一个病态问题。7.3 跨数据集泛化无监督方法的一个潜在优势是更容易跨数据集泛化因为它不依赖三维标注理论上可以在任意图像集合上训练。但实际中网络在训练集上学到的先验比如深度平滑的统计特性可能在新场景中不适用。我试过在一个室内数据集上训练然后直接用在室外场景上结果深度图出现了明显的尺度偏差。后来加了一个简单的在线自适应步骤——在测试时用少量测试图片微调深度网络的最后几层——泛化能力提升了不少。这个思路类似于测试时增强但需要小心不要过拟合到测试集。7.4 点云下游任务的直接应用重建出的点云可以直接用于一些下游任务比如三维目标检测、语义分割、配准等。但无监督重建的点云通常比较稀疏且噪声大直接喂给下游网络效果可能不好。我的经验是先用统计滤波和体素降采样做预处理然后把点云体素化或者投影到多视图再用成熟的二维网络做检测或分割。这样虽然绕了一步但比直接在噪声点云上训练三维网络要稳得多。8. 一些个人体会这个方向我断断续续跟了差不多一年最大的感受是无监督三维重建的瓶颈不在网络架构而在损失函数的设计和训练策略的调优。你看论文里的网络结构其实都很简单无非是卷积层加几个全连接层但损失函数的每一项权重、每一个掩码的阈值都会对最终点云质量产生巨大影响。我经常花一整天时间调一个损失权重最后发现点云质量提升微乎其微但换一个数据预处理方式效果立刻就不一样了。另一个体会是不要迷信论文里的指标。很多论文在公开数据集上报告的倒角距离、F-score看起来很漂亮但你用自己的数据一跑发现完全不是那么回事。原因很简单论文里的数据集通常经过精心筛选光照均匀、纹理丰富、视角覆盖好。真实场景里的图像集合往往乱七八糟这时候论文里的超参数基本都要重新调。所以我的建议是先把论文的代码跑通在它自带的数据集上复现出接近的结果然后立刻换到自己的数据上从数据清洗和预处理开始重新调。这个过程很痛苦但绕不过去。最后如果你刚开始接触这个方向我建议先从有监督的深度估计入手比如用NYU Depth V2或者KITTI Depth数据集训练一个单目深度估计网络。理解了深度估计的基本流程后再过渡到无监督多视图重建会容易很多。因为无监督重建的核心难点其实在于多视图几何约束的引入而不是深度网络本身。先把单视图深度估计玩熟了再往上叠多视图的复杂度学习曲线会平缓不少。