StereoVision--立体视觉(3)

转载自:https://zhuanlan.zhihu.com/p/30754263

紧接着上次的文章,这次将详细的说明一下立体匹配(这里需要说明一样,这里所有的图片都是存在对极约束的,也就是完成过校正,可以说对于左右两幅图像上的匹配点,它们的y值相等,而x之间的差别就是视差d),并且会给出最近看的一些文章,以及对文章的一些理解。(PS:目前,立体匹配领域,主要有两个评测网站,一个是KITTI(cvlibs.net/datasets/kit),另一个是middlebury(vision.middlebury.edu/s),两个网站上的算法都交叉,但是又不完全一样)


对于图像的匹配我们都知道,就是对两幅图片或者更多图片,找到它们相似的地方。那么对于立体匹配而言,就是基于同一场景得到的多张二维图,通过找到相同点进一步还原场景的三维信息,一般采用的图像是双目图像,下面给出Middlebury上的一组标准图,第一幅图和第二幅图分别是双目相机得到的左图和右图,第三幅图就是视差图。可以很明显的看出来越亮的地方表示视差越大,也就离相机越近,反之离相机也就越远。

下面给出立体匹配的四个最基本的步骤:

  1. 匹配代价计算(Matching Cost Computation:CC)
  2. 代价聚合(Cost Aggregation:CA)
  3. 视差计算(Disparity Computation )
  4. 视差精化(Disparity Refinement ):对上一步得到的粗估计的视差图进行精确计算,策略有很多,例如plane fitting,BP,动态规划等。这里不再熬述。

同样的,立体匹配也分为全局匹配和局部匹配两种,这两种在匹配步骤上也有所差异。

  • 全局匹配

全局立体匹配算法一般来说会省略第二步,主要是采用了全局的优化理论方法估计视差,建立全局能量函数,通过最小化全局能量函数得到最优视差值。其能量函数由数据项和平滑项构成。

E(d)=E_{data}(d)+E_{smooth}(d)

全局匹配算法得到的结果比较准确,但是其运行时间比较长,不适合实时运行。主要的算法有图割(graph cuts)、信念传播(belief propagation)、动态规划(Dynamic Programming )等算法。

  • 局部匹配

基本原理是给定在一幅图像上的某一点,选取该像素点邻域内的一个子窗口,在另一幅图像中的一个区域内,根据某种相似性判断依据,寻找与子窗口图像最为相似的子图,而其匹配的子图中对应的像素点就为该像素的匹配点。通常方法有SAD、SSD、NCC等等。


下面来详细的一步一步的介绍立体匹配的每一个步骤,并且结合一个具体的例子来进行说明。

匹配代价计算

一般来说,匹配代价的计算是对左右两幅图像的每一个像素点而言的,可以认为定义了一个处理左右两幅图像中匹配像素点的函数 f(I_{L}(x,y),I_{R}(x+d,y)) ,这里的 d 是我们定义的视差范围,也就是对于左图中的一个像素点 I_{L}(x,y) ,我们给它在右图中定义一个寻找视差的范围 d

举个例子:假设我们对于匹配图像定义了视差的寻找范围为0~16,也就是说 d\in(0,16) ( d 是整数),对于左图的一个像素点 I_{L}(x,y) 在右图中能够计算匹配代价的区域就是 I_{R}(x+0,y)到I_{R}(x+16,y) 这之间。

我们是不是可以这样想,对于每一个像素点,在给定的视差范围内,我们找到 min(f(I_{L}(x,y),I_{R}(x+d,y))),d\in(d_{min},d_{max}) ,我们是不是就可以认为这个最小值就代表这是正确的匹配点,而这个最小值对应的 d 就是我们要找的视差(这一步运用的是Winner Take All: WTA策略)。如果对每一个像素都这样进行操作的话,我们是不是就可以得到最后的视差图。显然这是成立的,但是这样做的最后的视差图效果非常的差,在这里可以给大家展示一下这样简单操作后的结果。

左图(标准图) 右图(简单处理后的视差图)

代价聚合

我们从上图中基于点之间的匹配很容易受噪声的影响,所以我们需要在点的周围建立一个window,让像素块和像素块之间进行比较,这样肯定靠谱些。代价聚合往往是局部算法或者半全局算法才会使用,全局算法抛弃了window,采用基于全图信息的方式建立能量函数。

或者说,我们换一种看法,可以看做是对匹配代价计算的结果进行滤波的一个过程。


举个例子:假设我们有一个3*3的窗,在这个窗上进行代价聚合的函数为 g(x) ,首先看对原始左图像一个像素点的聚合: \sum_{(x_{L},y_{L})\in(W_{x_{L}},W_{y_{L}})}^{}{g(x_{L},y_{L})} (结果是窗口的中心点的值),同理对于右边的图像,需要在视差范围内做聚合: \sum_{(x_{R}+d,y_{R})\in(W_{x_{R}},W_{y_{R}})}^{}{g(x_{R}+d,y_{R})} 。然后,下一步就需要对处理之后的像素计算匹配代价,并且找到它们的代价最小值: min(f(I_{L}(x^{’},y^{’}),I_{R}((x+d)^{’},y^{’}))),d\in(d_{min},d_{max}) 。我们可以看到,最后同一的公式是一个关于视差 d 的函数。

