【小白深度教程 1.5】手把手教你用立体匹配进行双目深度估计,以及 3D 点云生成(含 Python 代码解读)
在上两节中,我们展示了如何将从激光雷达获取的稀疏点云并进行补全:
【小白深度教程 1.4】手把手教你复现 CompletionFormer 深度补全网络(含代码解读)
【小白深度教程 1.3】使用 BP-Net 深度补全网络,进行 KITTI 稠密点云和图像融合(Depth Completion 实战)
但是,单目毕竟包含模糊性,例如这张广告牌,应当在平面有平滑的深度,但是单目深度往往失败(认为人像是有非平滑深度):
_立体匹配代码如何复现-CSDN博客_files/7d0037fd755540f99ae4e7258d49d49e.png)
因此这次我们尝试使用立体匹配技术,来进行准确的双目深度估计,并将场景转换成 3D 点云:
_立体匹配代码如何复现-CSDN博客_files/6e32de7647ee45c3974f01fe107a11a4.png)
_立体匹配代码如何复现-CSDN博客_files/85a3f025bf784846ad9a8636af3ca886.png)
实验用的数据可以从 [百度网盘] 下载:
链接: https://pan.baidu.com/s/15-R4KeskT79hLHu7nXEsBA
提取码: ym8a
1. 立体匹配的原理
基本原理可以查看之前的教程:
【小白深度教程 1.1】手把手教你双目深度估计(立体匹配),原理讲解和代码实战,使用 OpenCV(包含 Python、C++ 代码)
2. 块匹配算法(Block Matching Algorithm)
这里具体介绍一种立体匹配方法:
import cv2
import numpy as np
import time
import matplotlib.pyplot as plt
def generate_window(row, col, image, blockSize):
window = (image[row:row + blockSize, col:col + blockSize])
return window
def disparitymap(imgL,imgR,dispMap=[]):
# Size of the search window
blockSize = 5
h, w = imgL.shape
dispMap = np.zeros((h, w))
# maximum disparity to search for (Tuned by experimenting)
max_disp = int(w//3)
# Initializing disparity value
dispVal = 0
tic=time.time()
for row in range(0, h - blockSize + 1, blockSize):
for col in range(0, w - blockSize + 1, blockSize):
winR = generate_window(row, col, imgR, blockSize)
sad = 9999
dispVal = 0
for colL in range(col + blockSize, min(w - blockSize, col + max_disp)):
winL = generate_window(row, colL, imgL, blockSize)
tempSad = int(abs(winR- winL).sum())
if tempSad < sad:
sad = tempSad
dispVal = abs(colL - col)
for i in range(row, row + blockSize):
for j in range(col, col + blockSize):
dispMap[i, j] = dispVal
# Updating progress
if (row % 50 == 0):
print('Row number {} Percent complete {} %'.format(row,row*100/h))
toc = time.time()
print('elapsed time... {} mins'.format((toc - tic)/60 ))
#printing the disparity amap
print("Disparity map....\n")
plt.title('Disparity Map')
plt.ylabel('Height {}'.format(dispMap.shape[0]))
plt.xlabel('Width {}'.format(dispMap.shape[1]))
plt.imshow(dispMap,cmap='gray')
plt.show()
return dispMap
2.1 代码中的立体匹配过程概述
- 图像输入:输入左视图(
imgL)和右视图(imgR)。 - 窗口生成:函数
generate_window(row, col, image, blockSize)从图像中截取大小为blockSize × blockSize的窗口。 - 视差计算:函数
disparitymap(imgL, imgR)通过逐像素搜索最佳匹配窗口,计算视差图。
2.2 代码原理及公式
2.2.1. 窗口匹配和代价函数(SAD)
立体匹配的核心是窗口匹配。代码中使用了绝对差值和(Sum of Absolute Differences, SAD)作为代价函数。具体公式为:
SAD = ∑ x = 0 N − 1 ∑ y = 0 M − 1 ∣ I L ( x , y ) − I R ( x − d , y ) ∣ \text{SAD} = \sum_{x=0}^{N-1} \sum_{y=0}^{M-1} |I_L(x, y) - I_R(x - d, y)| SAD=x=0∑N−1y=0∑M−1∣IL(x,y)−IR(x−d,y)∣
其中:
- I L ( x , y ) I_L(x, y) IL(x,y) 表示左图像窗口中的像素值。
- I R ( x − d , y ) I_R(x - d, y) IR(x−d,y) 表示右图像窗口中的像素值,( d ) 为视差值。
- N N N 和 M M M 为窗口的宽度和高度。
通过遍历不同的 d d d 值(从 0 到 max_disp),计算 SAD 最小的 d d d 值,即为该像素的视差值。
2.2.2. 匹配过程
匹配过程的步骤如下:
- 遍历图像中的每一个位置 ( r o w , c o l ) (row, col) (row,col)。
- 从右图像中获取一个大小为
blockSize × blockSize的窗口winR。 - 初始化 SAD 值为一个大数值,表示当前最优匹配的代价。
- 在左图像中从当前位置
col + blockSize开始往左搜索不同的视差值,直到达到max_disp。 - 对于每一个可能的位置,计算 SAD。如果 SAD 比当前最小的 SAD 小,则更新最小 SAD 和对应的视差值。
2.2.3. 视差图生成
一旦找到最佳视差值,这个值会被赋给视差图中的对应位置。整个视差图的生成过程通过重复上述匹配步骤来完成。
2.3 代码的整体算法流程
- 图像窗口遍历:循环遍历图像的每个位置,获取右图像的匹配窗口。
- 视差搜索:在左图像的一个范围内搜索最优匹配窗口,计算 SAD 并找到最优视差。
- 视差赋值:将找到的视差值赋给对应位置的视差图。
2.4 性能与优化
该算法的性能较差,因为每个像素都需要进行窗口匹配,时间复杂度较高。可以考虑使用更高效的方法,如动态规划、图割算法、或卷积神经网络(CNN) 等方法来加速匹配过程。
3. 加载双目图像计算视差
实验用的数据可以从 [百度网盘] 下载:
链接: https://pan.baidu.com/s/15-R4KeskT79hLHu7nXEsBA
提取码: ym8a
首先设置相关参数:
# Factor for downscaling of test images
SCALE = 0.5
#Specify image paths
img_path1 = r'left.png'
img_path2 = r'right.png'
###################
# CAMERA PARAMS
###################
# The focal length of the two cameras, taken from calib.txt
FOCAL_LENGTH = 3979.911
# The distance between the two cameras, taken from calib.txt
X_A=1244.772
X_B=1369.115
Y= 1019.507
DOFFS = X_B-X_A
CAMERA_DISTANCE = 193.001
然后加载图像,计算视差图:
import cv2
import numpy as np
import time
import matplotlib.pyplot as plt
from depth import depth_map
from configs import img_path1, img_path2
from disparity import disparitymap
def generate_window(row, col, image, blockSize):
window = (image[row:row + blockSize, col:col + blockSize])
return window
def disparitymap(imgL,imgR,dispMap=[]):
# Size of the search window
blockSize = 5
h, w = imgL.shape
dispMap = np.zeros((h, w))
# maximum disparity to search for (Tuned by experimenting)
max_disp = int(w//3)
# Initializing disparity value
dispVal = 0
tic=time.time()
for row in range(0, h - blockSize + 1, blockSize):
for col in range(0, w - blockSize + 1, blockSize):
winR = generate_window(row, col, imgR, blockSize)
sad = 9999
dispVal = 0
for colL in range(col + blockSize, min(w - blockSize, col + max_disp)):
winL = generate_window(row, colL, imgL, blockSize)
tempSad = int(abs(winR- winL).sum())
if tempSad < sad:
sad = tempSad
dispVal = abs(colL - col)
for i in range(row, row + blockSize):
for j in range(col, col + blockSize):
dispMap[i, j] = dispVal
# Updating progress
if (row % 50 == 0):
print('Row number {} Percent complete {} %'.format(row,row*100/h))
toc = time.time()
print('elapsed time... {} mins'.format((toc - tic)/60 ))
#printing the disparity amap
print("Disparity map....\n")
plt.title('Disparity Map')
plt.ylabel('Height {}'.format(dispMap.shape[0]))
plt.xlabel('Width {}'.format(dispMap.shape[1]))
plt.imshow(dispMap,cmap='gray')
plt.show()
return dispMap
然后读取双目图像,下采样,并计算视差图:
img = cv2.imread(img_path1,1)
img = downsample_image(img,1)
imgL = Image_processing(img_path1)
imgR = Image_processing(img_path2)
Map = disparitymap(imgL,imgR)
_立体匹配代码如何复现-CSDN博客_files/a042d1527b5f4fffa674e9544675badb.png)
4. 读取相机参数并计算深度,并映射到 3D 点云
在立体匹配中,视差图(Disparity Map) 表示图像中每个像素在左右视图中的水平位移,而 深度图(Depth Map) 则表示每个像素到相机的距离。将视差图转换为深度图的关键在于使用相机的几何特性,结合相机的已知参数进行计算。
4.1 深度计算的公式
深度 Z Z Z 可以通过以下公式从视差 d d d 中计算得到:
Z = f ⋅ B d Z = \frac{f \cdot B}{d} Z=df⋅B
其中:
- Z Z Z 是深度值,即像素到相机的距离。
- f f f 是相机的焦距(focal length)。
- B B B 是两个相机之间的基线距离(baseline distance)。
- d d d 是视差值(disparity)。
4.2 代码详解
代码通过逐像素计算视差值来生成深度图,并将每个像素的位置转换为三维坐标。以下是主要步骤的解释:
-
输入参数:
dispMap: 输入的视差图。orignal_pic: 原始图像,用于获取每个像素的颜色信息。
-
初始化深度图:
- 使用
np.zeros(dispMap.shape)初始化一个与视差图大小相同的深度图depth。
- 使用
-
深度计算:
-
对于每个像素,根据视差值计算其深度:
depth[r, c] = (CAMERA_DISTANCE * FOCAL_LENGTH) / (dispMap[r, c]) -
这个公式直接应用了深度计算公式,其中
CAMERA_DISTANCE和FOCAL_LENGTH分别对应基线距离 ( B ) 和焦距 ( f )。
-
-
三维坐标计算:
-
使用视差值结合图像坐标计算三维坐标 ( (X, Y, Z) ):
ZZ = (CAMERA_DISTANCE * FOCAL_LENGTH) / (disparity + DOFFS) YY = (ZZ / FOCAL_LENGTH) * Yoffset XX = (ZZ / FOCAL_LENGTH) * Xoffset -
其中
DOFFS是一个视差的偏移量,用来校正视差值。 -
Xoffset和Yoffset计算当前像素在三维空间中的相对位置。
-
-
存储三维坐标和颜色信息:
- 每个像素的三维坐标和颜色信息(RGB)都被存储到
coordinates列表中。
- 每个像素的三维坐标和颜色信息(RGB)都被存储到
_立体匹配代码如何复现-CSDN博客_files/bd6736e969af4f10ba01a4a3f796e116.png)
其中,相机参数存储在 pose.txt 文件中:
{
"class_name" : "ViewTrajectory",
"interval" : 29,
"is_loop" : false,
"trajectory" :
[
{
"boundingbox_max" : [ 2381.265821, 1419.1680819999999, 5760.533383 ],
"boundingbox_min" : [ -1606.076206, -1314.9586420000001, 3378.7132350000002 ],
"field_of_view" : 60.0,
"front" : [ 0.12986555585021312, 0.3854355475454066, -0.91355042339877879 ],
"lookat" : [ 387.5948075, 52.104719999999929, 4569.6233090000005 ],
"up" : [ -0.067080574092563891, 0.92265716895578631, 0.3797419454760112 ],
"zoom" : 0.76000000000000012
}
],
"version_major" : 1,
"version_minor" : 0
}
4.3 映射到点云并存储到本地
将深度图映射到 3D 点云是计算机视觉和立体视觉应用中的一个关键步骤。3D 点云由大量的三维点组成,每个点都包含其在空间中的 X , Y , Z X, Y, Z X,Y,Z 坐标。这些点可以用来表示场景中的物体形状和深度信息。下面介绍如何将深度图映射为 3D 点云的原理和实现方法。
要将深度图转换为3D点云,需要利用相机的内参和深度信息。主要步骤包括:
-
像素坐标转换为图像坐标:
每个像素 ( u , v ) (u, v) (u,v) 的图像坐标可以通过深度值转换为世界坐标 ( X , Y , Z ) (X, Y, Z) (X,Y,Z)。 -
利用相机的内参矩阵:
使用相机内参矩阵将像素坐标和深度值映射到3D坐标。相机内参矩阵通常为:
K = [ f x 0 c x 0 f y c y 0 0 1 ] K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} K= fx000fy0cxcy1
其中:
- f x f_x fx 和 f y f_y fy 是焦距(分别对应于x和y方向的像素尺寸)。
- c x c_x cx 和 c y c_y cy 是图像中心的坐标(光学中心)。
- 从深度图计算3D坐标:
根据以下公式计算每个像素的3D坐标:
X = ( u − c x ) × Z f x X = \frac{(u - c_x) \times Z}{f_x} X=fx(u−cx)×Z
Y = ( v − c y ) × Z f y Y = \frac{(v - c_y) \times Z}{f_y} Y=fy(v−cy)×Z
Z = depth ( u , v ) Z = \text{depth}(u, v) Z=depth(u,v)
coordinates = depth_map(Map, img)
print('\n Creating the output file... \n')
create_output(coordinates, 'praxis.ply')
_立体匹配代码如何复现-CSDN博客_files/120b98f924df4546858b530aa6bd937c.png)
可以看到,依然有一些匹配错误的地方以及一些离散点
下一节中我们将介绍,如何使用深度学习方法进行优化
1万+
被折叠的
条评论
为什么被折叠?
_立体匹配代码如何复现-CSDN博客_files/weixin.png)
_立体匹配代码如何复现-CSDN博客_files/zhifubao.png)
_立体匹配代码如何复现-CSDN博客_files/jingdong.png)
_立体匹配代码如何复现-CSDN博客_files/recharge.png)