双目立体视觉一直是机器视觉研究领域的发展热点和难点,“热”是因为双目立体视觉有着及其广阔的应用前景,且随着光学、计算机科学等学科的不断发展,双目立体技术将不断进步直到应用到人类生活的方方面面。“难”则是因为受到摄像机、镜头等硬件设备及一些相关算法的限制,双目立体视觉的研究及如何更好的应用到生产实际中仍有待在座的各位去进行突破。
一.简介
双目立体视觉是机器视觉中的一个重要分支,自上世纪60年代中期开创以来,经过几十年的发展,如今在机器人视觉、航空测绘、军事应及医学成像、工业检测上应用极其广泛。双目立体视觉基于视差原理并利用成像设备从不同的位置获取被测物体的左右两幅图像,然后根据三角测量原理计算空间点在二维图像的位置偏差,最后再利用位置偏差进行三维重建来获取被测物体的三维几何信息(本文不对双目立体视觉的数学原理进行详细介绍)。
二.双目立体视觉的三大基本算法的原理及其代码实现(基于opencv)
双目立体视觉中常用的基于区域的局部匹配准则主要有图像序列中对应像素差的绝对值之和SAD(sum of absolute differences)、对应像素差的平方之和SSD(sum of squared differences)及半全局匹配算法SGM(semi—global matching)。
2.1 SAD(sum of absolute differences)的原理
匹配算法SAD的基本思想是对经行对准后的左右视图图像的对应像素块的对应像素差的绝对值进行求和。
其数学公式如下:
SAD匹配算法的基本流程如下:
①输入两幅已经校正实现行对准的左视图(Left-Image)及右视图(Right-Image)。
②对左视图Left-Image进行扫描选定一个锚点并构建一个类似于卷积核的小窗口。
③用此小窗口覆盖Left-Image,并选择出小窗口覆盖区域的全部像素点
④同样用此小窗口覆盖Right-Image,并选择出小窗口覆盖区域的全部像素点。
⑤Left-Image覆盖区域的像素减去Right-Image覆盖区域的像素,并求出所有像素点的差的绝对值之和。
⑥移动Right-Image的小窗口并重复④—⑤的操作(注意此处会设置一个搜索范围,超过此范围则跳出)
⑦找到这个范围内SAD值最小的小窗口,此时便找到了与Left-Image锚点的最佳匹配的像素块。
2.1.1 SAD(sum of absolute differences)的基于opencv的C 代码实现
首先先定义一个SAD 算法的头文件(SAD_Algorithm.h):
代码语言:javascript复制#include"iostream"
#include"opencv2/opencv.hpp"
#include"iomanip"
using namespace std;
using namespace cv;
class SAD
{
public:
SAD() :winSize(7), DSR(30) {}
SAD(int _winSize, int _DSR) :winSize(_winSize), DSR(_DSR) {}
Mat computerSAD(Mat &L, Mat &R); //计算SAD
private:
int winSize; //卷积核的尺寸
int DSR; //视差搜索范围
};
Mat SAD::computerSAD(Mat &L, Mat &R)
{
int Height = L.rows;
int Width = L.cols;
Mat Kernel_L(Size(winSize, winSize), CV_8U, Scalar::all(0));
Mat Kernel_R(Size(winSize, winSize), CV_8U, Scalar::all(0));
Mat Disparity(Height, Width, CV_8U, Scalar(0)); //视差图
for (int i = 0; i<Width - winSize; i ) //左图从DSR开始遍历
{
for (int j = 0; j<Height - winSize; j )
{
Kernel_L = L(Rect(i, j, winSize, winSize));
Mat MM(1, DSR, CV_32F, Scalar(0));
for (int k = 0; k<DSR; k )
{
int x = i - k;
if (x >= 0)
{
Kernel_R = R(Rect(x, j, winSize, winSize));
Mat Dif;
absdiff(Kernel_L, Kernel_R, Dif);//求差的绝对值之和
Scalar ADD = sum(Dif);
float a = ADD[0];
MM.at<float>(k) = a;
}
}
Point minLoc;
minMaxLoc(MM, NULL, NULL, &minLoc, NULL);
int loc = minLoc.x;
//int loc=DSR-loc;
Disparity.at<char>(j, i) = loc * 16;
}
double rate = double(i) / (Width);
cout << "已完成" << setprecision(2) << rate * 100 << "%" << endl; //显示处理进度
}
return Disparity;
}
调用示例:
#include"SAD_Algorithm.h"
int main(int argc, char* argv[])
{
Mat Img_L = imread("Teddy_L.png", 0); //此处调用的图像已放入项目文件夹中
Mat Img_R = imread("Teddy_R.png", 0);
Mat Disparity; //创建视差图
SAD mySAD(7, 30); //给出SAD的参数
Disparity = mySAD.computerSAD(Img_L, Img_R);
imshow("Teddy_L", Img_L);
imshow("Teddy_R", Img_R);
imshow("Disparity", Disparity); //显示视差图
waitKey();
system("pause"); //按任意键退出
return 0;
}
2.1.2 SAD算法的运行效果
可以看出SAD算法虽然运行较快,但效果较差。
2.2 SSD(sum of squared differences)的原理
SSD(sum of squared differences)算法大致与SAD(sum of absolute differences)相似。
其数学公式如下:
因SSD匹配算法与SAD匹配算法的过程及代码实现相类似,考虑到篇幅长度的原因,故SSD算法的基本过程及代码实现在本文中不在赘述,读者可去自行实现。
2.3 SGBM(semi-global block matching)的原理
SGM(semi-global matching)是一种用于计算双目立体视觉中的disparity的半全局匹配算法。其在opencv中的实现为SGBM(semi-global block matching)。
SGBM的原理:设置一个和disparity map(由每个像素点的disparity所构成)相关的全局能量函数,使这个能量函数最小。
原始文献:Heiko Hirschmuller. Stereo processing by semiglobal matching and mutual information.Pattern Analysis and Machine Intelligence, IEEE Transactions on, 30(2):328–341, 2008.
其能量函数如下:
D--disparity map(视差图)
p、q—图像中的某个像素
Np—像素点Pd 相邻像素点(一般认为是8连通)
C(P,Dp)--当前像素点的disparity为Dp时,该像素点的cost
P1、P2—惩罚系数,分别适用于当像素P相邻像素中的disparity值与P的disparity差值为1和大于1时
I[]—当[]内的参数为真时返回1,否则返回0
SGBM算法的基本流程如下:
①预处理:使用sobel算子对源图像进行处理,并将经sobel算子处理后的图像映射为新图像,并得到图像的梯度信息用于后续的计算代价。
②代价计算:使用采样方法对经预处理得到的图像梯度信息计算梯度代价、使用采样方法对源图像计算SAD代价。
③动态规划:默认四条路经,并对路径规划的参数P1,P2进行设置(包括P1、P2、cn(图像通道数量)以及SADWindowsize(SAD窗口大小)的设置)。
④后处理:包括唯一性检测、亚像素插值、左右一致性检测、连通区域的检测。
2.3.1 SGBM(semi-global block matching)的基于opencv的C 代码实现
首先先定义一个SGBM算法的头文件(SGBM_Algorithm.h):
具体参数见代码及其注释(若读者需优化可自行调整),不再赘述
代码语言:javascript复制enum { STEREO_BM = 0, STEREO_SGBM = 1, STEREO_HH = 2, STEREO_VAR = 3, STEREO_3WAY = 4 };
#include"iostream"
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
void calDispWithSGBM(Mat Img_L, Mat Img_R, Mat &imgDisparity8U)
{
Size imgSize = Img_L.size();
int numberOfDisparities = ((imgSize.width / 8) 15) & -16;
Ptr<StereoSGBM> sgbm = StereoSGBM::create(0, 16, 3);
int cn = Img_L.channels(); //左图像的通道数
int SADWindowSize = 9;
int sgbmWinSize = SADWindowSize > 0 ? SADWindowSize : 3;
sgbm->setMinDisparity(0); //minDisparity最小视差默认为0;
sgbm->setNumDisparities(numberOfDisparities); //numDisparity视差搜索范围,其值必须为16的整数倍;
sgbm->setP1(8 * cn*sgbmWinSize*sgbmWinSize);
sgbm->setP2(32 * cn*sgbmWinSize*sgbmWinSize); //一般建议惩罚系数P1、P2取此两值,P1、P2控制视差图的光滑度
//P2越大,视差图越平滑
sgbm->setDisp12MaxDiff(1); //左右一致性检测最大容许误差阈值
sgbm->setPreFilterCap(31); //预处理滤波器的截断值,预处理的输出值仅保留
//[-preFilterCap, preFilterCap]范围内的值,参数范围:1 - 31
sgbm->setUniquenessRatio(10); //视差唯一性百分比:视差窗口范围内最低代价是次低代价的(1 uniquenessRatio/100)倍时
//最低代价对应的视差值才是该像素点的视差,否则该像素点的视差为 0 ,不能为负值,一般去5——15
sgbm->setSpeckleWindowSize(100); //视差连通区域像素点个数的大小:对于每一个视差点,当其连通区域的像素点个数小于
//speckleWindowSize时,认为该视差值无效,是噪点。
sgbm->setSpeckleRange(32); //视差连通条件:在计算一个视差点的连通区域时,当下一个像素点视差变化绝对值大于
//speckleRange就认为下一个视差像素点和当前视差像素点是不连通的。
sgbm->setMode(0); //模式选择
sgbm->setBlockSize(sgbmWinSize); //设置SAD代价计算窗口,一般在3*3到21*21之间
//blockSize(SADWindowSize) 越小,也就是匹配代价计算的窗口越小,视差图噪声越大;
//blockSize越大,视差图越平滑;
//太大的size容易导致过平滑,并且误匹配增多,体现在视差图中空洞增多
//三种模式选择(HH、SGBM、3WAY)
int algorithm = STEREO_SGBM;
if (algorithm == STEREO_HH)
sgbm->setMode(StereoSGBM::MODE_HH);
else if (algorithm == STEREO_SGBM)
sgbm->setMode(StereoSGBM::MODE_SGBM);
else if (algorithm == STEREO_3WAY)
sgbm->setMode(StereoSGBM::MODE_SGBM_3WAY);
Mat imgDisparity16S = Mat(Img_L.rows, Img_L.cols, CV_16S);
sgbm->compute(Img_L, Img_R, imgDisparity16S);
//--Display it as a CV_8UC1 image:16位有符号转为8位无符号
imgDisparity16S.convertTo(imgDisparity8U, CV_8U, 255 / (numberOfDisparities*16.));
}
调用示例:
#include"SGBM_Algorithm.h"
int main()
{
Mat Img_L = imread("Teddy_L.png", 0);
Mat Img_R = imread("Teddy_R.png", 0);
Mat Disparity8U = Mat(Img_L.rows, Img_R.cols, CV_8UC1);//创建一个Disparity图像
calDispWithSGBM(Img_L, Img_R, Disparity8U);
imshow("Teddy_L", Img_L);
imshow("Teddy_R", Img_R);
imshow("Disparity", Disparity8U);
waitKey();
system("pause"); //按任意键退出
return 0;
}
2.3.2 SGBM算法的运行效果
我还顺便调整了SADWindowsize的大小来给读者探讨并展示当设置不同SADWindowsize大小时对Disparity效果图的影响,其结果如下(皆为MODE_SGBM模式下):
由上述在不同SADWindowsize大小设置(其他参数保持不变)的效果图对比下我们可得知如下结论:
SADWindowsize过小时,视差图的噪声较多;随着SADWindowsize的增大,视图越平滑,但当SADWindowsize过大时,视差图中的空洞现象会增加;故在选择SADWindowsize的大小时,应选取合适的大小(建议选择SADWindowsize=9)。
三.双目立体视觉的发展现状
目前在国外,双目立体视觉技术已广泛运用于生产生活实际中,但在我国,双目立体视觉技术仍处在起步阶段,仍需要在座的各位发奋图强、力争创新。
3.1 双目立体视觉的发展方向
就双目立体视觉的发展现况和发展目标(达到类似于人眼的通用双目立体视觉)仍是路漫漫其修远兮,我认为进一步的发展方向可以归纳如下:
①探索新的更具有通用性的计算理论和匹配算法结构,以解决目前存在的灰度失真、噪声干扰以及几何畸变的问题。
②提高算法的性能,对算法进行优化,尽可能向实时效果推进。
③建立更有效的双目体视模型能更充分地反映立体视觉不确定性的本质属性,为匹配提供更多的约束信息,降低立体匹配的难度。
④强调场景与任务的约束,建立适用于不同场景和任务的双目立体视觉系统的标准和方法。
3.2 双目立体视觉的国内外发展动态
双目体视目前主要应用于四个领域:机器人导航、微操作系统的参数检测、三维测量和虚拟现实。
目前在国外,日本大阪大学自适应机械系统研究院研制了一种自适应双目视觉伺服系统,利用双目体视的原理,如每幅图像中相对静止的三个标志为参考,实时计算目标图像的雅可比短阵,从而预测出目标下一步运动方向,实现了对动方式未知的目标的自适应跟踪。该系统仅要求两幅图像中都有静止的参考标志,无需摄像机参数。
日本奈良科技大学信息科学学院提出了一种基于双目立体视觉的增强现实系统(ar)注册方法,通过动态修正特征点的位置提高注册精度。
日本东京大学将实时双目立体视觉和机器人整体姿态信息集成,开发了仿真机器人动态行长导航系统,为机器人根据实时情况建立实时地图从而实现障碍物检测。
日本冈山大学使用立体显微镜、两个ccd摄像头、微操作器等研制了使用立体显微镜控制微操作器的视觉反馈系统,用于对细胞进行操作,对钟子进行基因注射和微装配等。
麻省理工学院计算机系统提出了一种新的用于智能交通工具的传感器融合方式,由雷达系统提供目标深度的大致范围,利用双目立体视觉提供粗略的目标深度信息,结合改进的图像分割算法,从而实现在高速环境下对视频图像中的目标位置进行分割。
华盛顿大学与微软公司合作为火星卫星“探测者”号研制了宽基线立体视觉系统,使“探测者”号能够在火星上对其即将跨越的几千米内的地形进行精确的定位及导航。
在国内,浙江大学机械系统完全利用透视成像原理,采用双目体视方法实现了对多自由度机械装置的动态、精确位姿检测,仅需从两幅对应图像中抽取必要的特征点的三维坐标,信息量少,处理速度快,尤其适于动态情况。
维视图像公司采用双目ccd相机,从工业相机内参标定、镜头畸变标定、立体匹配、特征点分割处理等方面给出了详细的数学模型和算法接口。其双目标定软件ccas采用了张正友平面标定法,可以实现机器人导航、微操作系统的参数检测、三维测量和虚拟现实等应用。
东南大学电子工程系基于双目立体视觉,提出了一种灰度相关多峰值视差绝对值极小化立体匹配新方法,可对三维不规则物体(偏转线圈)的三维空间坐标进行非接触精密测量。
哈工大采用异构双目活动视觉系统实现了全自主足球机器人导航。将一个固定摄像机和一个可以水平旋转的摄像机,分别安装在机器人的顶部和中下部,可以同时监视不同方位视点,体现出比人类视觉优越的一面。即使在实际比赛中当其他传感器失效的情况下,仅仅依靠双目协调仍然可以实现全自主足球机器人导航。
火星863计划课题“人体三维尺寸的非接触测量”,采用“双视点投影光栅三维测量”原理,由双摄像机获取图像对,通过计算机进行图像数据处理,不仅可以获取服装设计所需的特征尺寸,还可根据需要获取人体图像上任意一点的三维坐标。
四.笔者总结
本文从双目立体视觉的三个最基本的匹配算法出发,讲述了其基本原理、步骤及其opencv代码实现并对双目立体视觉的发展趋势及现状做了总结,可供读者参考借鉴,若有纰漏,请见谅!