基于OpenCV的三维重建完整实现与实战解析
三维重建技术通过从二维图像中恢复物体的空间结构,成为增强现实(AR)、虚拟现实(VR)、机器人导航与医学影像分析等领域的关键技术。OpenCV作为开源计算机视觉库,提供了丰富的图像处理与几何建模工具,为三维重建的实现提供了高效支持。本章将系统介绍三维重建的基本流程,包括图像采集、特征提取、匹配、相机姿态估计与三维点云生成。通过对比传统方法与基于OpenCV的实现方式,展示其在开发效率与算法集成方面
简介:本程序围绕计算机视觉中的三维重建技术,基于OpenCV库从二维图像中恢复三维信息。内容涵盖特征匹配、立体匹配、基础矩阵与本质矩阵估计、三角测量、稀疏与稠密点云构建、泊松表面重建等关键技术。通过该程序实战,学习者可掌握OpenCV在三维重建中的核心API使用方法,如findHomography、solvePnP、triangulatePoints等,并提升在相机标定、RANSAC去噪等方面的实战能力,适用于SLAM、AR/VR、机器人视觉等领域。 
1. OpenCV三维重建技术概述
三维重建技术通过从二维图像中恢复物体的空间结构,成为增强现实(AR)、虚拟现实(VR)、机器人导航与医学影像分析等领域的关键技术。OpenCV作为开源计算机视觉库,提供了丰富的图像处理与几何建模工具,为三维重建的实现提供了高效支持。
本章将系统介绍三维重建的基本流程,包括图像采集、特征提取、匹配、相机姿态估计与三维点云生成。通过对比传统方法与基于OpenCV的实现方式,展示其在开发效率与算法集成方面的优势。同时,将简要回顾相机模型(如针孔模型)、图像坐标系与世界坐标系之间的转换关系,为后续章节奠定理论基础。
2. 特征检测与匹配(SIFT/SURF/ORB)
特征检测与匹配是三维重建流程中至关重要的一环。它决定了图像之间关键点的识别与对应关系,是后续立体匹配、运动恢复结构(SfM)和三维点云构建的基础。本章将深入剖析主流特征检测算法的原理,包括 SIFT、SURF 和 ORB,并探讨其在特征匹配中的实际应用与优劣比较。同时,我们将介绍 OpenCV 中相关 API 的使用方式,并通过完整的 Python 示例代码展示如何在实际项目中实现特征点匹配。
2.1 特征检测算法原理
特征检测旨在从图像中提取具有独特性和稳定性的关键点,为后续的特征匹配和三维重建提供可靠的图像对应关系。SIFT、SURF 和 ORB 是当前最常用的三种特征检测与描述算法,它们分别在精度、速度和专利限制方面各有侧重。
2.1.1 SIFT特征提取原理与尺度空间分析
SIFT(Scale-Invariant Feature Transform)由 David Lowe 于 1999 年提出,具有尺度不变性和旋转不变性,广泛应用于图像检索、三维重建和目标识别等领域。
核心原理
SIFT 特征提取分为以下四个主要步骤:
- 尺度空间极值检测 :使用高斯差分(DoG)近似拉普拉斯金字塔,检测图像中的极值点,以获取尺度不变的关键点。
- 关键点定位 :通过插值方法精确定位关键点位置,并剔除低对比度和边缘响应点。
- 方向赋值 :基于局部图像梯度分布,为每个关键点分配一个或多个方向,实现旋转不变性。
- 特征描述 :在关键点周围区域构建方向梯度直方图(HOG),形成 128 维的特征向量。
尺度空间分析
尺度空间(Scale Space)是 SIFT 的核心概念之一,它通过构建图像金字塔来模拟不同尺度下的图像信息。如下图所示,每一层图像通过不同 σ 的高斯滤波进行模糊,构建 DoG 图像用于检测极值点。
graph TD
A[原始图像] --> B[高斯金字塔构建]
B --> C[DoG图像生成]
C --> D[极值点检测]
D --> E[关键点定位]
E --> F[方向分配]
F --> G[特征描述]
SIFT 的尺度不变性使其在尺度变化较大的图像中仍能保持良好的检测效果,但其计算复杂度较高,实时性较差。
2.1.2 SURF算法的积分图像优化机制
SURF(Speeded-Up Robust Features)是对 SIFT 的改进,由 Bay 等人在 2006 年提出,旨在提高特征检测速度。
核心机制
SURF 的关键优化点在于使用 积分图像 (Integral Image)来加速卷积操作,从而大幅提升计算效率。
- 积分图像 :每个像素点的值等于该点左上角所有像素之和,可以快速计算任意矩形区域的像素和。
- Hessian 矩阵近似 :使用盒状滤波器(Box Filter)近似二阶导数,用于检测关键点。
- 特征描述 :采用 64 维或 128 维的 Haar 小波响应构建特征向量。
性能对比
| 特性 | SIFT | SURF |
|---|---|---|
| 尺度不变性 | ✅ | ✅ |
| 旋转不变性 | ✅ | ✅ |
| 计算速度 | 较慢 | 快 |
| 专利限制 | 无 | 曾有专利限制(现已过期) |
| 描述子维度 | 128 | 64 或 128 |
SURF 在保证检测质量的同时,显著提升了运行速度,适合对实时性要求较高的三维重建场景。
2.1.3 ORB算法的FAST角点检测与BRIEF描述子
ORB(Oriented FAST and Rotated BRIEF)是 2011 年由 Ethan Rublee 等人提出的一种非专利特征检测与描述算法,适用于嵌入式系统和移动设备。
技术组成
- FAST 角点检测 :快速检测图像中的角点,但不具备尺度和旋转不变性。
- BRIEF 描述子 :基于图像局部像素对的比较结果生成二进制特征向量,计算高效。
- 改进机制 :加入方向信息(方向 FAST)和多尺度金字塔,增强尺度不变性。
ORB 特点
| 特性 | 是否支持 |
|---|---|
| 尺度不变性 | ✅(通过图像金字塔) |
| 旋转不变性 | ✅(通过方向 FAST) |
| 描述子类型 | 二进制向量(通常 256 位) |
| 是否专利限制 | ❌ |
| 适用平台 | 移动设备、嵌入式系统 |
ORB 的二进制描述子使得其匹配过程可通过 Hamming 距离进行,速度极快,非常适合资源受限环境下的三维重建。
2.2 特征匹配策略
特征匹配是将不同图像中的特征点进行对应的过程,其准确性直接影响三维重建的质量。
2.2.1 暴力匹配与FLANN匹配方法对比
OpenCV 提供了两种主要的特征匹配方式: 暴力匹配 (Brute-Force Matcher)和 FLANN 匹配 (Fast Library for Approximate Nearest Neighbors)。
暴力匹配(Brute-Force Matcher)
暴力匹配通过计算所有特征点之间的距离进行匹配,精度高但效率低。
bf = cv2.BFMatcher()
matches = bf.match(des1, des2)
cv2.BFMatcher():创建暴力匹配器。match():返回最佳匹配结果。
FLANN 匹配(Fast Library for Approximate Nearest Neighbors)
FLANN 是一种近似最近邻搜索算法,速度快,适合大规模数据匹配。
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
knnMatch():返回每个特征点的 k 个最佳匹配结果。k=2:返回最近邻和次近邻,便于后续比值筛选。
性能对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 暴力匹配 | 精度高 | 速度慢 | 小规模图像 |
| FLANN 匹配 | 速度快 | 精度略低 | 大规模图像 |
2.2.2 匹配结果的筛选与匹配质量评估
为了提升匹配的准确性,需要对原始匹配结果进行筛选。
比值筛选(Ratio Test)
根据 Lowe 的 SIFT 论文,采用比值筛选可有效去除错误匹配:
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)
m.distance:最近邻匹配距离。n.distance:次近邻匹配距离。0.7:经验阈值,可根据实际图像调整。
匹配质量评估指标
- 匹配点数量 :越多越可能表示图像相似。
- 平均距离 :越小匹配越可靠。
- RANSAC 筛选 :进一步剔除异常点,提升几何一致性。
2.2.3 特征点匹配在三维重建中的应用实例
在三维重建中,特征点匹配用于建立图像之间的对应关系,进而计算基础矩阵或本质矩阵,恢复相机姿态。
应用流程
- 图像采集 :拍摄多视角图像。
- 特征检测与描述 :使用 SIFT、SURF 或 ORB 提取特征。
- 特征匹配 :利用 BF 或 FLANN 进行特征点匹配。
- 几何验证 :通过 RANSAC 剔除异常匹配。
- 三维重建准备 :为后续三角化和点云构建提供基础。
2.3 OpenCV中的特征检测与匹配API
OpenCV 提供了丰富的 API 来实现特征检测与匹配功能,下面我们将介绍常用接口及其使用方法。
2.3.1 cv2.SIFT_create()与cv2.ORB_create()的使用
使用 SIFT 创建检测器
sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(image, None)
detectAndCompute():同时检测关键点并计算描述子。keypoints:检测到的关键点列表。descriptors:对应的特征描述子矩阵。
使用 ORB 创建检测器
orb = cv2.ORB_create(nfeatures=500)
keypoints, descriptors = orb.detectAndCompute(image, None)
nfeatures:控制检测关键点的最大数量。
2.3.2 cv2.DescriptorMatcher.match()方法详解
该方法用于执行特征匹配操作。
示例代码
bf = cv2.BFMatcher(cv2.NORM_L2)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
cv2.NORM_L2:使用 L2 距离进行匹配(适用于 SIFT/SURF)。match():返回匹配结果列表。sorted():按距离排序,保留最优匹配。
2.3.3 基于Python实现特征点匹配的完整代码示例
下面是一个完整的特征点匹配示例,使用 ORB 检测器与 BF 匹配器:
import cv2
import numpy as np
# 读取图像
img1 = cv2.imread('image1.jpg', 0)
img2 = cv2.imread('image2.jpg', 0)
# 创建 ORB 检测器
orb = cv2.ORB_create()
# 检测关键点并计算描述子
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 创建 BF 匹配器
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# 匹配描述子
matches = bf.match(des1, des2)
# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)
# 绘制匹配结果
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示图像
cv2.imshow('Feature Matching', img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码分析
cv2.NORM_HAMMING:适用于二进制描述子(如 ORB),使用汉明距离进行匹配。crossCheck=True:启用交叉匹配,提升匹配精度。drawMatches():可视化匹配结果,前 10 个最佳匹配。
参数说明
| 参数 | 含义 |
|---|---|
img1 , img2 |
输入图像 |
kp1 , kp2 |
关键点列表 |
des1 , des2 |
描述子矩阵 |
matches |
匹配结果 |
flags |
绘图选项, NOT_DRAW_SINGLE_POINTS 表示不绘制未匹配点 |
本章深入讲解了特征检测与匹配的基本原理与实现方式,介绍了 SIFT、SURF 和 ORB 的优缺点,并通过完整的代码示例展示了如何使用 OpenCV 实现特征点匹配。这些技术为后续的三维重建奠定了坚实的基础。
3. 立体匹配算法(SGM、光流法)
立体匹配算法是三维重建中不可或缺的一部分,它通过分析立体图像对之间的差异,生成视差图,从而为三维空间的深度信息提供基础。本章将深入探讨立体匹配中的核心算法,包括 SGM(Semi-Global Matching) 和 光流法(Optical Flow) ,并分析它们在静态与动态场景中的应用。通过对OpenCV中相关API的使用讲解,结合代码实现,帮助读者掌握如何在实际项目中应用这些算法。
3.1 立体视觉基础
在深入立体匹配算法之前,首先需要理解立体视觉的基本概念与流程。立体视觉模拟了人类双眼的观察方式,通过两个不同视角的图像来计算场景中物体的深度信息。
3.1.1 立体图像对的获取与校正
立体图像对是指从两个不同视角拍摄的同一场景图像,通常由双目相机或移动单目相机拍摄获得。为了确保两个图像之间的像素一一对应,必须进行 立体校正 (Stereo Rectification)。
import cv2
import numpy as np
# 读取左右图像
img_left = cv2.imread('left.jpg', 0)
img_right = cv2.imread('right.jpg', 0)
# 读取相机参数
camera_matrix_left = np.load('K1.npy')
dist_coeffs_left = np.load('D1.npy')
camera_matrix_right = np.load('K2.npy')
dist_coeffs_right = np.load('D2.npy')
R = np.load('R.npy') # 旋转矩阵
T = np.load('T.npy') # 平移向量
# 立体校正
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(
camera_matrix_left, dist_coeffs_left,
camera_matrix_right, dist_coeffs_right,
img_left.shape[::-1], R, T
)
# 图像去畸变并校正
map1x, map1y = cv2.initUndistortRectifyMap(camera_matrix_left, dist_coeffs_left, R1, P1, img_left.shape[::-1], cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(camera_matrix_right, dist_coeffs_right, R2, P2, img_right.shape[::-1], cv2.CV_32FC1)
img_rect_left = cv2.remap(img_left, map1x, map1y, cv2.INTER_LINEAR)
img_rect_right = cv2.remap(img_right, map2x, map2y, cv2.INTER_LINEAR)
逐行解析:
- 第5-6行:读取左右图像为灰度图,简化后续处理。
- 第9-12行:加载相机内参和外参,这些参数通常通过相机标定获得。
- 第15-18行:调用
cv2.stereoRectify函数计算校正所需的旋转和平移矩阵。 - 第21-23行:使用
cv2.initUndistortRectifyMap生成映射表,用于去除图像畸变并进行校正。 - 第25-26行:应用映射表对图像进行去畸变和校正。
参数说明:
camera_matrix_left/right:左/右相机的内参矩阵。dist_coeffs_left/right:左/右相机的畸变系数。R和T:右相机相对于左相机的旋转和平移向量。R1,R2:校正后的旋转矩阵。P1,P2:投影矩阵,用于将图像映射到校正后的坐标系。Q:重投影矩阵,用于从视差图重建三维坐标。
3.1.2 极几何约束与视差图生成
在立体匹配中, 极几何 (Epipolar Geometry)提供了图像点之间的几何约束。通过基础矩阵(Fundamental Matrix)或本质矩阵(Essential Matrix),可以限制匹配点的搜索范围,从而提高匹配效率。
视差图(Disparity Map)表示左右图像中对应像素的水平位移差,反映了场景的深度信息。
graph TD
A[立体图像对] --> B[极几何约束]
B --> C[匹配点搜索]
C --> D[视差图生成]
D --> E[深度图重建]
如上图所示,立体匹配的流程包括图像对的极几何约束、匹配点搜索、视差图生成和深度图重建。
3.2 SGM半全局匹配算法
SGM(Semi-Global Matching)是一种广泛用于立体匹配的算法,其核心思想是在多个方向上进行代价聚合,从而提高匹配精度与鲁棒性。
3.2.1 SGM算法的基本原理与代价聚合策略
SGM算法通过以下步骤实现匹配:
- 代价计算 :采用SAD(Sum of Absolute Differences)或Hamming距离计算代价。
- 代价聚合 :沿多个路径(如4或8个方向)进行代价累加,增强鲁棒性。
- 视差优化 :通过WTA(Winner Take All)策略选择最小代价对应的视差值。
- 后处理 :剔除异常点,提升视差图质量。
# 使用OpenCV中的StereoSGBM算法
window_size = 3
min_disp = 0
num_disp = 16*5 # 必须是16的倍数
stereo = cv2.StereoSGBM_create(
minDisparity=min_disp,
numDisparities=num_disp,
blockSize=window_size,
P1=8 * 3 * window_size**2,
P2=32 * 3 * window_size**2,
disp12MaxDiff=1,
uniquenessRatio=10,
speckleWindowSize=100,
speckleRange=32,
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY
)
disparity = stereo.compute(img_rect_left, img_rect_right).astype(np.float32) / 16.0
逐行解析:
- 第4-5行:设置最小视差与视差范围,视差范围必须是16的倍数。
- 第6-14行:创建
StereoSGBM对象,设置各种参数: blockSize:匹配块大小。P1/P2:平滑惩罚项,控制视差变化。uniquenessRatio:唯一性比率,确保匹配点具有唯一性。speckleWindowSize和speckleRange:用于去除小斑点。- 第16行:调用
compute方法生成视差图。
参数说明:
| 参数名 | 作用说明 |
|---|---|
| minDisparity | 最小视差值,通常为0 |
| numDisparities | 视差范围上限,必须为16的倍数 |
| blockSize | 匹配窗口大小 |
| P1 / P2 | 代价聚合中的惩罚项,影响视差平滑性 |
| uniquenessRatio | 唯一性比率,保证匹配点不重复 |
| speckleWindowSize | 小斑点滤波窗口大小 |
| speckleRange | 小斑点滤波最大视差变化 |
| mode | 匹配模式, STEREO_SGBM_MODE_SGBM_3WAY 更稳定 |
3.3 光流法在动态场景中的应用
与静态立体匹配不同, 光流法 (Optical Flow)用于估计视频序列中像素点的运动轨迹,适用于动态场景的三维重建。
3.3.1 光流法的基本假设与数学模型
光流法基于以下两个假设:
- 亮度恒定性 :在连续帧中,像素点的灰度值保持不变。
- 运动平滑性 :相邻像素点的运动趋势相似。
其数学模型为:
I(x, y, t) = I(x + \Delta x, y + \Delta y, t + \Delta t)
通过泰勒展开,可以得到光流方程:
I_x u + I_y v + I_t = 0
其中 $u = \frac{dx}{dt}$,$v = \frac{dy}{dt}$ 是像素点在x、y方向上的速度。
3.3.2 cv2.calcOpticalFlowFarneback()函数详解
OpenCV提供了 cv2.calcOpticalFlowFarneback 函数实现稠密光流计算,适用于大位移运动。
import cv2
import numpy as np
# 读取两帧图像
prev_frame = cv2.imread('frame1.jpg', 0)
next_frame = cv2.imread('frame2.jpg', 0)
# 计算光流
flow = cv2.calcOpticalFlowFarneback(
prev=prev_frame,
next=next_frame,
flow=None,
pyr_scale=0.5,
levels=3,
winsize=15,
iterations=3,
poly_n=5,
poly_sigma=1.2,
flags=0
)
逐行解析:
- 第5-6行:读取两帧图像并转为灰度图。
- 第9-18行:调用
calcOpticalFlowFarneback函数计算光流: pyr_scale:金字塔缩放比例,控制分辨率。levels:金字塔层数。winsize:窗口大小,越大越平滑。iterations:每层的迭代次数。poly_n和poly_sigma:用于多项式展开的参数。flags:标志位,0表示使用高斯滤波。
参数说明:
| 参数名 | 作用说明 |
|---|---|
| prev / next | 输入的前后两帧图像 |
| flow | 输出的光流向量图,形状为(h, w, 2) |
| pyr_scale | 金字塔缩放比例,控制图像分辨率 |
| levels | 金字塔层数 |
| winsize | 匹配窗口大小,影响平滑性 |
| iterations | 每层迭代次数 |
| poly_n | 多项式展开的邻域大小 |
| poly_sigma | 多项式展开的标准差 |
| flags | 标志位,0表示使用高斯滤波 |
3.3.3 光流法在视频序列三维重建中的初步尝试
光流法可以与立体匹配结合,用于动态场景的三维重建。例如,在双目视频序列中,每一帧都可以通过光流法估计运动,再结合立体匹配生成深度图,从而实现动态三维重建。
graph LR
A[双目视频输入] --> B[图像校正]
B --> C[光流估计]
C --> D[立体匹配]
D --> E[三维点云生成]
E --> F[动态三维重建]
流程说明:
- 图像校正 :对双目视频帧进行校正,确保对应像素对齐。
- 光流估计 :使用
calcOpticalFlowFarneback估计帧间运动。 - 立体匹配 :使用
StereoSGBM生成视差图。 - 三维点云生成 :通过视差图与相机参数计算三维坐标。
- 动态三维重建 :将每一帧的点云融合,形成连续的三维模型。
通过上述流程,可以实现对动态物体或移动场景的实时三维重建,适用于自动驾驶、动作捕捉等应用场景。
本章详细讲解了立体匹配的核心算法SGM与光流法,涵盖了从图像校正到视差图生成、再到动态三维重建的完整流程,并结合OpenCV API给出了具体的代码实现。下一章将继续深入三维重建的核心——基础矩阵与本质矩阵的估计方法,敬请期待。
4. 基础矩阵与本质矩阵估计
4.1 对极几何与相机运动模型
4.1.1 极线、极点与对极约束
对极几何是研究两个不同视角下图像之间几何关系的核心理论之一。在三维重建中,当两台相机从不同位置拍摄同一场景时,如何通过图像上的点建立对应关系,是恢复场景三维结构的关键。
极线(Epipolar Line) 是指,在一幅图像上的某一点,其在另一幅图像中对应点必须位于的直线上。这个直线由相机的相对位姿和该点在第一个图像中的位置决定。
极点(Epipole) 是指,相机光心在另一台相机图像平面上的投影点。例如,第一台相机的光心在第二台相机图像平面上的投影即为极点。
对极约束(Epipolar Constraint) 是指:对于两个相机拍摄的图像中对应的点 $ p_1 $ 和 $ p_2 $,存在一个矩阵 $ F $(基础矩阵)使得:
p_2^T F p_1 = 0
其中 $ p_1, p_2 $ 是齐次坐标表示的图像点。该约束为两图像点之间的几何关系提供了数学表达方式。
对极几何的可视化示意图
graph TD
A[Scene Point P] --> B[Camera 1 Image Plane]
A --> C[Camera 2 Image Plane]
B --> D[Image Point p1]
C --> E[Image Point p2]
D --> F[Epipolar Line in Image 2]
E --> F
D --> G[Epipole e2 in Image 2]
E --> H[Epipole e1 in Image 1]
这个流程图展示了三维空间中的一个点 $ P $,在两个相机成像平面中分别对应点 $ p_1 $ 和 $ p_2 $,其中 $ p_1 $ 在第二幅图像中对应一条极线 $ l_2 $,而所有可能的 $ p_2 $ 都在这条线上。
4.1.2 本质矩阵与基础矩阵的数学定义
本质矩阵(Essential Matrix, E) 是在已知相机内参的情况下,描述两个相机之间相对旋转和平移关系的矩阵。其数学定义如下:
设 $ R $ 为两个相机之间的旋转矩阵,$ t $ 为平移向量(以向量形式表示),则本质矩阵定义为:
E = [t]_{\times} R
其中 $[t]_{\times}$ 是 $ t $ 的反对称矩阵(Skew-symmetric matrix),用于表示叉积操作。
基础矩阵(Fundamental Matrix, F) 则是不依赖于相机内参的版本,用于描述两个图像之间的极几何关系。它与本质矩阵的关系为:
F = K_2^{-T} E K_1^{-1}
其中 $ K_1 $、$ K_2 $ 分别为两台相机的内参矩阵。
本质矩阵和基础矩阵的秩均为2,并且它们都是3×3的矩阵。本质矩阵的自由度为5(3个旋转参数 + 2个归一化平移参数),而基础矩阵的自由度为7。
| 矩阵类型 | 是否依赖相机内参 | 表达关系 | 自由度 |
|---|---|---|---|
| 本质矩阵 E | 是 | 相机位姿关系 | 5 |
| 基础矩阵 F | 否 | 图像点对应关系 | 7 |
4.2 矩阵估计方法
4.2.1 8点法与7点法在基础矩阵估计中的应用
8点法(8-point algorithm) 是一种经典的基础矩阵估计方法。它要求至少有8对匹配点,利用这些点构造线性方程组来求解基础矩阵。
设每对匹配点为 $ (x_i, x’_i) $,则满足 $ x’_i^T F x_i = 0 $。将每个点对代入,可构造一个线性系统:
A f = 0
其中 $ A $ 是一个 $ N \times 9 $ 的矩阵,$ f $ 是 $ F $ 的9个元素组成的列向量。通过奇异值分解(SVD)求解最小奇异值对应的向量即可得到 $ F $。
7点法(7-point algorithm) 是一种非线性方法,适用于仅存在7对匹配点的情况。它通过构造一个三次方程求解可能的 $ F $ 矩阵,通常会得到1~3个解,再通过重投影误差选择最优解。
两种方法的对比:
| 方法 | 所需点数 | 线性/非线性 | 计算复杂度 | 适用场景 |
|---|---|---|---|---|
| 8点法 | 8 | 线性 | 低 | 点对较多的情况 |
| 7点法 | 7 | 非线性 | 高 | 点对较少的情况 |
4.2.2 cv2.findFundamentalMat()函数的使用
OpenCV 提供了 cv2.findFundamentalMat() 函数用于估计基础矩阵。其调用方式如下:
F, mask = cv2.findFundamentalMat(points1, points2, method=cv2.FM_RANSAC, ransacReprojThreshold=3.0, confidence=0.99)
参数说明:
points1,points2:匹配点对,类型为 N×2 的 numpy 数组。method:估计方法,可选cv2.FM_7POINT(7点法)、cv2.FM_8POINT(8点法)、cv2.FM_RANSAC(RANSAC + 8点法)。ransacReprojThreshold:RANSAC中用于判断内点的最大重投影误差。confidence:置信度阈值。
示例代码片段:
import cv2
import numpy as np
# 假设 points1 和 points2 是两组匹配点
points1 = np.array([[100, 200], [150, 250], [300, 100], [250, 300], [200, 150], [180, 180], [220, 220], [240, 240]], dtype=np.float32)
points2 = np.array([[105, 205], [155, 255], [305, 105], [255, 305], [205, 155], [185, 185], [225, 225], [245, 245]], dtype=np.float32)
# 估计基础矩阵
F, mask = cv2.findFundamentalMat(points1, points2, cv2.FM_RANSAC, 3.0, 0.99)
print("Estimated Fundamental Matrix:\n", F)
print("Inliers mask:\n", mask)
逐行分析:
- 第1~2行导入必要的库。
- 第4~5行定义两组匹配点。
- 第8行调用
cv2.findFundamentalMat(),使用 RANSAC 方法提高鲁棒性。 - 第10~11行输出估计出的矩阵和内点掩码。
4.2.3 cv2.findEssentialMat()函数计算本质矩阵
本质矩阵估计依赖于相机的内参矩阵。OpenCV 提供了 cv2.findEssentialMat() 函数来计算本质矩阵:
E, mask = cv2.findEssentialMat(points1, points2, cameraMatrix=K, method=cv2.RANSAC, threshold=1.0)
参数说明:
points1,points2:匹配点对。cameraMatrix:相机内参矩阵 $ K $。method:估计方法,通常使用cv2.RANSAC。threshold:RANSAC 中的重投影误差阈值。
代码示例:
K = np.array([[800, 0, 320],
[0, 800, 240],
[0, 0, 1]], dtype=np.float32)
E, mask = cv2.findEssentialMat(points1, points2, K, cv2.RANSAC, 1.0)
print("Estimated Essential Matrix:\n", E)
逐行分析:
- 第1~3行定义相机内参矩阵 $ K $。
- 第5行调用
cv2.findEssentialMat()。 - 第6行输出估计结果。
4.3 基于RANSAC的异常值剔除
4.3.1 RANSAC算法在矩阵估计中的作用
RANSAC(Random Sample Consensus)是一种鲁棒估计方法,广泛用于处理含有异常值(Outliers)的数据集。在估计基础矩阵或本质矩阵时,特征匹配往往会产生大量错误匹配点(即异常值),直接使用这些数据估计矩阵会导致结果严重偏离真实值。
RANSAC 的基本流程如下:
- 随机选取最小样本数(如8点法选8对点)。
- 使用该样本集估计模型(如基础矩阵)。
- 计算所有点到模型的误差(如重投影误差),将误差小于阈值的点作为内点(Inliers)。
- 若内点数大于当前最优模型的内点数,则更新最优模型。
- 重复步骤1~4若干次,最终选择最优模型。
RANSAC流程图
graph TD
A[开始] --> B[随机选取样本]
B --> C[估计模型]
C --> D[计算误差]
D --> E{误差 < 阈值?}
E -->|是| F[标记为内点]
E -->|否| G[标记为异常值]
F --> H[统计内点数量]
H --> I{是否为最优模型?}
I -->|是| J[更新最优模型]
I -->|否| K[继续迭代]
J --> L[是否达到最大迭代次数?]
K --> L
L -->|否| B
L -->|是| M[输出最优模型]
4.3.2 cv2.RANSAC参数配置与结果分析
在 OpenCV 中,RANSAC 参数主要通过 cv2.findFundamentalMat() 或 cv2.findEssentialMat() 的参数进行配置。以 cv2.findFundamentalMat() 为例:
F, mask = cv2.findFundamentalMat(points1, points2, cv2.FM_RANSAC, 3.0, 0.99)
参数详解:
cv2.FM_RANSAC:使用 RANSAC + 8点法进行估计。3.0:RANSAC 的重投影误差阈值(像素单位)。0.99:置信度,即最终模型包含至少一个内点的概率。
结果分析:
F:估计出的基础矩阵。mask:布尔掩码数组,标记哪些点是内点(True)。
我们可以通过 mask 来筛选出可靠的匹配点:
inliers1 = points1[mask.ravel() == 1]
inliers2 = points2[mask.ravel() == 1]
这样可以提高后续三维重建的精度。
4.3.3 实际图像数据中的矩阵估计效果对比
为了验证 RANSAC 在矩阵估计中的有效性,我们可以使用真实图像数据进行对比实验。
实验步骤:
- 加载两幅图像并提取特征点。
- 使用 ORB 或 SIFT 进行特征匹配。
- 使用
cv2.findFundamentalMat()估计基础矩阵,比较是否使用 RANSAC 的效果。
代码片段:
import cv2
import numpy as np
# 加载图像
img1 = cv2.imread('left.jpg', 0)
img2 = cv2.imread('right.jpg', 0)
# 提取特征点
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 匹配特征点
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# 提取匹配点坐标
points1 = np.float32([kp1[m.queryIdx].pt for m in matches])
points2 = np.float32([kp2[m.trainIdx].pt for m in matches])
# 估计基础矩阵(使用RANSAC)
F_ransac, mask_ransac = cv2.findFundamentalMat(points1, points2, cv2.FM_RANSAC, 3.0, 0.99)
# 估计基础矩阵(不使用RANSAC)
F_8point, mask_8point = cv2.findFundamentalMat(points1, points2, cv2.FM_8POINT)
# 可视化匹配结果
def draw_matches(img1, img2, pts1, pts2, mask=None):
h1, w1 = img1.shape
h2, w2 = img2.shape
vis = np.zeros((max(h1, h2), w1 + w2, 3), np.uint8)
vis[:h1, :w1] = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
vis[:h2, w1:w1 + w2] = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
if mask is None:
mask = np.ones(len(pts1), dtype=bool)
for i, (p1, p2) in enumerate(zip(pts1, pts2)):
if mask[i]:
color = (0, 255, 0)
cv2.line(vis, tuple(p1.astype(int)), (int(p2[0] + w1), int(p2[1])), color)
return vis
# 可视化RANSAC结果
vis_ransac = draw_matches(img1, img2, points1, points2, mask_ransac.ravel())
cv2.imshow('RANSAC Matches', vis_ransac)
# 可视化8点法结果
vis_8point = draw_matches(img1, img2, points1, points2, mask_8point.ravel())
cv2.imshow('8-point Matches', vis_8point)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果对比分析:
| 方法 | 内点比例 | 矩阵估计稳定性 | 匹配精度 |
|---|---|---|---|
| 8点法 | 低 | 低 | 一般 |
| 8点法 + RANSAC | 高 | 高 | 高 |
从可视化结果可以看出,使用 RANSAC 显著提高了匹配点的质量,剔除了大量异常匹配点,使得基础矩阵估计更加稳定和准确。
总结:
- 基础矩阵与本质矩阵是三维重建中两个关键的几何约束矩阵。
- 使用 8点法或7点法可以估计基础矩阵,结合 RANSAC 可有效剔除异常值。
- OpenCV 提供了高效的 API 接口,便于快速实现矩阵估计与匹配点筛选。
- 实验表明,RANSAC 在提高匹配质量方面具有显著优势,是实际应用中不可或缺的技术手段。
5. 三角测量三维坐标计算
5.1 三角化原理与相机位姿恢复
5.1.1 相机投影模型与三维点坐标反投影
在三维重建中,三角化(Triangulation)是通过两个或多个视角下的二维图像点,计算其在三维空间中的对应坐标的过程。这依赖于相机的投影模型和已知的相机位姿(即旋转和平移参数)。
相机的投影模型通常用以下公式表示:
\mathbf{x} = K [R | \mathbf{t}] \mathbf{X}
其中:
- $\mathbf{x}$ 是图像平面上的像素坐标(齐次坐标)。
- $K$ 是相机的内参矩阵,包含焦距、主点坐标等信息。
- $R$ 和 $\mathbf{t}$ 分别是旋转矩阵和平移向量,表示相机的外部参数。
- $\mathbf{X}$ 是三维空间点的坐标(齐次坐标)。
为了进行三角化,我们需要从两个或多个视角中获取同一空间点的图像点,结合相机位姿信息,反推出该点在三维空间中的坐标。OpenCV 提供了 cv2.triangulatePoints() 函数,能够高效地完成这一任务。
5.1.2 cv2.triangulatePoints()函数解析
cv2.triangulatePoints(P1, P2, points1, points2) 函数是 OpenCV 中用于三角化的核心函数。其参数含义如下:
| 参数名 | 类型 | 描述 |
|---|---|---|
P1 , P2 |
numpy.ndarray | 3x4 的投影矩阵(Projection Matrix),形式为 $K [R |
points1 , points2 |
numpy.ndarray | Nx2 的二维图像点集合(浮点类型) |
该函数返回一个 4xN 的数组,表示每个点的齐次三维坐标。
示例代码 :
import cv2
import numpy as np
# 假设已知两个相机的投影矩阵 P1 和 P2
P1 = np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0]])
P2 = np.array([[1, 0, 0, -0.1],
[0, 1, 0, 0],
[0, 0, 1, 0]])
# 假设有两幅图像中对应的特征点
points1 = np.array([[100, 200], [150, 250]], dtype=np.float32)
points2 = np.array([[110, 210], [160, 260]], dtype=np.float32)
# 三角化计算三维坐标
points_4d = cv2.triangulatePoints(P1, P2, points1, points2)
# 转换为非齐次坐标(除以最后一个分量)
points_3d = points_4d[:3] / points_4d[3]
print("三维坐标:\n", points_3d.T)
代码逻辑分析:
- 第1~4行:导入必要的库和定义两个相机的投影矩阵。
P1是单位矩阵,表示第一个相机在原点,P2则是相对于第一个相机有一定平移的第二个相机。 - 第7~10行:定义两组二维图像点,表示同一空间点在两个视角下的投影。
- 第13行:调用
cv2.triangulatePoints()函数,输入两个投影矩阵和对应点,返回齐次三维坐标。 - 第16行:将齐次坐标转换为普通三维坐标(除以第四个元素)。
- 第18行:输出结果。
该代码展示了如何从两个视角的二维点恢复三维坐标。这是后续稀疏点云构建和三维重建的基础。
5.2 三维坐标计算流程
5.2.1 从图像坐标到相机坐标系的转换
在三角化之前,需要将图像坐标转换为相机坐标系下的坐标。这一步通常依赖于相机内参矩阵 $K$。
相机坐标系下的归一化坐标可以通过以下公式获得:
\mathbf{x}_{\text{norm}} = K^{-1} \mathbf{x}
其中:
- $\mathbf{x}_{\text{norm}}$ 是归一化图像坐标(无焦距影响)。
- $K^{-1}$ 是相机内参矩阵的逆。
- $\mathbf{x}$ 是原始图像坐标(齐次坐标)。
示例代码:
def normalize_coordinates(points, K):
"""
将图像坐标转换为归一化相机坐标
:param points: Nx2 的图像点数组
:param K: 3x3 的相机内参矩阵
:return: Nx2 的归一化坐标
"""
points_homogeneous = cv2.convertPointsToHomogeneous(points)
K_inv = np.linalg.inv(K)
normalized_points = []
for point in points_homogeneous:
normalized = K_inv @ point.T
normalized_points.append(normalized[:2].flatten())
return np.array(normalized_points)
# 示例相机内参
K = np.array([[800, 0, 320],
[0, 800, 240],
[0, 0, 1]])
# 假设图像点
points = np.array([[100, 200], [150, 250]], dtype=np.float32)
# 调用函数进行归一化
normalized = normalize_coordinates(points, K)
print("归一化坐标:\n", normalized)
代码分析:
- 第1~8行:定义
normalize_coordinates函数,将二维图像点转换为归一化坐标。 - 第11~15行:定义相机内参矩阵 $K$ 和图像点。
- 第18行:调用函数得到归一化后的坐标。
通过该步骤,可以消除相机焦距的影响,为后续的三角化计算做好准备。
5.2.2 利用本质矩阵恢复相机旋转和平移参数
在已知本质矩阵 $E$ 的情况下,可以恢复出相机的旋转矩阵 $R$ 和平移向量 $\mathbf{t}$。OpenCV 提供了 cv2.recoverPose() 函数来实现这一功能。
示例代码:
E = np.array([[0, 0, 0.1],
[0, 0, 0],
[-0.1, 0, 0]])
points1_norm = np.array([[0.1, 0.2], [0.3, 0.4]])
points2_norm = np.array([[0.15, 0.25], [0.35, 0.45]])
# 恢复 R 和 t
_, R, t, _ = cv2.recoverPose(E, points1_norm, points2_norm)
print("旋转矩阵 R:\n", R)
print("平移向量 t:\n", t)
代码分析:
- 第1~5行:定义本质矩阵 $E$ 和归一化后的图像点。
- 第8行:调用
cv2.recoverPose()函数,输入本质矩阵和归一化点,返回旋转和平移参数。 - 第10~11行:输出结果。
通过此函数,我们可以从匹配点和本质矩阵中恢复出相机的姿态,为三角化提供投影矩阵 $P1 = K[I|0]$ 和 $P2 = K[R|t]$。
5.3 实践中的精度优化
5.3.1 图像噪声对三角化结果的影响
图像噪声是影响三角化精度的关键因素之一。在实际应用中,特征点的检测和匹配不可避免地存在误差,这些误差会直接影响三维坐标的计算精度。
噪声影响分析:
- 误差传播模型 :假设图像点存在小误差 $\Delta \mathbf{x}$,则对应的三维坐标误差 $\Delta \mathbf{X}$ 与相机位姿、焦距等参数有关。
- 数值稳定性 :当两个相机的视差角(即相机间的夹角)较小时,三角化对误差的敏感度更高,容易导致数值不稳定。
示例代码(添加噪声):
def add_noise(points, std_dev=1.0):
noise = np.random.normal(0, std_dev, points.shape)
return points + noise.astype(np.float32)
# 添加噪声
points1_noisy = add_noise(points1, std_dev=2.0)
points2_noisy = add_noise(points2, std_dev=2.0)
# 使用有噪声的点进行三角化
points_4d_noisy = cv2.triangulatePoints(P1, P2, points1_noisy, points2_noisy)
points_3d_noisy = points_4d_noisy[:3] / points_4d_noisy[3]
print("带噪声的三维坐标:\n", points_3d_noisy.T)
代码分析:
- 第1~4行:定义
add_noise函数,模拟图像点检测中的噪声。 - 第7~8行:对原始点添加噪声。
- 第11~13行:使用带噪声的点进行三角化,并输出结果。
通过该实验可以直观地观察到图像噪声对三维坐标的影响。
5.3.2 多视角三角化与误差传播分析
多视角三角化可以提高三维点的精度,因为更多的观测可以提供更多的约束,从而减少单视角误差的影响。
误差传播模型示意:
使用 Mermaid 绘制误差传播流程图:
graph TD
A[图像点检测] --> B[添加噪声]
B --> C[三角化计算]
C --> D[误差传播分析]
D --> E[三维点精度评估]
多视角优化策略:
- 最小二乘法优化 :使用所有视角的图像点构造目标函数,最小化重投影误差。
- 非线性优化(如Bundle Adjustment) :联合优化所有相机位姿和三维点坐标,获得全局最优解。
5.3.3 利用OpenCV实现简单三维坐标计算的完整代码
import cv2
import numpy as np
# 相机内参
K = np.array([[800, 0, 320],
[0, 800, 240],
[0, 0, 1]])
# 投影矩阵 P1 和 P2
P1 = K @ np.hstack((np.eye(3), np.zeros((3, 1))))
P2 = K @ np.hstack((np.eye(3), np.array([[0.1], [0], [0]])))
# 图像点
points1 = np.array([[100, 200], [150, 250]], dtype=np.float32)
points2 = np.array([[110, 210], [160, 260]], dtype=np.float32)
# 三角化
points_4d = cv2.triangulatePoints(P1, P2, points1, points2)
points_3d = points_4d[:3] / points_4d[3]
print("最终三维坐标:\n", points_3d.T)
代码分析:
- 第1~9行:定义相机内参和两个视角的投影矩阵。
- 第12~13行:定义图像点。
- 第16~17行:调用三角化函数并输出结果。
该代码展示了从图像点到三维坐标的完整流程,适用于初学者理解和后续扩展。
总结 :本章详细介绍了三角化原理、OpenCV 中的函数使用、坐标转换流程、误差分析及多视角优化方法。通过代码示例和流程图,读者可以全面掌握如何利用 OpenCV 进行三维坐标计算,并理解其中的关键技术和注意事项。
6. 稀疏三维点云构建
6.1 稀疏点云的意义与应用场景
6.1.1 稀疏点云与稠密点云的区别
三维点云根据密度可以分为稀疏点云和稠密点云。稀疏点云通常由少量的特征点或关键点构成,适用于实时性要求高、计算资源有限的场景,例如SLAM(Simultaneous Localization and Mapping)系统中的视觉里程计(Visual Odometry)。稠密点云则包含大量像素级的三维点,适用于需要高精度重建的场景,如三维建模、医学影像等。
| 特性 | 稀疏点云 | 稠密点云 |
|---|---|---|
| 数据量 | 小 | 大 |
| 计算复杂度 | 低 | 高 |
| 应用场景 | SLAM、VO | 三维建模、AR/VR |
| 获取方式 | 特征点三角化 | 立体匹配、光流法 |
稀疏点云构建的核心在于如何从图像中提取具有代表性的特征点,并通过三角化算法将其转换为三维空间中的点。
6.1.2 在SLAM与视觉里程计中的基础作用
在SLAM系统中,稀疏点云用于构建环境的地图,并通过匹配当前帧与地图中的点来估计相机的运动轨迹。视觉里程计则利用连续帧之间的稀疏点云来计算相机的位姿变化,是实现机器人自主导航的关键技术之一。
在实际应用中,稀疏点云因其计算效率高、内存占用低,常用于实时系统中作为三维重建的初步表示形式。
6.2 基于特征点的点云生成
6.2.1 从二维图像到三维空间的映射
稀疏点云的构建流程通常包括以下几个步骤:
- 图像获取 :从双目相机或单目相机序列中获取图像对。
- 特征提取 :使用SIFT、SURF或ORB等特征检测算法提取图像中的关键点。
- 特征匹配 :在图像对之间进行特征点匹配,找到对应的点对。
- 基础矩阵估计 :利用匹配点对估计基础矩阵(Fundamental Matrix)或本质矩阵(Essential Matrix)。
- 相机位姿恢复 :通过本质矩阵恢复相机的旋转和平移参数。
- 三角化计算 :利用
cv2.triangulatePoints()函数将二维点对映射为三维点。
import cv2
import numpy as np
# 假设已知两个相机的投影矩阵 P1 和 P2
P1 = np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0]])
P2 = np.array([[1, 0, 0, -0.1], # 假设相机沿x轴移动0.1单位
[0, 1, 0, 0],
[0, 0, 1, 0]])
# 假设已经匹配到两个图像中的特征点
points1 = np.array([[100, 200], [150, 250]], dtype=np.float32).T
points2 = np.array([[110, 210], [160, 260]], dtype=np.float32).T
# 三角化计算
points4D = cv2.triangulatePoints(P1, P2, points1, points2)
points3D = points4D[:3] / points4D[3] # 转换为三维坐标
print("三维点坐标:")
print(points3D)
代码解释:
P1和P2分别是两个相机的投影矩阵。points1和points2是两个图像中对应特征点的坐标。cv2.triangulatePoints()函数将二维点对转换为齐次坐标下的三维点(4D)。- 最后通过除以第四个分量将齐次坐标转换为标准三维坐标。
6.2.2 使用OpenCV可视化稀疏点云
OpenCV本身并不直接提供三维点云可视化功能,但可以通过与Open3D或Matplotlib结合实现基本的点云可视化。
以下是一个使用Matplotlib绘制三维点的示例:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 假设 points3D 是上一步得到的三维点坐标
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# 绘制点云
ax.scatter(points3D[0], points3D[1], points3D[2], c='r', marker='o')
ax.set_xlabel('X Axis')
ax.set_ylabel('Y Axis')
ax.set_zlabel('Z Axis')
plt.title('Sparse 3D Point Cloud')
plt.show()
6.3 点云数据的保存与可视化
6.3.1 PLY格式与Open3D库的结合使用
PLY(Polygon File Format)是一种常用的三维模型文件格式,支持点云、网格等多种几何数据的存储。
以下代码展示如何使用Open3D库将稀疏点云保存为PLY文件:
import open3d as o3d
import numpy as np
# 构建点云对象
pcd = o3d.geometry.PointCloud()
# 将 points3D 转换为 numpy 数组并赋值
pcd.points = o3d.utility.Vector3dVector(points3D.T)
# 保存为PLY文件
o3d.io.write_point_cloud("sparse_point_cloud.ply", pcd)
print("点云已保存为 sparse_point_cloud.ply")
Open3D还支持点云的加载与可视化:
# 加载PLY文件
pcd_loaded = o3d.io.read_point_cloud("sparse_point_cloud.ply")
# 可视化
o3d.visualization.draw_geometries([pcd_loaded])
6.3.2 使用OpenCV与PCL库进行点云处理
除了Open3D,PCL(Point Cloud Library)也是一个强大的三维点云处理库。虽然OpenCV本身不集成PCL,但可以通过接口调用PCL进行更复杂的点云处理任务,如滤波、配准、分割等。
安装PCL并结合OpenCV的流程如下:
- 安装PCL库(例如在Ubuntu中使用
sudo apt install libpcl-dev)。 - 使用
pcl::PointCloud类进行点云操作。 - 将OpenCV处理后的点云数据转换为PCL支持的格式。
6.3.3 稀疏点云构建的完整流程示例与代码实现
以下是完整的稀疏点云构建流程示例代码:
import cv2
import numpy as np
import open3d as o3d
# 步骤1:特征检测与匹配
orb = cv2.ORB_create()
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# 假设 img1 和 img2 是两个视角的图像
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# 提取匹配点
points1 = np.float32([kp1[m.queryIdx].pt for m in matches])
points2 = np.float32([kp2[m.trainIdx].pt for m in matches])
# 步骤2:估计基础矩阵并剔除异常值
F, mask = cv2.findFundamentalMat(points1, points2, cv2.FM_RANSAC)
points1 = points1[mask.ravel() == 1]
points2 = points2[mask.ravel() == 1]
# 步骤3:三角化计算三维点
K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) # 相机内参矩阵
E = K.T @ F @ K # 计算本质矩阵
_, R, t, _ = cv2.recoverPose(E, points1, points2, K)
P1 = np.hstack((np.eye(3), np.zeros((3, 1))))
P2 = np.hstack((R, t))
points4D = cv2.triangulatePoints(P1, P2, points1.T, points2.T)
points3D = (points4D[:3] / points4D[3]).T
# 步骤4:保存为PLY文件
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points3D)
o3d.io.write_point_cloud("sparse_point_cloud.ply", pcd)
print("稀疏点云构建完成并保存为PLY文件")
通过上述代码,我们完成了从图像获取、特征匹配、基础矩阵估计、相机位姿恢复、三角化计算到点云保存的完整稀疏点云构建流程。
简介:本程序围绕计算机视觉中的三维重建技术,基于OpenCV库从二维图像中恢复三维信息。内容涵盖特征匹配、立体匹配、基础矩阵与本质矩阵估计、三角测量、稀疏与稠密点云构建、泊松表面重建等关键技术。通过该程序实战,学习者可掌握OpenCV在三维重建中的核心API使用方法,如findHomography、solvePnP、triangulatePoints等,并提升在相机标定、RANSAC去噪等方面的实战能力,适用于SLAM、AR/VR、机器人视觉等领域。
更多推荐

所有评论(0)