【小白深度教程 1.5】手把手教你用立体匹配进行双目深度估计,以及 3D 点云生成(含 Python 代码解读)

在上两节中,我们展示了如何将从激光雷达获取的稀疏点云并进行补全:

【小白深度教程 1.4】手把手教你复现 CompletionFormer 深度补全网络(含代码解读)

【小白深度教程 1.3】使用 BP-Net 深度补全网络,进行 KITTI 稠密点云和图像融合(Depth Completion 实战)

但是,单目毕竟包含模糊性,例如这张广告牌,应当在平面有平滑的深度,但是单目深度往往失败(认为人像是有非平滑深度):

在这里插入图片描述

因此这次我们尝试使用立体匹配技术,来进行准确的双目深度估计,并将场景转换成 3D 点云:

在这里插入图片描述
在这里插入图片描述
实验用的数据可以从 [百度网盘] 下载:

链接: 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 代码中的立体匹配过程概述

  1. 图像输入:输入左视图(imgL)和右视图(imgR)。
  2. 窗口生成:函数 generate_window(row, col, image, blockSize) 从图像中截取大小为 blockSize × blockSize 的窗口。
  3. 视差计算:函数 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=0N1y=0M1IL(x,y)IR(xd,y)

其中:

  • I L ( x , y ) I_L(x, y) IL(x,y) 表示左图像窗口中的像素值。
  • I R ( x − d , y ) I_R(x - d, y) IR(xd,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 代码的整体算法流程

  1. 图像窗口遍历:循环遍历图像的每个位置,获取右图像的匹配窗口。
  2. 视差搜索:在左图像的一个范围内搜索最优匹配窗口,计算 SAD 并找到最优视差。
  3. 视差赋值:将找到的视差值赋给对应位置的视差图。

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)

在这里插入图片描述

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=dfB

其中:

  • Z Z Z 是深度值,即像素到相机的距离。
  • f f f 是相机的焦距(focal length)。
  • B B B 是两个相机之间的基线距离(baseline distance)。
  • d d d 是视差值(disparity)。

4.2 代码详解

代码通过逐像素计算视差值来生成深度图,并将每个像素的位置转换为三维坐标。以下是主要步骤的解释:

  1. 输入参数:

    • dispMap: 输入的视差图。
    • orignal_pic: 原始图像,用于获取每个像素的颜色信息。
  2. 初始化深度图:

    • 使用 np.zeros(dispMap.shape) 初始化一个与视差图大小相同的深度图 depth
  3. 深度计算:

    • 对于每个像素,根据视差值计算其深度:

      depth[r, c] = (CAMERA_DISTANCE * FOCAL_LENGTH) / (dispMap[r, c])
      
    • 这个公式直接应用了深度计算公式,其中 CAMERA_DISTANCEFOCAL_LENGTH 分别对应基线距离 ( B ) 和焦距 ( f )。

  4. 三维坐标计算:

    • 使用视差值结合图像坐标计算三维坐标 ( (X, Y, Z) ):

      ZZ = (CAMERA_DISTANCE * FOCAL_LENGTH) / (disparity + DOFFS)
      YY = (ZZ / FOCAL_LENGTH) * Yoffset
      XX = (ZZ / FOCAL_LENGTH) * Xoffset
      
    • 其中 DOFFS 是一个视差的偏移量,用来校正视差值。

    • XoffsetYoffset 计算当前像素在三维空间中的相对位置。

  5. 存储三维坐标和颜色信息:

    • 每个像素的三维坐标和颜色信息(RGB)都被存储到 coordinates 列表中。

在这里插入图片描述

其中,相机参数存储在 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点云,需要利用相机的内参和深度信息。主要步骤包括:

  1. 像素坐标转换为图像坐标:
    每个像素 ( u , v ) (u, v) (u,v) 的图像坐标可以通过深度值转换为世界坐标 ( X , Y , Z ) (X, Y, Z) (X,Y,Z)

  2. 利用相机的内参矩阵:
    使用相机内参矩阵将像素坐标和深度值映射到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 是图像中心的坐标(光学中心)。
  1. 从深度图计算3D坐标:
    根据以下公式计算每个像素的3D坐标:

X = ( u − c x ) × Z f x X = \frac{(u - c_x) \times Z}{f_x} X=fx(ucx)×Z

Y = ( v − c y ) × Z f y Y = \frac{(v - c_y) \times Z}{f_y} Y=fy(vcy)×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')

在这里插入图片描述
可以看到,依然有一些匹配错误的地方以及一些离散点

下一节中我们将介绍,如何使用深度学习方法进行优化

评论  1
成就一亿技术人!
拼手气红包 6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠  查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额 3.43前往充值 >
需支付: 10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小寒学姐学AI

有用的话可以请我喝一杯咖啡~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付: ¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值