计算机视觉第七次实验——基于NCC视差匹配
文章目录
一、视差原理
平时我们能够看到的二维图像,能够让自己具备三维立体效果的有:立体图片、3D电影、VR虚拟现实。也许你会说当你看到一种普通的照片时,仍然能够分辨物体之间的前后立体关系,但其实这只是利用光影效果、明暗对比来体现的,这跟我们身体所处或眼睛所看到的立体世界(上下、左右、前后)有着本质差别,因为普通图片始终是二维空间。那如何利用二维空间来生成三维的立体效果呢,比如我们看3d电影时,图片只是在屏幕这个二维平面进行显示,为什么我们在观影时可以看到一些物体从身边擦肩而过的立体画面。接下来就提到我们需要引入的核心概念——视差。基本原理:人类之所以能立体,主要原因是左右眼双眼看到的不同画面所构成的视差。通俗的解释就是,你的左眼和右眼看到的图景是不一样的,这两幅图景的差别就构成视差,随后大脑将左右眼不同的图像进行合成,从而形成立体视觉。计算机想要模拟人类视觉,只需要利用两台摄像机拍摄出左右眼两个视角的图像就可以了。为了加深大家的理解,在此以3d电影和2d电影区别为例: 3d电影眼睛的两个镜片会分别给左右眼呈现不同的视觉效果,假如不带眼睛会看起来一片模糊,但带上眼睛之后,由于做了不同眼睛图像的分离,便可以看的很清楚,并且更加具备立体感。以前理解2d电影本身也是一个3d的世界,但其实从拍摄照片的角度来看,记录在照相机或者摄像机里面的只有二维数据,不同像素点的亮度值直接决定景深,仍然是一种2d的处理和呈现。但是对于3d电影,拍摄手法要比2d电影要复杂很多,并且带上3d眼镜之后,能够感受到一些画面在自己身边,也就是以投影屏幕作为一个二维维度,并且有了三维的延伸,这就是2d电影和3d电影最大的差别了。
二、视差理论的应用
2.1 立体图像
2.2 3D电影
2.3 VR虚拟现实
三、归一化相关性(NCC)原理
归一化相关性,normalization cross-correlation,因此简称NCC,下文中笔者将用NCC来代替这冗长的名称。
NCC,顾名思义,就是用于归一化待匹配目标之间的相关程度,注意这里比较的是原始像素。通过在待匹配像素位置p(px,py)构建33邻域匹配窗口,与目标像素位置p’(px+d,py)同样构建邻域匹配窗口的方式建立目标函数来对匹配窗口进行度量相关性,注意这里构建相关窗口的前提是两帧图像之间已经校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。相关程度的度量方式由如下式子定义:
上式中的变量需要解释一下:其中p点表示图像**I1***待匹配像素坐标(px,py),d表示在图像***I2***被查询像素位置在水平方向上与px的距离。如下图所示:
左边为图像***I1***,右边为图像***I2***。图像***I******1***,蓝色方框表示待匹配像素坐标(px,py),图像***I2***蓝色方框表示坐标位置为(px,py),红色方框表示坐标位置(px+d,py)
Wp表示以待匹配像素坐标为中心的匹配窗口,通常为33匹配窗口。
没有上划线的**I1***表示匹配窗口中某个像素位置的像素值,带上划线的***I1***表示匹配窗口所有像素的均值。***I2***同理。
上述公式表示度量两个匹配窗口之间的相关性,通过归一化将匹配结果限制在 [-1,1]的范围内,可以非常方便得到判断匹配窗口相关程度:
若NCC = -1,则表示两个匹配窗口完全不相关,相反,若NCC = 1时,表示两个匹配窗口相关程度非常高。
我们很自然的可以想到,如果同一个相机连续拍摄两张图像(注意,此时相机没有旋转也没有位移,此外光照没有明显变化,因为基于原始像素的匹配方法通常对上述条件是不具备不变性的),其中有一个位置是重复出现在两帧图像中的。比如桌子上的一个可乐瓶。那么我们就可以对这个可乐瓶的位置做一下匹配。直观的看,第一帧中可乐瓶上某一个点,它所构成邻域窗口按理说应该是与第二帧相同的,就算不完全相同,也应该是具有非常高相关性的。基于这种感性的理解,于是才有前辈提出上述的NCC匹配方法。(纯属个人理解)
四、双目立体匹配
这一部分是说明NCC如何用于双目匹配。
假设有校正过的两帧图像***I****1***,、I2,由上述NCC计算流程的描述可知,对图像I1一个待匹配像素构建33匹配窗口,在图像I2极线上对每一个像素构建匹配窗口与待匹配像素匹配窗口计算相关性,相关性最高的视为最优匹配。很明显,这是一个一对多的过程。如果图像尺寸是640480,则每一个像素的匹配过程是是1对640,两帧图像完全匹配需要计算640480640 = 196608000,即一亿九千多万次~ 尽管计算机计算速度非常快,但也着实是非常消耗计算资源的。由于NCC匹配流程是通过在同一行中查找最优匹配,因此它可以并行处理,这大概也算是一种弥补吧~
双目立体匹配流程如下:
1. 采集图像:通过标定好的双目相机采集图像,当然也可以用两个单目相机来组合成双目相机。(标定方法下次再说)
2. 极线校正:校正的目的是使两帧图像极线处于水平方向,或者说是使两帧图像的光心处于同一水平线上。通过校正极线可以方便后续的NCC操作。
3. 特征匹配:这里便是我们利用NCC做匹配的步骤啦,匹配方法如上所述,右视图中与左视图待测像素同一水平线上相关性最高的即为最优匹配。完成匹配后,我们需要记录其视差d,即待测像素水平方向xl与匹配像素水平方向xr之间的差值d = xr - xl,最终我们可以得到一个与原始图像尺寸相同的视差图D。
4. 深度恢复:通过上述匹配结果得到的视差图D,我们可以很简单的利用相似三角形反推出以左视图为参考系的深度图。计算原理如下图所示:
如图,Tx为双目相机基线,f为相机焦距,这些可以通过相机标定步骤得到。而xr - xl就是视差d。
通过公式 z = f * Tx / d可以很简单地得到以左视图为参考系的深度图了。
五、实验原图及实验代码
5.1 tsukuba
5.2 cones
5.3实验代码
# -*- 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)
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\59287\Desktop\a/scene1.row3.col3.ppm').convert('L'), 'f')
im_r = array(Image.open(r'C:\Users\59287\Desktop\a/scene1.row3.col4.ppm').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 11
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
import scipy.misc
scipy.misc.imsave('depth.png',res)
show()
六、实验结果
6.1 tsukuba
6.1.1窗口值为1
6.1.2窗口值为3
6.1.3窗口值为5
6.1.4窗口值为7
6.1.5窗口值为9
6.1.6窗口值为11
6.2 cones
6.2.1窗口值为1
6.2.2窗口值为3
6.2.3窗口值为5
6.2.4窗口值为7
6.2.5窗口值为9
6.2.6窗口值为11
七、实验小结
有上述实验结果可以知道窗口值较大时大匹配的图片比较清晰,精度高,但是太大了,会出现误配的情况。窗口值较小时小则图片比较模糊,精度低,当窗口值为1时,出现了黑色的图像。由第二组图片中小圆脸前面绿色的圆锥和小圆脸旁边红色的圆锥看,因为绿圆锥距离相机的位移近,红圆锥距离相机位移远,变化窗口值时,它的像素也变化的不一样,比较近的会变得越来越偏白色,灰度值比较低,后面比较远的会越来越黑,,就是灰度值会比较高。