一、简述
立体视觉是一种计算机视觉技术,其目的是从两幅或两幅以上的图像中推理出图像中每个像素点的深度信息。
二、原理
立体视觉借鉴了人类双眼的“视差”原理,即左、右眼对于真实世界中某一物体的观测是存在差异的,我们的大脑正是利用了左、右眼的差异,使得我们能够辨识物体的远近。左、右眼的差异在立体视觉技术中,我们把它称作“视差(值)”。相应地,利用左、右图像中所有位置的视差,便可生成一幅视差图。
看图找不同(下面这两幅图实际是两个相机从不同位置拍摄的同一场景)。当然,由于这两幅图不同的地方太多,所以一眼便能找到很多的不同之处。我们这里来看两处明显的差异:第一,左图中最左侧的圆锥在右图中消失了;第二,左图中最右侧的杯子在右图中有明显的移位。实际上,如果我们把右下角的杯子看做一个质点,它们的水平(默认图像已做完校正和极线约束,这句看不懂可以暂时跳过,不影响理解)移位即为视差(值)。
刚才我们把一个完整的杯子看做一个质点,能明显看出杯子在左右的移位,那么,根据我们的直觉,可以想象对于左图像的每一个像素点在右图像中都发生了移位,理论上,左图像中每一个像素点都能在右图像中找到唯一一个像素点与之匹配,然后计算两者的水平位移差值即得到该像素点的视差,依次迭代完整幅图像的所有像素点便可获得视差图。
视差图中每一个像素点的值是空间中相机光心到某一3D点的距离成比例所赋予的值
在该立体重建算法中,我们将对于每个像素尝试不同的偏移,并按照局部图像周围
归一化的互相关值,选择具有最好分数的偏移,然后记录下该最佳偏移。因为每个
偏移在某种程度上对应于一个平面,所以该过程有时称为扫平面法。虽然该方法并
不是立体重建中最好的方法,但是非常简单,通常会得出令人满意的结果。
当密集地应用在图像中时,归一化的互相关值可以很快地计算出来。这和我们在第
2 章中应用于稀疏点对应的不同。我们使用每个像素周围的图像块(根本上说,是
局部周边图像)来计算归一化的互相关。对于这里的情形,我们可以在像素周围重
新写出公式(2.3)中的 NCC,如下所示
因为本次主要涉及视差的内容,所以不再多加介绍
以上文字图片参考自:立体视觉
三、要求
- 从理论角度,分析以窗口代价计算视差的原理
- 实现NCC 视差匹配方法,即给定左右两张视图,根据NCC计算视差图
- 分析不同窗口值对匹配结果的影响,重点考查那些点(或者哪些类型的点)在不同窗口大小下的匹配精度影响
四、代码
# coding=utf-8
from PIL import Image
from pylab import *
from numpy import *
from numpy.ma import array
from scipy.ndimage import filters
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'F:\Pictures\one/1.png').convert('L'), 'f')
im_r = array(Image.open(r'F:\Pictures\one/2.png').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 9
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
import scipy.misc
scipy.misc.imsave('depth.png',res)
show()
五、结果与分析
1.算法对参考图像中的每个像素计算一个合适大小、形状和权重的窗口,然后对这个窗口内的视差值进行加权平均。理想的支持窗口应该完全覆盖弱纹理区域,并在窗口内深度连续。但是,在局部立体匹配算法的能量函数中,只有基于局部区域的约束数据项,没有平滑项。
2.
运用标准版本中的wid=9
(1)使用高斯版本的wid=3进行对比
分析:与标准版本相比,高斯版本更为模糊,细节方面根本不行。
(2)wid=18
分析:可以看出当wid越大的时候,细节方面越好,但是噪声却更少了。
(3)wid=6
分析:当wid=6的时候,细节处理得很差,但是噪声更多了,有一些高wid会被处理掉的部分,在这个情况下能够表示出来。