最近开始研究双目匹配算法,于是在网上代码网站比如CSDN,GITHUB,虽然找到很多代码,但是由于用的编译环境或者库不一样,导致有些代码无法理解,所以,为了得到自己想要的结果,本人花了几天的时间根据算法的基本原理,结合自己的编译环境,编写了代码。具体代码和步骤如下所示:
编译环境: visual studio 2015 + opencv3.2.0
先对SAD立体匹配算法做一个详细的解释(摘抄自某个作者的博客)。
SAD 算法:SAD算法是一种最简单的匹配算法,用公式表示为:
SAD(x,y,d) = Sum{|Left(x,y) - Right(x,y,d)|} 选择最小值,其中d为视差范围
此方法就是以左目图像的匹配点为中心,定义一个窗口D,其大小为 (2 * hwind+1) (2 * hwind+1),统计其窗口的灰度值的和,然后在右目图像中逐步计算其左右窗口的灰度和的差值,最后搜索到的差值最小的区域的中心像素即为匹配点。
基本流程:
1.构造一个小窗口,类似与卷积核。
2.用窗口覆盖左边的图像,选择出窗口覆盖区域内的所有像素点。
3.同样用窗口覆盖右边的图像并选择出覆盖区域的像素点。
4.左边覆盖区域减去右边覆盖区域,并求出所有像素点差的绝对值的和。
5.移动右边图像的窗口,重复3,4的动作。(这里有个搜索范围,超过这个范围跳出)
6.找到这个范围内SAD值最小的窗口,即找到了左边图像的最佳匹配的像素块。
以下这段话是对于双目立体匹配小白而言的,如果读者对于双目匹配有一定的了解,可以直接略过这段话直接看下面的程序。
有些人看到上面的这段定义就疑惑了,想着为什么两幅图像相减就能得到立体的图像呢。下面为大家解释一下。
大家看到上面的这两幅图像,renwu_left(以下统称为左图),renwu_right(以下统称为右图)。在这两个图像的最右边我标记了两个红色的方框。从图中可以看出,左图相对于右图相对靠左,所以说,在左图中要找到与右图相匹配的像素,就要在固定左图Left(x,y)的前提下,在右图中以同样的坐标为基点 Right(x,y),向左寻找匹配区域 Right(x,y,d)//d为选择移动的像素范围。 这样通过遍历图像,就可以得到匹配图像。又有读者问了,就算匹配上,那怎么得到深度呢?下面为读者解释一下(这个概念也是花了我好长时间才搞明白的)
从上面两幅图可以看出我们看出A点较B点近,所以A点的视差(x1-x1’)要大于B点的视差(x2-x2’)。(注意:在图中x1’,x2’为负数!!!!!!)所以在图中A,B,C三点(D为遮挡点,以后再讨论)的视差大小为:A>B>C,而三点的深度大小比较为:C>B>A,所以,深度和视差成反比。
(具体理论可以参考:http://blog.csdn.net/xiaohaijiejie/article/details/49721415)
讲了这么多,读者明白为什么可以凭借视差得到深度了吧。如果还是不明白,可以留言问我。下面上代码(结果不是很理想,以后会再改进。如果读者有更好的建议,可以留言)
//******************SAD************************
#include
#include
#include
#include
using namespace std;
using namespace cv;
//定义图片读取位置
string file_dir = "C:\\Program Files\\FLIR Integrated Imaging Solutions\\Triclops Stereo Vision SDK\\stereomatching\\Grab_Stereo\\pictures\\";
//定义图片存储位置
string save_dir = "C:\\Program Files\\FLIR Integrated Imaging Solutions\\Triclops Stereo Vision SDK\\stereomatching\\Grab_Stereo\\";
//-------------------定义Sad处理图像函数---------------------
//int sub_kernel(Mat &kernel_left, Mat &kernel_right);
//Mat Left_sad, Right_sad;
//--------------------得到Disparity图像-----------------------
int winSize = 7;//匹配窗口的大小 //可以改变大小
float sub_Sum;//存储匹配范围的视差和。声明为float类型是因为一个像素为8位的uchar类型,像素差加起来要比8位多,float是32位
int DSR = 30;//视差搜索范围 //可以改变大小
Mat getDisparity(Mat &left, Mat &right);//函数声明 以Mat类型返回
int main()
{
//-----------------显示左右灰度图像---------------
Mat LeftImg = imread(file_dir + "Renwu_left.png", 0);
Mat RightImg = imread(file_dir + "Renwu_right.png", 0);
Mat Disparity;
namedWindow("Renwu_left", 1);
namedWindow("Renwu_right", 1);
imshow("Renwu_left", LeftImg);
waitKey(5);
imshow("Renwu_right", RightImg);
waitKey(5);
//------------------处理左右图得到视差图像---------
Disparity = getDisparity(LeftImg, RightImg);
namedWindow("Disparity", 1);
imshow("Disparity", Disparity);
waitKey(0);
return 0;
}
int sub_kernel(Mat &kernel_left, Mat &kernel_right)
{
Mat Dif;
absdiff(kernel_left, kernel_right, Dif);
Scalar Add ;
Add = sum(Dif);
sub_Sum = Add[0];
return sub_Sum;//返回匹配窗像素相减之后的和
}
Mat getDisparity(Mat &left, Mat &right)
{
double start, end;//定义开始处理图像的时间和结束时间
start = getTickCount();
int ImgHeight = left.rows;
int ImgWidth = left.cols;
//------------------处理图像kernel大小--------------
Mat Kernel_L(Size(winSize, winSize), CV_8UC1, Scalar::all(0));
Mat Kernel_R(Size(winSize, winSize), CV_8UC1, Scalar::all(0));
Mat disparity(ImgHeight, ImgWidth, CV_8UC1,Scalar(0));//视差图
for (int i = 0; i < ImgHeight - winSize; i++)
{
for (int j = 0; j < ImgWidth - winSize; j++)
{
Kernel_L = left(Rect(j, i, winSize, winSize));
Mat Temp(1, DSR, CV_32F, Scalar(0));//之所以是float型,是因为求取两个图像的差之后相加得到的和可能会大于8位
//------------------将左右图视差放入matchLevel数组中-----------
for (int k = 0; k < DSR; k++)
{
int y = j - k;
if (y >= 0)
{
Kernel_R = right(Rect(y, i, winSize, winSize));
//---------------对左右图kernel进行处理---------------
Temp.at<float>(k) = sub_kernel(Kernel_L, Kernel_R);
}
}
//---------------寻找最佳匹配点--------------
Point minLoc;
minMaxLoc(Temp, NULL, NULL, &minLoc, NULL);
int loc = minLoc.x;//之所以是x坐标,请参考我文中的解释
disparity.at(i, j) = loc * 16;
}
}
//记录运行 时间
end = getTickCount();
double time = ((end - start)/CLOCKS_PER_SEC);
cout << "Time is : " << time << "ms" << endl;
return disparity;
}