立体视差 ,亦称立体视像、立体知觉。基于双眼视差所 获得的深度知觉。
立体视差的测量包括三个步骤:
(1)必须从一幅图像中选出位于场景中一个表面上的某一特定位置;
(2)必须在另一幅图像中鉴别出同一个位置;
(3)测出这两个对应像点之间的视差。
在一组重复摄影的两张照片上(立体模式),同一地物的影象,沿着摄影基线(摄影地点和下一个摄影地点之间的飞行方向线)方向位置变换,这个变化量叫“立体视差”。
立体视差在每张照片上,从那点到照片基线(临接摄影地点与照片的摄影地点的像连结的直线),下垂线的足和主点(相片中心)之间叫距离之和。这是把相对两个照片的基线方向,使其成为一直线,按摄影顺序排列时,和相应的同一地物影象间的两个照片主点之间的间隔差。
一个多视图成像的特殊例子是立体视觉(或者立体成像),即使用两台只有水平(向一侧)偏移的照相机观测同一场景。当照相机的位置只有水平偏移观测同一场景时,两幅图像具有相同的图像平面,图像的行是垂直对齐的,那么称图像对是经过矫正的。该设置在机器人学中很常见,常被称为立体平台。
极线矫正:通过将图像扭曲到公共的平面上,使外极线位于图像行上,任何立体照相机设置都能得到矫正(我们通常构建立体平台来产生经过矫正的图像对)。
假设两幅图像经过了矫正,那么对应点的寻找限制在图像的同一行上。一旦找到对应点,由于深度是和偏移成正比的,那么深度(Z 坐标)可以直接由水平偏移来计算,如下图所示:
其中, f f f 是经过矫正图像的焦距, T T T是两个照相机中心之间的距离, x l x_l xl 和 x r x_r xr 是左右两幅图像中对应点的 x x x坐标。分开照相机中心的距离称为基线。
根据上图可以得到相似三角形:
Δ ( p l , p , p r ) ∼ Δ ( O l , p , O r ) \Delta (p_l,p,p_r)\sim \Delta (O_l,p,O_r) Δ(pl,p,pr)∼Δ(Ol,p,Or)
根据相似三角形的性质可以得出如下公式:
T + x l − x r T = Z − f Z \frac{T+x_l-x_r}{T} = \frac{Z-f}{Z} TT+xl−xr=ZZ−f
所以深度(Z 坐标)的计算如下:
Z = f T x r − x l Z=f\frac{T}{x_r-x_l} Z=fxr−xlT
立体重建(有时称为致密深度重建)就是恢复深度图(或者相反,视差图),图像中每个像素的深度(或者视差)都需要计算出来。
在立体重建算法中,我们将对于每个像素尝试不同的偏移,并按照局部图像周围归一化的互相关值,选择具有最好分数的偏移,然后记录下该最佳偏移。因为每个偏移在某种程度上对应于一个平面,所以该过程有时称为扫平面法。
归一化互相关(NCC)算法的原理如下:如有校正过的两帧图像 I 1 I_1 I1,、 I 2 I_2 I2,NCC算法对图像 I 1 I_1 I1一个待匹配像素构建n*n匹配窗口,在图像 I 2 I_2 I2极线上对每一个像素构建匹配窗口与待匹配像素匹配窗口计算相关性,相关性最高的视为最优匹配。由于NCC匹配流程是通过在同一行中查找最优匹配,因此它可以并行处理,对于运行效率有一定的提升。
我们使用每个像素周围的图像块(根本上说,是局部周边图像)来计算归一化的互相关。针对图像,像素周围重新写出公式中的NCC,如下所示:
n c c ( I 1 , I 2 ) = ∑ x ( I 1 ( x ) − u 1 ) ( I 2 ( x ) − u 2 ) ( ∑ x ( I 1 ( x ) − u 1 ) 2 ∑ x ( I 2 ( x ) − u 2 ) 2 ) ncc(I_1,I_2)=\frac{\sum_{x}(I_1(x)-u_1)(I_2(x)-u_2)}{\sqrt(\sum_{x}(I_1(x)-u_1)^2\sum_{x}(I_2(x)-u_2)^2)} ncc(I1,I2)=(∑x(I1(x)−u1)2∑x(I2(x)−u2)2)∑x(I1(x)−u1)(I2(x)−u2)
经过上述分析,可以得到计算视差图的基本步骤如下:
在本次实验中,首先实现视差图的计算,其次改变窗口值(wid)观察实验结果并分析窗口值(wid)对视差图的影响。
from PIL import Image
from pylab import *
import numpy as np
import scipy.ndimage.filters as filters
import scipy.misc
# 使用归一化的互相关计算视差图像
def plane_sweep_ncc(im_l,im_r,start,steps,wid):
m, n = im_l.shape
# 保存不同求和值的数组
mean_l = np.zeros((m, n))
mean_r = np.zeros((m, n))
s = np.zeros((m, n))
s_l = np.zeros((m, n))
s_r = np.zeros((m, n))
# 保存深度平面的数组
dmaps = np.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(roll(norm_l, -displ - start) * norm_r, wid, s) # 和归一化
filters.uniform_filter(roll(norm_l, -displ - start) * roll(norm_l, -displ - start), wid, s_l)
filters.uniform_filter(norm_r * norm_r, wid, s_r) # 和反归一化
# 保存ncc 的分数
dmaps[:, :, displ] = s / np.sqrt((s_l * s_r)+1e-5)
# 为每个像素选取最佳深度
return 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(roll(norm_l,-displ-start)*norm_r,wid,0,s) # 和归一化
filters.gaussian_filter(roll(norm_l,-displ-start)*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 argmax(dmaps,axis=2)
if __name__ == '__main__':
im_l = array(Image.open('data/3.jpg').convert('L'), 'f')
im_r = array(Image.open('data/4.jpg').convert('L'), 'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 9
res1 = plane_sweep_ncc(im_l, im_r, start, steps, wid)
scipy.misc.imsave('depth1.png', res1)
# scipy.misc.imsave('depth2wid'+str(wid)+'.png', res1)
res2 = plane_sweep_gauss(im_l, im_r, start, steps, wid)
scipy.misc.imsave('depth2.png', res2)
① 第一组图像的NCC均匀滤波器视差图和NCC高斯滤波器视差图结果
NCC均匀滤波器视差图:wid=9
NCC高斯滤波器视差图:wid=3
② 第一组图像的NCC均匀滤波器视差图和NCC高斯滤波器视差图结果
NCC均匀滤波器视差图:wid=27
NCC高斯滤波器视差图:wid=9
以上实验结果是两组图像分别实现均匀滤波器计算视差和高斯滤波器计算视差的方法,可以看出:
与均值滤波版本相比,高斯滤波版本具有较少的噪声,但缺少很多细节信息。 对于第一组图像来说均匀滤波器计算出的视差图较为容易观察一些,而对于第二组图像来说其均匀滤波器计算出的视差图太过杂乱,使用高斯滤波器计算的视差图较为平滑,减少了均匀滤波器的杂乱感更容易观察一些。
分析原因如下:
均匀滤波器给定正方形图像块中所有像素相同的权值,所以可以观察到使用均匀滤波器得到的视差图没有那么平滑。而高斯滤波器高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到,所以而后使用高斯滤波器替换均匀滤波器,可以看到产生了更加平滑视差图。
均匀滤波器和高斯滤波器的区别:
其窗口模板的系数和均值滤波器不同,均值滤波器的模板系数都是相同的为1;而高斯滤波器的模板系数,则随着距离模板中心的增大而系数减小。所以,高斯滤波器相比于均值滤波器对图像个模糊程度较小。
对于接下来考虑wid值对于视差图计算的影响进行实验,所以选用较为容易观察的数据进行实验,即第一组图像中的NCC均匀滤波器的方法。
观察代码可知:wid值在主函数中进行赋值,从而作为参数传给计算视差图的函数,在计算视差图的函数中再次将wid值作为参数传给均匀滤波器的方法。即wid值的影响应该为:先影响滤波结果,再接着滤波结果影响视差图的计算,所以在进行分析的时候应首先考虑wid值对滤波的影响,再考虑滤波对是视差图的影响。
随着wid值的改变,即改变了滤波器的模板大小,对于均值滤波器来说,经过滤波后图像的每一个像素的灰度值都是原图像中窗口值大小内的所有灰度值的均值,可以得到结论:当wid值越小,得到的像素值越不能参考周围像素点的灰度值;当wid值越大,得到的像素值越能参考周围像素点的灰度值。
实验结果分为两部分:第一部分为不同wid值整合在一起进行对比的图像,如下六张图分别是wid不同值的图像整合在一张图中,用来观察轮廓及大致影响。第二部分为六张不同wid值的细节图,用于观察细节。
在观察实验结果的时候需注意:灰度值越小的地方视差越小,即距离照相机越近。
(一) 第一部分
由上图观察可得:随着wid值的增大,图像深度信息的图像的轮廓逐渐明显,可以大致看出距离照相机最近的物体即为台灯,但较远处的物体就无法分辨。
可以得出结论:
wid值越大,产生的深度图噪声小,然而计算代价高。模板太大会丢失物体边缘上的细节。
进而思考:wid值影响了滤波结果,滤波结果影响了NCC的匹配度的计算。当wid值较小,滤波结果就较为局限,进而NCC的匹配度就会不准确,然后在计算视差的时候可能造成匹配到的不是同一特征点,最后计算出的视差会有误差,视差图就较为杂乱不易观察;在wid值较大的时候,滤波结果就较为开放,可以考虑到周围像素的影响,进而降低NCC匹配度的误差,得到的视差图轮廓明显,易观察。
(二) 第二部分
① wid=3
② wid=5
③ wid=7
④ wid=9
⑤ wid=11
⑥ wid=13
在这六幅图像中,选取台灯为观察点截取局部图像如下:
3/9 | 5/11 | 7/13 |
---|---|---|
可以看到当wid值较小的时候台灯的两根支架的细节信息是很丰富的,虽然当wid=3时整张图像很杂乱,无法抓住重点,担当截取出台灯的部分可以看到其细节是很丰富的,随着wid值的增大台灯两根支架的细节信息逐渐消失。
由于wid值影响了滤波结果,滤波结果影响了NCC的匹配度的计算。可以分析:当wid值较小,滤波结果仅仅只受自身和周围较少像素的影响,所以就会保留更多自身的特征性在,进行NCC的匹配计算视差的时候细节信息就会更丰富一些,但也可以看到当wid值较小的时候视差图是很杂乱的,无法直接观察出视差的区别。;在wid值较大的时候,可以考虑到周围像素的影响,可以使自身和周围像素更好地融合,得到的视差图的轮廓信息较为明显,易观察。
总结以上所有分析过程可以得到如下实验结论:
1.在进行实验的过程中遇到如下报错:
RuntimeError: output shape not correct
经过排查发现同一组的两幅图像尺寸不一致如下:
使用python库中图像处理的函数更改图像尺寸大小即可。
2.在进行实验的过程中遇到如下警告:
RuntimeWarning: divide by zero encountered in true_divide
dmaps[:, :, displ] = s / np.sqrt((s_l * s_r))
其原因大致时因为根号下的数不能为0,所以将改行代码更改为:
dmaps[:, :, displ] = s / np.sqrt((s_l * s_r)+1e-5)
为根号下的数字加上1e-5,其数值不大,对原本数据也不会造成影响,所以造成的影响不大,同时也解决了根号下不能为0的问题。