原理:
深度信息可以通过计算1幅图像和其它图像的特征位置的像素差获得。视差图和深度图很像,因为视差大的像素离摄像机近,而视差小的像素离摄像机远。按以米为单位来计算摄像机距物体多远需要额外的计算。
计算视差图的标准方法是用简单的块匹配。我们选择右边图像中的1块小区域,并在左边图像中搜索匹配最近的像素区域。同理,当搜索右边图像时,我们从和左边图像的模板相同的坐标处开始,向左和向右搜索至最大距离。视差为右边图像的小区域和左边图像的最近匹配区域的中心像素的水平距离。
视差图计算的步骤:
1、块比较
寻找“最近匹配块”,简单的方法叫差的绝对值的和(SAD)。在计算深度图之前,先将两幅图像转换为灰度图像(像素值为0到255)。
2、图像修正
注意到我们仅在水平方向而没在垂直方向上搜索匹配块。这里用到的图像都是已修正的,左图的特征将会在右图同一像素行上。
3、搜索范围和方向
块匹配算法要求我们指定从模板位置器我们想搜索多远,这应该基于希望在图像中找到的最大视差。
4、模板大小
更大的模板产生的深度图噪声小,然而计算代价高。模板太大会丢失物体边缘上的细节。
5、图像边缘上的模板形状
裁剪模板至最大。默认模板大小为7x7个像素。但对于左上角(行1,列1),我们不能包含填满模板,所以只用44的模板。对2行1列的像素,我们用54的模板。块大小同理。
6、子像素估计
块匹配计算得到的视差值为整数,对应像素偏移。如果可能在最近匹配块和它的邻居间插入来微调视差值至“子像素”位置。之前我们仅用最小代价的位置作为视差,但现在我们考虑最小代价和两个相邻代价值。我们用这3个值拟合抛物线,并解析抛物线的最小值来获得子像素的位置。即抛物线的横轴为像素横坐标,纵轴为代价,代价最低的点应该是抛物线的极点。实际计算得到的极点是估计值。
7、平滑和图像金字塔
通过考虑相邻像素的视差来提高视差图的精度。采用动态规划。图像金字塔来加速块匹配的过程。它涉及到对图像下采样来来在粗糙尺度上快速搜索,然后在更细尺度上提精搜索。
原理:
归一化相关性,简称NCC。就是用于归一化待匹配目标之间的相关程度,注意这里比较的是原始像素。通过在待匹配像素位置p(px,py)构建33邻域匹配窗口,与目标像素位置p’(px+d,py)同样构建邻域匹配窗口的方式建立目标函数来对匹配窗口进行度量相关性,注意这里构建相关窗口的前提是两帧图像之间已经校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。相关程度的度量方式由如下式子定义:
其中p点表示图像I1待匹配像素坐标(px,py),d表示在图像I2被查询像素位置在水平方向上与px的距离。如下图所示:
左边为图像I1,右边为图像I2。图像I1,蓝色方框表示待匹配像素坐标(px,py),图像I2蓝色方框表示坐标位置为(px,py),红色方框表示坐标位置(px+d,py)。
Wp表示以待匹配像素坐标为中心的匹配窗口,通常为33匹配窗口。
没有上划线的I1表示匹配窗口中某个像素位置的像素值,带上划线的I1表示匹配窗口所有像素的均值。I2同理。
上述公式表示度量两个匹配窗口之间的相关性,通过归一化将匹配结果限制在 [-1,1]的范围内,可以非常方便得到判断匹配窗口相关程度:
若NCC = -1,则表示两个匹配窗口完全不相关,相反,若NCC = 1时,表示两个匹配窗口相关程度非常高。
双目立体匹配流程如下:
窗口值为3:
由上图可看出,窗口值为3的时候,基本可以看出雕塑、台灯、雕塑后面的摄像机、书桌上的物品、书架的大致轮廓,而书桌的轮廓却不太清晰,只能看出一点模模糊糊的轮廓,四周边角的视觉匹配效果也不太理想,可能是因为书桌部分地方亮度比较暗导致的。
窗口值为7:
由上图可看出,窗口值为7的时候,雕塑、台灯、书桌上的物品、雕塑后面的摄像机的轮廓都变得更清晰了,书桌的轮廓也显露了出来。相比窗口值为3的时候的视觉匹配,窗口值为7的时候书架的轮廓反而变得不清晰了,但还是能看出一点点轮廓。
窗口值为11:
由上图可看出,窗口值为11的时候,雕塑、书桌、书桌上的物品的大致轮廓依旧清晰,相比窗口值为7的时候的视觉匹配,窗口值为11的时候台灯、雕塑后面的摄像机的轮廓开始变得模糊,书架的轮廓更是完全看不见了。
# -*- 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
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)
im_l = array(Image.open(r'C:\Users\mangowu\Desktop\test\scene1.row3.col3.ppm').convert('L'),'f')
im_r = array(Image.open(r'C:\Users\mangowu\Desktop\test\scene1.row3.col4.ppm').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('C:/Users/mangowu/Desktop/test/3.png',res)
show()