实验要求:
- 从理论角度,分析以窗口代价计算视差的原理
- 实现NCC 视差匹配方法,即给定左右两张视图,根据NCC计算视差图
- 分析不同窗口值对匹配结果的影响,重点考查那些点(或者哪些类型的点)在不同窗口大小下的匹配精度影响
立体视觉( StereoVision)是一个多视图成像的特殊例子。人之所以可以看到世界是得益于我们的视觉系统。我们要想让机器人跟人一样可以分辨物体,那我们需要给机器人的不仅是1.能够识别物体的系统,且这种系统要2.能够判断出障碍物距离有多远。
立体视觉通过将图像扭曲到公共的平面上,使外极线位于图像行上,最后任何立体照相机设置都能得到矫正(我们通常构建立体平台来产生经过矫正的图像对)。
立体视觉,即使用两台只有水平(向一侧)偏移的照相机观测同一场景。当照相机的位置如上设置,两幅图像具有相同的图像平面,图像的行是垂直对齐的,那么称图像对是经过矫正的。该设置在机器人学中很常见,常被称为立体平台。
假设两幅图像经过了矫正,那么对应点的寻找限制在图像的同一行上。一旦找到对应点,由于深度是和偏移成正比的,那么深度(Z 坐标)可以直接由水平偏移来计算,
其中,f 是经过矫正图像的焦距,b 是两个照相机中心之间的距离,xl 和 xr 是左右两幅图像中对应点的 x 坐标。分开照相机中心的距离称为基线。
立体重建(致密深度重建)就是恢复深度图(视差图),图像中每个像素的深度(视差)都需要计算出来。
在基于归一化互相关的立体重建算法(NCC)中,会对于每个像素尝试不同的偏移,并按照局部图像周围归一化的互相关值,选择具有最好分数的偏移,然后记录下该最佳偏移。因为每个偏移在某种程度上对应于一个平面,所以该过程称为扫平面法。虽然该方法并不是立体重建中最好的方法,但是非常简单,通常也会得出令人满意的结果。当密集地应用在图像中时,归一化的互相关值可以很快地计算出来。我们使用每个像素周围的图像块(根本上说,是局部周边图像)来计算归一化的互相关。以上,我们可以在像素周围重新写出公式:
在前面我们1.使用每个像素周围的图像块(局部周边图像)来计算归一化的互相关。跳过归一化常数,然后对2.该像素周围局部像素块中的像素求和。然后,对3.图像中的每个像素进行该操作。这三个求和操作是在局部图像块区域上进行的,使用图像滤波器来快速计算。
通过标定好的双目相机采集图像,也可以用两个单目相机来组合成双目相机。
校正的目的是使两帧图像极线处于水平方向,或者说是使两帧图像的光心处于同一水平线上。通过校正极线方便后续的NCC操作。
这里是NCC匹配算法的主要步骤,匹配方法如上所述,右视图中与左视图待测像素同一水平线上相关性最高的即为最优匹配。完成匹配后,需要记录其视差d,即待测像素水平方向xl与匹配像素水平方向xr之间的差值 d=xr-xl,最终我们可以得到一个与原始图像尺寸相同的视差图D。
通过上述匹配结果得到的视差图D,我们可以很简单的利用相似三角形反推出以左视图为参考系的深度图。计算原理如下:
# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
import cv2
from numpy import *
from numpy.ma import array
from scipy.ndimage import filters
import scipy.misc
def plane_sweep_ncc(im_l,im_r,start,steps,wid):
""" 使用归一化的互相关计算视差图像 """
m,n = im_l.shape
# 保存不同求和值的数组
mean_l = zeros((m,n))
mean_r = zeros((m,n))
s = zeros((m,n))
s_l = zeros((m,n))
s_r = zeros((m,n))
# 保存深度平面的数组
dmaps = zeros((m,n,steps))
# 计算图像块的平均值
filters.uniform_filter(im_l,wid,mean_l)
filters.uniform_filter(im_r,wid,mean_r)
# 归一化图像
norm_l = im_l - mean_l
norm_r = im_r - mean_r
# 尝试不同的视差
for displ in range(steps):
# 将左边图像移动到右边,计算加和
filters.uniform_filter(np.roll(norm_l, -displ - start) * norm_r, wid, s) # 和归一化
filters.uniform_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, s_l)
filters.uniform_filter(norm_r*norm_r,wid,s_r) # 和反归一化
# 保存 ncc 的分数
dmaps[:,:,displ] = s / sqrt(s_l * s_r)
# 为每个像素选取最佳深度
return np.argmax(dmaps, axis=2)
def plane_sweep_gauss(im_l,im_r,start,steps,wid):
""" 使用带有高斯加权周边的归一化互相关计算视差图像 """
m,n = im_l.shape
# 保存不同加和的数组
mean_l = zeros((m,n))
mean_r = zeros((m,n))
s = zeros((m,n))
s_l = zeros((m,n))
s_r = zeros((m,n))
# 保存深度平面的数组
dmaps = zeros((m,n,steps))
# 计算平均值
filters.gaussian_filter(im_l,wid,0,mean_l)
filters.gaussian_filter(im_r,wid,0,mean_r)
# 归一化图像
norm_l = im_l - mean_l
norm_r = im_r - mean_r
# 尝试不同的视差
for displ in range(steps):
# 将左边图像移动到右边,计算加和
filters.gaussian_filter(np.roll(norm_l, -displ - start) * norm_r, wid, 0, s) # 和归一化
filters.gaussian_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, 0, s_l)
filters.gaussian_filter(norm_r*norm_r,wid,0,s_r) # 和反归一化
# 保存 ncc 的分数
dmaps[:,:,displ] = s / np.sqrt(s_l * s_r)
# 为每个像素选取最佳深度
return np.argmax(dmaps, axis=2)
im_l = array(Image.open(r'C://Users//yangyang//Desktop//image1//pic1.jpg').convert('L'), 'f')
im_r = array(Image.open(r'C://Users//yangyang//Desktop//image1//pic2.jpg').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 9
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
scipy.misc.imsave('depth.png',res)
show()
参考博客:
https://www.jianshu.com/p/fd51fac14394
https://blog.csdn.net/TeFuirnever/article/details/90760179