从‘双目交汇’到‘视差图’:用Python+OpenCV手把手复现一个简易的立体匹配流程(附代码)
·
从‘双目交汇’到‘视差图’:用Python+OpenCV手把手复现一个简易的立体匹配流程
当我们用双眼观察世界时,大脑会自动计算左右眼图像的微小差异,从而感知深度——这就是 双目立体视觉 的核心原理。在计算机视觉领域,通过算法模拟这一过程,可以从两张不同视角拍摄的图像中恢复三维信息。本文将带你用Python和OpenCV一步步实现这个神奇的过程。
1. 环境准备与数据加载
1.1 安装必要的库
确保你的Python环境(建议3.8+)已安装以下库:
pip install opencv-python numpy matplotlib
1.2 获取测试图像
Middlebury数据集是立体匹配的标准测试集。我们使用经典的"Teddy"图像对:
import cv2
import numpy as np
# 加载左右视图
img_left = cv2.imread('teddy_left.png', cv2.IMREAD_GRAYSCALE)
img_right = cv2.imread('teddy_right.png', cv2.IMREAD_GRAYSCALE)
# 显示图像
import matplotlib.pyplot as plt
plt.figure(figsize=(10,4))
plt.subplot(121), plt.imshow(img_left, 'gray'), plt.title('左视图')
plt.subplot(122), plt.imshow(img_right, 'gray'), plt.title('右视图')
plt.show()
提示:如果使用KITTI数据集,需要注意图像已经过极线校正,可直接进行立体匹配
2. 极线校正原理与实现
2.1 为什么需要极线校正
原始双目图像中,对应点可能出现在任意位置。极线校正后:
- 对应点仅出现在同一水平线上
- 视差计算简化为水平方向搜索
- 计算效率大幅提升
2.2 OpenCV实现
# 假设我们已经获得相机参数
stereo = cv2.StereoBM_create(numDisparities=64, blockSize=15)
# 计算视差图
disparity = stereo.compute(img_left, img_right)
# 可视化
plt.imshow(disparity, 'jet'), plt.colorbar()
plt.title('原始视差图'), plt.show()
校正前后的图像对比:
| 校正前 | 校正后 |
|---|---|
| 对应点位置任意 | 对应点在同一水平线 |
| 计算复杂度高 | 只需水平搜索 |
3. 代价计算与聚合
3.1 AD-Census混合代价
结合绝对差(AD)和Census变换的优点:
def ad_cost(left, right, d):
return np.abs(left - np.roll(right, d))
def census_transform(img, window=3):
center = window//2
census = np.zeros_like(img)
for i in range(center, img.shape[0]-center):
for j in range(center, img.shape[1]-center):
census[i,j] = (img[i-center:i+center+1, j-center:j+center+1] > img[i,j]).astype(np.uint8).sum()
return census
def ad_census_cost(left, right, max_disp, alpha=0.5):
cost_volume = np.zeros((left.shape[0], left.shape[1], max_disp))
census_left = census_transform(left)
census_right = census_transform(right)
for d in range(max_disp):
ad = ad_cost(left, right, d)
census = np.abs(census_left - np.roll(census_right, d))
cost_volume[:,:,d] = alpha*ad + (1-alpha)*census
return cost_volume
3.2 代价聚合示例
简单的Box Filter实现:
def box_filter(cost_volume, radius=2):
filtered = np.zeros_like(cost_volume)
for d in range(cost_volume.shape[2]):
filtered[:,:,d] = cv2.boxFilter(cost_volume[:,:,d], -1, (2*radius+1, 2*radius+1))
return filtered
4. 视差计算与优化
4.1 WTA(赢者通吃)策略
def wta(cost_volume):
return np.argmin(cost_volume, axis=2)
4.2 视差后处理
常见的优化步骤包括:
- 左右一致性检查
- 亚像素优化
- 中值滤波去噪
- 空洞填充
实现示例:
def post_process(disparity):
# 中值滤波
disparity = cv2.medianBlur(disparity.astype(np.float32), 3)
# 空洞填充
mask = disparity == 0
disparity[mask] = np.median(disparity[~mask])
return disparity
5. 完整流程与效果评估
5.1 整合完整流程
def stereo_matching(left, right, max_disp=64):
# 代价计算
cost = ad_census_cost(left, right, max_disp)
# 代价聚合
cost = box_filter(cost, radius=2)
# WTA视差计算
disp = wta(cost)
# 后处理
disp = post_process(disp)
return disp
5.2 效果对比
将我们的结果与OpenCV内置算法比较:
| 方法 | 运行时间 | 主观质量 |
|---|---|---|
| 本文实现 | 中等 | 边缘清晰,有噪声 |
| SGBM | 较慢 | 平滑,细节保留好 |
| BM | 快 | 块状效应明显 |
在实际项目中,可以根据需求选择不同的代价计算和聚合方法。对于实时性要求高的场景,可以尝试CUDA加速或简化算法流程。
更多推荐


所有评论(0)