再看一看以滤波的方式来解释,这里我们直接对得到的视差图进行代价聚合: \sum_{(x_{D},y_{D})\in(W_{x_{D}},W_{y_{D}})}^{}{g(x_{D},y_{D})}(x_{D},y_{D}) 表示视差图坐标),这里的 (x_{D},y_{D}) 就是经过匹配代价计算后的像素值。可以看到最后两种方法的结果都是一样的(可以使用最简单的Box filter去验证)。

视差计算

其实在上面两个步骤的时候,就已经提及到了视差计算。视差计算最常用的策略就是WTA策略。这一步也可以分为局部算法与全局算法,局部算法直接优化代价聚合模型。而全局算法,要建立一个能量函数。输出的是一个粗略的视差图。

视差精化

这一步也就是对得到的视差进行优化的过程,通常方法都是“亚像素求精+均值滤波”。但是不可否认的是,立体匹配最关键的步骤仍然是代价计算和代价聚合步骤。


下面以局部匹配中的SAD做为例子来更好的理解立体匹配,其中着重考虑的是代价计算和代价聚合。

这里简单的介绍一下SAD,SAD就是Sum of Absolute differences,下面给出SAD计算公式: C(x,y,d)=\sum_{x,y\in W}{|I_{L}(x,y),I_{R}(x+d,y)|}

下面给出实现的代码:

#include<iostream>   
#include <opencv2/core/core.hpp>  
#include<opencv2/highgui/highgui.hpp>   

using namespace std; 
using namespace cv;
  
int main(){  
 
	int hWin = 3;//窗的大小为3*3  

	cout<<"hWin: "<<hWin<<";  "<<"compare length:  "<<compareLength<<endl;    
	cout<<"SAD test"<<endl;  

	Mat left_image = imread("disp2.png");
	Mat right_image = imread("disp6.png");
 
	
	int imageWidth = left_image.cols;  
	int imageHeight =left_image.rows;  
	//cout << left_image.size();
	Mat SAD_image = Mat(left_image.size(),CV_8UC1,1);  
	Mat MatchLevel_image = Mat(left_image.size(),CV_8UC1,1);  

	//设定视差寻找范围d为0~7
	int minDBounds = 0;  
	int maxDBounds = 7;  

	namedWindow("Left");  
	namedWindow("Right");  
	namedWindow("SAD");  
	namedWindow("MatchLevel");  

	imshow("Left",left_image);  
	imshow("Right",right_image);  


	/*SAD Transform */  
	int i,j ,m,n,k;  
	unsigned char centerPixel = 0;   
	unsigned char neighborPixel = 0;  
	int bitCount = 0;  
	unsigned int bigger = 0;  

	int sum = 0;  
	unsigned int *match_level = new unsigned int[maxDBounds - minDBounds  + 1];  
	int temp_min = 0;  
	int temp_index = 0;  

	unsigned char* dst;  
	unsigned char* left_src  = NULL;  
	unsigned char* right_src = NULL;  

	unsigned char left_pixel = 0;  
	unsigned char right_pixel =0;  
	unsigned char sub_pixel = 0;  


	for(i = 0 ; i < imageHeight;i++)
	{  
		for(j = 0; j< imageWidth;j++)
		{  
			//计算每一个像素的SAD
			for (k = minDBounds;k <= maxDBounds;k++)  
			{  
				sum = 0;  
				for (m = i-hWin; m <= i + hWin;m++)  
				{  
					for (n = j - hWin; n <= j + hWin;n++)  
					{  
						if (m < 0 || m >= imageHeight || n <0 || n >= imageWidth )  
						{  
							sub_pixel  = 0;  
						}else if (n + k >= imageWidth)  
						{  
							sub_pixel = 0;  
						}else  
						{  
							left_src = (unsigned char*)left_image.data 
								+ m*left_image.step + n + k;   
							right_src = (unsigned char*)right_image.data   
								+ m*right_image.step + n;  

							left_pixel = *left_src;  
							right_pixel = *right_src;  
							if (left_pixel > right_pixel)  
							{  
								sub_pixel = left_pixel - right_pixel;  
							}else   
							{  
								sub_pixel = right_pixel -left_pixel;  
							}  

						}  

						sum += sub_pixel;  
					}  
				}  
				match_level[k] = sum;  
				//cout<<"SUM:"<<sum<<endl;   
			}  

			/*寻找最佳匹配点*/  

			temp_min = 0;  
			temp_index = 0;  
			for ( m = 1;m < maxDBounds - minDBounds + 1;m++)  
			{  
				//cout<<"match_level[m]:"<<match_level[m]<<endl;   
				if (match_level[m] < match_level[temp_index])  
				{  
					temp_min = match_level[m];  
					temp_index = m;  
				}  
			}  
			dst = (unsigned char *)SAD_image.data + i*SAD_image.step + j;    
			*dst = temp_index*8;  

			dst = (unsigned char *)MatchLevel_image.data + i*MatchLevel_image.step + j;  
			*dst = temp_min;   
		} 
	}  

	imshow("SAD",SAD_image);  
	imshow("MatchLevel",MatchLevel_image);  

	imwrite("MatchLevel.jpg", MatchLevel_image);
	imwrite("depth.jpg", SAD_image);

	waitKey(0);  
 
	return 0;  
} 

下面是标准视差图和SAD的结果图

左图(标准图) 右图(SAD的视差图)

我们可以很明显的看到,SAD的结果和最终的标准图还是有很大的差别,图中的有些特征也没有很好的还原出来。

后面将开始着重于对立体匹配文章的解读,将一些文章的读后感跟大家分享。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值