采集 · SfM

全景视频做 3DGS:为什么 200° 鱼眼直喂 COLMAP 必然失败

这是用全景相机做三维重建的人几乎都会撞的一堵墙: 素材没问题、软件不报错,但重建就是不出来。 原因不在素材,在投影方式和相机内参。这一篇把两件事都讲清。

技术 · 采集管线 · 含可复现的修复步骤

先说结论

如果你正拿 360° 全景视频(或双鱼眼原片)直接喂给 COLMAP 做三维重建, 大概率会看到稀疏点云几乎为空,或者重建出来的位姿完全错乱。

两个原因,都很具体:

  1. 200° 双鱼眼画面不能直接当针孔相机用。 必须先用重投影转成透视图(我们从 200° 转成 122°),否则初始三角化根本不成立。
  2. COLMAP 猜的焦距是错的。 它的默认值按图像宽度估算,与我们实际的情况差了约 4 倍。 内参错 4 倍,位姿解算自然崩。

修好这两点之后,同一段素材的注册率从完全失败变成了 100%(30/30 帧)。素材一个字没改。


问题一:为什么鱼眼不能直喂

SfM(运动恢复结构)要成立,得先知道「这张图里的这个像素,对应空间中哪条光线」。 这个关系由相机模型定义。

COLMAP 支持的相机模型里,最常用的是针孔模型(PINHOLE)—— 它的假设是「直线投影」:空间中的直线在图像上仍然是直线。

而鱼眼镜头是非线性投影:为了把 200° 甚至更广的视野压进一个画面, 它把边缘强烈压缩。画面上的直线是弯的。

用针孔模型去解释鱼眼画面,会发生什么

特征匹配阶段往往还能凑合(局部的纹理块仍然对得上), 但到了初始对三角化这一步就会失败 —— 因为算出来的两条光线根本不相交,或者交在荒谬的位置。

这就是「素材看着没问题、重建却出不来」的原因。 不是素材坏了,是模型选错了。

修复:重投影成透视图

做法是把鱼眼画面重投影成透视投影(也叫平面投影、rectilinear)。 相当于「从这张 200° 的画面里,抠出一块符合直线投影的区域」。

我们用的视场角是 122° —— 这不是随便取的, 而是与常用航空摄影测量软件的配置保持一致。

用 ffmpeg 的 v360 滤镜可以做这件事:

# 双鱼眼 → 等距柱状(equirectangular)
ffmpeg -i input.mp4 -vf "v360=input=dfisheye:output=e:ih_fov=200:iv_fov=200" eq.mp4

# 等距柱状 → 122° 透视
ffmpeg -i eq.mp4 -vf "v360=input=e:output=flat:h_fov=122:v_fov=68" flat.mp4

如果相机原片本身已经是等距柱状(很多全景相机的标准输出格式), 可以直接做第二步。

注意:重投影会损失边缘分辨率。 122° 是从 200° 里"抠"出来的一块,所以最终成图的有效像素比原片少。 如果你的原片分辨率不够高,重投影后的清晰度可能不足以支撑重建 —— 这一点要在采集阶段就考虑到。


问题二:COLMAP 的默认焦距为什么差 4 倍

这是更隐蔽的一个坑,因为你不会收到任何错误提示。

COLMAP 在没有提供内参时,会用一个经验公式猜焦距。 这个猜法在「普通相机拍的照片」上大致可用, 但对重投影出来的透视画面,它的假设不成立。

为什么会差这么多

焦距与视场角的关系是:

f = (W / 2) / tan(FOV / 2)

W   = 图像宽度(像素)
FOV = 水平视场角

以我们处理的画面为例,宽度约 2048 像素、水平视场 122°:

f = (2048 / 2) / tan(122° / 2)
  = 1024 / tan(61°)
  ≈ 1024 / 1.804
  ≈ 568 像素

而 COLMAP 的默认估计大致相当于 1.2 × 图像宽度, 也就是 2400 像素上下 —— 与实际值 差了约 4 倍。

焦距错 4 倍,等于告诉解算器"这个视角比实际窄得多", 位姿解算出来的结果自然完全对不上。

修复:显式提供内参

不要依赖自动估计。把焦距和主点显式算出来传进去:

colmap feature_extractor \
  --database_path database.db \
  --image_path images/ \
  --ImageReader.camera_model PINHOLE \
  --ImageReader.single_camera 1 \
  --ImageReader.camera_params "568.0,568.0,1024.0,576.0"

camera_params 的四个值依次是 fx, fy, cx, cy(焦距 x、焦距 y、主点 x、主点 y)。

其中:

  • fx = fy:重投影出来的透视图是各向同性的,两轴焦距相同
  • cx = W/2, cy = H/2:主点在画面中心(假设重投影时没有偏心)

如果重投影时做了裁剪或偏移,主点位置要相应调整 —— 这一点容易被忽略,但会直接导致重建偏移。


修复前后的对比

同一段素材(65 秒全景视频),只改上面两点:

修复前修复后
投影方式 200° 双鱼眼直喂 122° 透视图
相机内参 COLMAP 自动估计 显式计算并提供
相机模型 — PINHOLE
注册率 失败(初始三角化不成立) 100%(30/30 帧)

上表结果来自同一段素材的两次处理,仅修改了投影方式与相机内参两项,其余参数完全一致。


除了这两点,还有几个容易忽略的地方

1. 帧间位移太大

用运动延时(hyperlapse)拍的素材,相邻帧之间已经走了很远, 重叠度不足,匹配会失败。我们按「每 N 帧取 1 帧」抽帧来控制间隔。

2. 模糊帧和过曝帧要提前剔除

移动太快导致运动模糊、或者高光溢出,都会污染匹配。 我们在抽帧阶段就做质检(拉普拉斯方差判模糊、过曝像素占比), 不合格的帧直接剔掉并记录原因。

3. 重复帧要去重

手持或慢速拍摄时会有大量几乎相同的帧,白白增加计算量。 用帧差阈值去重。

4. 手和自拍杆要遮掉

手持全景相机时,画面底部中央往往是手或杆子。 这些区域在相邻帧之间一直在动,会被匹配算法当成移动物体, 影响位姿解算。我们在底部区域加了遮罩。


一句话总结

用全景素材做三维重建,不要在相机模型上想当然。 先问自己两个问题:
① 画面的投影方式,和你告诉解算器的相机模型一致吗?
② 内参是你算出来的,还是软件猜的?

这两个问题的答案,往往就是"重建不出来"和"重建得很顺"的分界线。

有类似的采集或重建问题?

我们做完整的实景重建管线 —— 从采集方案、位姿解算到浏览器渲染。 如果你手上有素材重建不出来,可以说说情况,我们看看是哪一环的问题。