1. 多视点图像到底在解决什么问题
多视点图像,英文叫 Multi-View Image,也有人叫多目视觉、多视角成像。简单说,就是用两个或更多相机,从不同角度同时拍同一个场景,然后把这些图像放在一起处理。它解决的核心问题是:单张照片只有二维信息,深度、遮挡关系、物体真实尺寸全都丢了,而多视点图像能把这些信息找回来。
我第一次接触这个概念是在一个工业检测项目里。当时用单个相机拍零件表面,反光、阴影、遮挡导致缺陷漏检率很高。后来换成两个相机从不同角度拍,同一个缺陷在两个视角下表现不一样,交叉验证之后漏检率直接降了一个数量级。从那以后我就意识到,多视点图像不是简单“多拍几张”,而是一套完整的几何与视觉计算体系。
这套东西能做什么?最直接的就是深度估计和三维重建。你手机里的人像模式虚化、自动驾驶的障碍物测距、工业上的三维尺寸测量、医学影像的三维建模,底层都离不开多视点图像。再往上一层,还有多视点立体匹配、光场成像、自由视点视频这些应用。
适合谁来参考?如果你是做计算机视觉的开发者、做三维重建的工程师、做工业检测的技术人员,或者只是对“相机怎么测距”这件事好奇的爱好者,这篇内容都能给你一条从原理到实操的完整路径。我会尽量少堆公式,多用实际项目里的例子和参数来说话。
2. 核心原理拆解:从两个视角到一张深度图
2.1 视差才是深度的来源
人为什么能判断远近?因为两只眼睛看到的画面有细微差异,大脑根据这个差异算出距离。多视点图像的核心原理一模一样,这个差异叫视差(Disparity)。
假设两个相机水平放置,间距为 B(基线),焦距为 f。同一个空间点在左右图像上的横坐标分别是 xL 和 xR,那么视差 d = xL - xR。这个点的深度 Z 可以用一个非常简洁的公式算出来:
Z = (f × B) / d
这个公式是整个多视点视觉的基石。它告诉我们两件事:第一,视差越大,物体越近;第二,基线 B 越大,同样深度下的视差越大,测距越准,但基线太大又会导致两个视角重叠区域变小,匹配变难。所以基线的选择是一个典型的工程权衡。
我做过一个室内场景重建的项目,基线选了 12 厘米,焦距 4 毫米,传感器像素尺寸 3 微米。在 2 米距离上,视差大约是 4×0.12/(0.003×2) ≈ 80 像素。这个视差量足够做亚像素匹配,深度精度能到毫米级。如果基线缩到 3 厘米,同样距离视差只有 20 像素,精度就掉到厘米级了。这就是为什么测距设备往往基线不能太小的原因。
2.2 极线约束:把二维搜索变成一维
多视点图像处理里最耗时的步骤是立体匹配,也就是在左右图里找同一个点。如果每个像素都要在另一张图的整个二维区域里搜,计算量会爆炸。极线约束就是用来救场的。
极线约束说的是:左图上的一个点,在右图上的对应点一定位于某一条直线上,这条线叫极线。这条线的位置由两个相机的相对位姿决定。有了这个约束,搜索空间从二维降到一维,计算量直接降一个维度。
要让极线变成水平线,需要做极线校正(Rectification)。校正之后,左右图的同一行对应同一个空间水平面,匹配只需要在同一行里找。这一步是所有多视点立体匹配的标准前置操作,OpenCV 里的stereoRectify就是干这个的。
注意:极线校正依赖准确的相机标定参数。如果标定不准,校正后的极线不会水平,匹配就会大面积出错。我见过有人跳过标定直接做匹配,结果深度图全是噪点,排查了两天才发现是标定板拍得太少。
2.3 多视点的优势不只是“多”
两个相机已经能算深度,为什么还要三个、四个甚至更多?原因有三个。
第一是遮挡。一个相机看不到的地方,另一个相机可能看得到。视角越多,被遮挡的区域越少,重建的完整度越高。
第二是鲁棒性。两个视角匹配错了没有第三方来纠正,多视角可以通过几何一致性检验把错误匹配剔掉。比如三个视角里有两个算出的深度一致,第三个差很远,那第三个大概率是错的。
第三是精度。多视点可以通过冗余观测做最小二乘优化,把深度误差进一步压低。在工业测量里,从双目的 0.1 毫米精度做到多目的 0.02 毫米精度,靠的就是冗余。
3. 相机标定:多视点系统的地基
3.1 内参和外参分别是什么
相机标定要解两类参数。内参描述相机自身的成像特性,包括焦距、主点坐标、畸变系数。外参描述相机之间的相对位置和姿态,包括旋转矩阵和平移向量。
内参决定了像素和物理尺寸的换算关系,外参决定了多个相机之间的几何关系。多视点系统里,外参的精度直接决定深度精度。我个人的经验是:内参可以一次标定长期使用,外参在每次设备移动或震动后都必须重新标定。
3.2 标定实操:从拍板到解算
标定流程我一般这样做:
- 打印一张棋盘格标定板,格子尺寸根据工作距离选。工作距离 1 米以内用 20 毫米格子,1 到 3 米用 50 毫米格子。
- 两个相机同时拍摄标定板,至少 15 到 20 个不同姿态。姿态要覆盖整个视场,包括四个角和中心,倾斜角度也要有变化。
- 用 OpenCV 的
findChessboardCorners提取角点,calibrateCamera分别解两个相机的内参。 - 用
stereoCalibrate解外参,得到两个相机之间的旋转和平移。 - 用
stereoRectify做极线校正,保存映射表。
这里有个细节很多人忽略:标定板的平整度。打印出来的纸如果贴在不平的板上,角点提取会有系统性偏差。我一般会把标定板贴在一块玻璃或铝板上,实测下来重投影误差能从 0.5 像素降到 0.15 像素。
3.3 标定质量的判断标准
标定完怎么知道好不好?看重投影误差。这个指标衡量的是:用标定出的参数把空间点投影回图像,和实际检测到的角点差多少像素。
| 误差范围 | 评价 | 处理建议 |
|---|---|---|
| < 0.2 像素 | 优秀 | 可直接用于高精度测量 |
| 0.2 - 0.5 像素 | 良好 | 一般三维重建够用 |
| 0.5 - 1.0 像素 | 勉强 | 建议重新标定 |
| > 1.0 像素 | 不合格 | 检查标定板、光照、角点提取 |
我踩过的一个坑是:标定时光照不均匀,标定板一侧亮一侧暗,角点提取在暗侧偏差大,导致重投影误差 0.8 像素。后来加了两个补光灯,误差降到 0.2 像素以下。所以标定环境的光照均匀性,比很多人想象的更重要。
4. 立体匹配:多视点图像的核心战场
4.1 匹配算法的三大流派
立体匹配算法分三大类,各有各的适用场景。
局部算法:只用一个像素周围的小窗口做匹配,速度快,适合实时应用。缺点是纹理少的区域容易出错。典型的如 SAD、SSD、Census 变换。
全局算法:把整张图的匹配当成一个能量最小化问题,用动态规划或图割求解。精度高,但速度慢,适合离线处理。典型的如 SGM(半全局匹配)、Graph Cut。
深度学习方法:用神经网络直接学视差图,代表是 PSMNet、GA-Net 这些。精度和鲁棒性都很好,但需要 GPU 和训练数据。
我实际项目里的选择逻辑是:实时性要求高、纹理丰富,用 SGM;精度要求高、可以离线,用深度学习;嵌入式设备算力有限,用 Census 加后处理。
4.2 SGM 为什么是工程首选
SGM(Semi-Global Matching)是我用得最多的算法,因为它在精度和速度之间找到了很好的平衡。它的核心思想是:从多个方向做一维动态规划,然后把结果聚合起来。
一维动态规划只考虑一行像素,计算量小,但容易产生条纹伪影。SGM 从 8 个或 16 个方向分别做,每个方向都算一个代价,最后求和。这样既保留了一维的高效,又通过多方向聚合抑制了条纹。
SGM 的关键参数有四个:
- P1:视差变化 1 个像素的惩罚,一般设 8 到 16。
- P2:视差变化大于 1 个像素的惩罚,一般设 32 到 64,必须是 P1 的 3 到 4 倍。
- 视差范围:根据基线和工作距离算,宁大勿小,但太大会拖慢速度。
- 一致性检查阈值:左右一致性检查的容差,一般设 1 到 2 像素。
实操心得:P2 设得太小,视差图会在物体边缘产生大量飞点;设得太大,边缘又会被过度平滑。我的经验是先按 P2 = 3×P1 起步,然后根据边缘质量微调。
4.3 后处理:让视差图从“能用”到“好用”
原始视差图一定有很多问题:遮挡区域没值、纹理弱区域乱值、边缘飞点。后处理就是把这些修掉。
标准后处理流程是:
- 左右一致性检查:左图算出的视差和右图算出的视差互相验证,差超过阈值的标记为无效。
- 无效值填充:用周围有效值插值填补,或者用左右最近有效值填充。
- 亚像素优化:用抛物线拟合把视差精度从整数像素提到 0.1 像素。
- 中值滤波:去掉孤立的飞点,窗口一般 3×3 或 5×5。
- 连通域过滤:去掉面积太小的连通区域,这些通常是噪声。
我做过对比测试:不做后处理的视差图,在标准测试集上的坏点率是 15% 左右;做完上述五步,坏点率能降到 3% 以下。这中间的差距,就是能不能实际用的差距。
5. 从视差到三维点云:完整实操链路
5.1 点云生成的数学过程
有了视差图,生成三维点云就是套公式。对左图上的每个像素 (u, v),视差为 d,深度 Z = f×B/d。然后反投影到三维空间:
X = (u - cx) × Z / f Y = (v - cy) × Z / f Z = f × B / d
其中 (cx, cy) 是主点坐标。这一步在 OpenCV 里用reprojectImageTo3D一行就能完成,但前提是视差图质量要过关。
5.2 点云滤波与网格化
原始点云有几万个到几百万个点,直接可视化会很乱。我一般做三步处理:
统计滤波:每个点看它周围 K 个邻居的平均距离,距离超过全局均值加两倍标准差的点删掉。这一步去掉离群点。
体素下采样:把空间划分成小立方体,每个立方体只保留一个点。这样点云密度均匀,后续处理快很多。体素大小根据需要的精度选,一般 1 到 5 毫米。
法线估计:每个点估计一个法线方向,用于后续的网格化或配准。法线估计的邻域大小要匹配点云密度,太小噪声大,太大细节丢。
网格化我常用泊松重建,它能生成平滑的封闭曲面。但泊松重建对噪声敏感,所以前面的滤波一定要做干净。如果只是做测量不需要曲面,点云本身就够了。
5.3 一个完整的参数配置实例
下面是我在一个 1 米工作距离的室内重建项目里的实际参数,可以直接参考:
# 相机参数 focal_length = 4.0 # mm baseline = 120.0 # mm pixel_size = 3.0 # um # 视差范围计算 # 最近距离 0.5m,最远 3m # d_near = f*B/Z_near = 4*120/(0.5*1000) = 0.96mm = 320像素 # d_far = f*B/Z_far = 4*120/(3*1000) = 0.16mm = 53像素 min_disparity = 48 # 16的倍数,留余量 max_disparity = 336 # 16的倍数,留余量 num_disparities = 288 # SGM参数 P1 = 12 P2 = 48 uniqueness_ratio = 10 speckle_window_size = 100 speckle_range = 2 disp12_max_diff = 1这套参数在 1280×720 分辨率下,单帧处理时间约 80 毫秒,深度精度在 1 米处约 2 毫米。如果你的场景更近,视差范围要相应加大;更远则减小,同时精度会下降。
6. 常见问题与排查技巧实录
6.1 深度图出现大面积空洞
这是最常见的问题。原因通常有三个:纹理太弱、遮挡、曝光不一致。
纹理太弱是最难办的。白墙、玻璃、金属表面,局部算法完全找不到匹配点。解决办法是投影纹理:用投影仪往场景上打随机斑点图案,人为制造纹理。工业检测里这招很常用,代价是需要额外一个投影设备。
遮挡导致的空洞是物理限制,只能靠增加视角来缓解。如果只有两个相机,遮挡区域就是无解的,后处理填充只是视觉上好看,深度值并不准。
曝光不一致会让左右图的同一区域亮度差很多,匹配代价算不准。解决办法是匹配前做亮度归一化,或者用对亮度不敏感的代价函数,比如 Census 变换。
6.2 深度图边缘飞点严重
边缘飞点是前景和背景交界处的典型问题。前景物体的视差和背景差很多,匹配窗口跨越边界时,窗口里混了两种视差,算出来的值两边不靠。
解决办法有三个:一是用自适应窗口,窗口形状根据颜色相似度调整,不跨越边界;二是用边缘感知的代价聚合,SGM 的多方向聚合本身就有一定边缘保持能力;三是后处理时用左右一致性检查把边缘飞点标记为无效,再用背景视差填充。
我实测下来,SGM 加左右一致性检查,边缘飞点能减少 70% 以上。剩下的用中值滤波处理,基本就干净了。
6.3 深度值整体偏移或缩放不对
如果深度图看起来形状对,但数值整体偏大或偏小,问题一定出在标定或基线。
先检查基线 B 的单位。标定输出的平移向量单位是毫米还是米,要和焦距单位统一。我见过有人焦距用毫米、基线用米,算出来的深度差了 1000 倍。
再检查焦距。标定输出的焦距是像素单位还是物理单位。OpenCV 的calibrateCamera输出的是像素单位,如果直接拿物理焦距去算,也会错。
最后检查视差图有没有做亚像素优化。如果视差只有整数精度,深度值会呈阶梯状,看起来像整体偏移。亚像素优化后深度就连续了。
6.4 多相机同步问题
多视点系统如果拍的是动态场景,相机必须同步。不同步会导致同一时刻不同相机拍到的画面不一致,匹配全错。
硬件同步用触发线,一个相机做主触发,其他相机接收触发信号。软件同步用时间戳对齐,但精度只能到毫秒级,动态场景不够用。
我做过一个传送带上的零件检测,相机不同步导致零件位置在左右图里差了 5 个像素,深度全乱。后来加了硬件触发,问题消失。所以动态场景一定要硬件同步,这是硬性要求。
6.5 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 大面积空洞 | 纹理弱/遮挡/曝光差 | 看原图纹理和亮度 | 投影纹理/加视角/归一化 |
| 边缘飞点 | 匹配窗口跨边界 | 看边缘区域视差 | 自适应窗口/一致性检查 |
| 深度整体偏移 | 单位不统一/标定错 | 检查B和f的单位 | 统一单位/重新标定 |
| 深度阶梯状 | 视差无亚像素 | 看视差图精度 | 抛物线拟合优化 |
| 动态场景错乱 | 相机不同步 | 看时间戳 | 硬件触发同步 |
| 重投影误差大 | 标定板不平/光照差 | 看误差分布 | 换标定板/补光 |
7. 多视点图像的进阶方向
7.1 自由视点视频与光场
多视点图像往视频方向走,就是自由视点视频。用几十个相机围一圈拍,用户可以任意切换视角,像在场景里走动一样。体育赛事回放、演唱会直播已经在用这个技术。
再进一步是光场成像。光场不仅记录光线的位置,还记录方向。拍一次可以后期重新对焦、改变景深、甚至微调视角。消费级光场相机前几年火过一阵,但因为分辨率损失太大,没普及开。不过在工业检测里,光场相机能一次拍到多角度信息,对反光表面的缺陷检测很有优势。
7.2 多视点与深度学习的结合
传统多视点方法依赖几何,对纹理弱、重复纹理的场景很吃力。深度学习直接从数据里学匹配,对这些困难场景鲁棒性好很多。
现在主流的做法是:用 CNN 提取特征,用代价体做匹配,用 3D CNN 做代价聚合,最后回归视差。整个流程端到端训练,不需要手工设计代价函数。
但深度学习也有短板:需要大量标注数据,泛化到新场景可能掉点,推理需要 GPU。我的建议是:如果场景固定、数据充足,用深度学习;如果场景多变、数据少,传统方法加后处理更稳。
7.3 多视点图像的质量评价
怎么判断一组多视点图像好不好?我一般看四个指标:
- 重叠率:相邻视角的重叠区域占比,一般要求 60% 以上。
- 基线比:基线和工作距离的比值,一般 0.1 到 0.3 比较合适。
- 纹理丰富度:图像梯度均值,太低说明纹理弱,匹配困难。
- 曝光一致性:不同视角的亮度均值差异,超过 20% 就要做归一化。
这四个指标在采集阶段就能算,提前发现问题比后期补救省事得多。
8. 我踩过的坑和实操建议
第一个坑是迷信高分辨率。一开始我觉得分辨率越高越好,用了 4K 相机,结果处理速度慢到没法用,而且高分辨率下噪声也更明显,匹配反而更差。后来降到 1280×720,速度上来了,精度也没降多少。分辨率要匹配工作距离和精度需求,不是越高越好。
第二个坑是忽略温度影响。相机和支架会热胀冷缩,长时间工作后外参会漂移。我做过一个连续运行 8 小时的项目,早上标定的参数,下午深度就偏了 3 毫米。后来加了温度补偿,或者每隔几小时重新标定一次,问题才解决。如果你的项目精度要求高、运行时间长,温度这个因素一定要考虑。
第三个坑是后处理过度。中值滤波窗口开太大,边缘细节全糊了;无效值填充太激进,把遮挡区域填上了错误的深度。后处理的原则是:能少做就少做,每一步都要有明确的理由,做完要验证效果。
第四个坑是只测静态场景。标定和验证都在静态下做,一到动态就出问题。动态场景要考虑运动模糊、同步误差、曝光时间。曝光时间要短到运动模糊小于 1 个像素,同步误差要小于 1 毫秒。这些在静态测试里完全看不出来。
最后分享一个实用技巧:用棋盘格做深度验证。标定完之后,把棋盘格放在不同距离,用多视点系统算深度,和卷尺量的真实距离对比。如果误差在预期范围内,说明整个链路是通的。这个验证比看视差图直观得多,也更容易发现系统性问题。
多视点图像这个方向,入门不难,做好很难。难的不是算法本身,而是工程细节:标定、同步、光照、温度、后处理,每一个环节都能让最终结果差之毫厘谬以千里。但一旦把这些细节都理顺了,它能给你的深度信息,是单目视觉永远给不了的。