opencv中四种⽴体匹配算法⽰例(StereoBM,StereoSGBM,StereoB。。。
OpenCV3.4.4
StereoBM,StereoSGBM在正式库中
StereoBinaryBM, StereoBinarySGBM在contrib模块中
1 StereoBM
// 预处理滤波参数
preFilterType:预处理滤波器的类型,主要是⽤于降低亮度失真(photometric distortions)、消除噪声和增强纹理等, 有两种可选类型:CV_STEREO_BM_NORMALIZED_RESPONSE(归⼀化响应) 或者 CV_STEREO_BM_XSOBEL(⽔平⽅向Sobel算⼦,默认类型), 该参数为 int 型;
preFilterSize:预处理滤波器窗⼝⼤⼩,容许范围是[5,255],⼀般应该在 5x5…21x21 之间,参数必须为奇数值, int 型
preFilterCap:预处理滤波器的截断值,预处理的输出值仅保留[-preFilterCap, preFilterCap]范围内的值,参数范围:1 - 31(⽂档中是31,但代码中是 63), int
// SAD 参数
SADWindowSize:SAD窗⼝⼤⼩,容许范围是[5,255],⼀般应该在 5x5 ⾄ 21x21 之间,参数必须是奇数,int 型
minDisparity:最⼩视差,默认值为 0, 可以是负值,int 型
numberOfDisparities:视差窗⼝,即最⼤视差值与最⼩视差值之差, 窗⼝⼤⼩必须是 16 的整数倍,int 型
// 后处理参数
textureThreshold:低纹理区域的判断阈值。如果当前SAD窗⼝内所有邻居像素点的x导数绝对值之和⼩于指定阈值,则该窗⼝对应的像素点的视差值为 0(That is, if the sum of absolute values of x-derivatives computed over SADWindowSize by SADWindowSize pixel neighborhood is smaller than the parameter, no disparity is computed at the pixel),该参数不能为负值,int 型uniquenessRatio:视差唯⼀性百分⽐, 视差窗⼝范围内最低代价是次低代价的(1 + uniquenessRatio/100)倍时,最低代价对应的视差值才是该像素点的视差,否则该像素点的视差为 0 (the minimum margin in percents between the best (minimum) cost function value and the cond best value to accept the computed
disparity, that is, accept the computed disparity d^ only if SAD(d) >= SAD(d^) x (1 + uniquenessRatio/100.) for any d != d*+/-1 within the arch range ),该参数不能为负值,⼀般5-15左右的值⽐较合适,int 型
东华小学speckleWindowSize:检查视差连通区域变化度的窗⼝⼤⼩, 值为 0 时取消 speckle 检查,int 型
speckleRange:视差变化阈值,当窗⼝内视差变化⼤于阈值时,该窗⼝内的视差清零,int 型
// OpenCV2.1 新增的状态参数
roi1, roi2:左右视图的有效像素区域,⼀般由双⽬校正阶段的 cvStereoRectify 函数传递,也可以⾃⾏设定。⼀旦在状态参数中设定了roi1 和 roi2,OpenCV 会通过cvGetValidDisparityROI 函数计算出视差图的有效区域,在有效区域外的视差值将被清零。
disp12MaxDiff:左视差图(直接计算得出)和右视差图(通过cvValidateDisparity计算得出)之间的最⼤容许差异。超过该阈值的视差值将被清零。该参数默认为 -1,即不执⾏左右视差检查。int 型。注意在程序调试阶段最好保持该值为 -1,以便查看不同视差窗⼝⽣成的视差效果。具体请参见《使⽤OpenGL动态显⽰双⽬视觉三维重构效果⽰例》⼀⽂中的讨论。
牛郎织女缩写在上述参数中,对视差⽣成效果影响较⼤的主要参数是 SADWindowSize、numberOfDisparities 和 u
niquenessRatio 三个,⼀般只需对这三个参数进⾏调整,其余参数按默认设置即可。
在OpenCV2.1中,BM算法有C和C++ 两种实现模块。
2 StereoSGBM
SGBM算法的状态参数⼤部分与BM算法的⼀致,下⾯只解释不同的部分:
SADWindowSize:SAD窗⼝⼤⼩,容许范围是[1,11],⼀般应该在 3x3 ⾄ 11x11 之间,参数必须是奇数,int 型
P1, P2:控制视差变化平滑性的参数。P1、P2的值越⼤,视差越平滑。P1是相邻像素点视差增/减 1 时的惩罚系数;P2是相邻像素点视差变化值⼤于1时的惩罚系数。P2必须⼤于P1。OpenCV2.1提供的例程 stereo_match.cpp 给出了 P1 和 P2 ⽐较合适的数值。fullDP:布尔值,当设置为 TRUE 时,运⾏双通道动态编程算法(full-scale 2-pass dynamic programming algorithm),会占⽤
O(W H numDisparities)个字节,对于⾼分辨率图像将占⽤较⼤的内存空间。⼀般设置为 FALSE。
注意OpenCV2.1的SGBM算法是⽤C++ 语⾔编写的,没有C实现模块。与H. Hirschmuller提出的原算法相⽐,主要有如下变化:
算法默认运⾏单通道DP算法,只⽤了5个⽅向,⽽fullDP使能时则使⽤8个⽅向(可能需要占⽤⼤量内存)。
算法在计算匹配代价函数时,采⽤块匹配⽅法⽽⾮像素匹配(不过SADWindowSize=1时就等于像素匹配了)。
燕麦南瓜粥匹配代价的计算采⽤BT算法(“Depth Discontinuities by Pixel-to-Pixel Stereo” by S. Birchfield and C. Tomasi),并没有实现基于互熵信息的匹配代价计算。
增加了⼀些BM算法中的预处理和后处理程序。
原理解释
⽬前⽴体匹配算法是计算机视觉中的⼀个难点和热点,算法很多,但是⼀般的步骤是:
A、匹配代价计算
匹配代价计算是整个⽴体匹配算法的基础,实际是对不同视差下进⾏灰度相似性测量。常见的⽅法有灰度差的平⽅SD(squared intensity differences),灰度差的绝对值AD(absolute intensity differences)等。另外,在求原始匹配代价时可以设定⼀个上限值,来减弱叠加过程中的误匹配的影响。以AD法求匹配代价为例,可⽤下式进⾏计算,其中T为设定的阈值。
这就是在参数设置中阈值的作⽤,在视差图中经常有⿊⾊区域,就是和阈值的设置关。
B、 匹配代价叠加
⼀般来说,全局算法基于原始匹配代价进⾏后续算法计算。⽽区域算法则需要通过窗⼝叠加来增强匹配代价的可靠性,根据原始匹配代价不同,可分为:
此图是核⼼算法的解释,就是计算区域内像素差值,可以为单个像素也可以为⼀定区域内,主要看SAD的窗⼝⼤⼩的设置,同时SAD设置决定误匹配的多少和运算效率问题,所以⼤⼩设置⼀定要很慎重。
C、 视差获取
对于区域算法来说,在完成匹配代价的叠加以后,视差的获取就很容易了,只需在⼀定范围内选取叠加匹配代价最优的点(SAD和SSD取最⼩值,NCC取最⼤值)作为对应匹配点,如胜者为王算法WTA(Winner-take-all)。⽽全局算法则直接对原始匹配代价进⾏处理,⼀般会先给出⼀个能量评价函数,然后通过不同的优化算法来求得能量的最⼩值,同时每个点的视差值也就计算出来了。
D、视差细化(亚像素级)
⼤多数⽴体匹配算法计算出来的视差都是⼀些离散的特定整数值,可满⾜⼀般应⽤的精度要求。但在⼀些精度要求⽐较⾼的场合,如精确的三维重构中,就需要在初始视差获取后采⽤⼀些措施对视差进⾏细化,如匹配代价的曲线拟合、图像滤波、图像分割等。
亚像素级的处理就是涉及到BMState参数设置后后续参数的设置了。
有关⽴体匹配的介绍和常见匹配算法的⽐较,推荐⼤家看看Stefano Mattoccia 的讲义 Stereo Vision: algorithms and
applications,190页的ppt,讲解得⾮常形象详尽。
⽤于⽴体匹配的图像可以是彩⾊的吗?
在OpenCV3.4.4中,BM算法只能对8位灰度图像计算视差,SGBM算法则可以处理24位(8bits*3)彩⾊图像。所以在读⼊图像时,应该根据采⽤的算法来处理图像:
怎样获取与原图像有效像素区域相同的视差图?
在OpenCV2.0及以前的版本中,所获取的视差图总是在左侧和右侧有明显的⿊⾊区域,这些区域没有有效的视差数据。视差图有效像素区域与视差窗⼝(ndisp,⼀般取正值且能被16整除)和最⼩视差值
(mindisp,⼀般取0或负值)相关,视差窗⼝越⼤,视差图左侧的⿊⾊区域越⼤,最⼩视差值越⼩,视差图右侧的⿊⾊区域越⼤。其原因是为了保证参考图像(⼀般是左视图)的像素点能在⽬标图像(右视图)中按照设定的视差匹配窗⼝匹配对应点,OpenCV 只从参考图像的第 (ndisp - 1 + mindisp) 列开始向右计算视差,第 0 列到第 (ndisp -1 + mindisp) 列的区域视差统⼀设置为 (mindisp - 1) *16;视差计算到第 width + mindisp 列时停⽌,余下的右侧区域视差值也统⼀设置为 (mindisp - 1) *16。
static const int DISPARITY_SHIFT = 4;
…
int ndisp = state->numberOfDisparities;
int mindisp = state->minDisparity;
int lofs = MAX(ndisp - 1 + mindisp, 0);
int rofs = -MIN(ndisp - 1 + mindisp, 0);
int width = left->cols, height = left->rows;
int width1 = width - rofs - ndisp + 1;
short FILTERED = (short)((mindisp - 1) << DISPARITY_SHIFT);
initialize the left and right borders of the disparity map
for( y = 0; y < height; y++ )
{
for( x = 0; x < lofs; x++ )
dptr[y dstep + x] = FILTERED;
for( x = lofs + width1; x < width; x++ )
dptr[y dstep + x] = FILTERED;
}
dptr += lofs;
for( x = 0; x < width1; x++, dptr++ )
这样的设置很明显是不符合实际应⽤的需求的,它相当于把摄像头的视场范围缩窄了。因此,OpenCV2.1 做了明显的改进,不再要求左右视图和视差图的⼤⼩(size)⼀致,允许对视差图进⾏左右边界延拓,这样,虽然计算视差时还是按上⾯的代码思路来处理左右边界,但是视差图的边界得到延拓后,有效视差的范围就能够与对应视图完全对应。具体的实现代码范例如下:
//
// 对左右视图的左边进⾏边界延拓,以获取与原始视图相同⼤⼩的有效视差区域
copyMakeBorder(img1r, img1b, 0, 0, m_nMaxDisp, 0, IPL_BORDER_REPLICATE);
copyMakeBorder(img2r, img2b, 0, 0, m_nMaxDisp, 0, IPL_BORDER_REPLICATE);
//
// 计算视差
if( alg == STEREO_BM )
{
bm(img1b, img2b, dispb);
// 截取与原始画⾯对应的视差区域(舍去加宽的部分)
displf = lRange(m_nMaxDisp, ls);
}
el if(alg == STEREO_SGBM)
{
sgbm(img1b, img2b, dispb);
displf = lRange(m_nMaxDisp, ls);
}
4. StereoBM和StereoSGBM的输出结果好像不是以像素点为单位的视差?
BM函数得出的结果是以16位符号数的形式的存储的,出于精度需要,所有的视差在输出时都扩⼤了16倍(2^4)。其具体代码表⽰如下:
dptr[y*dstep] = (short)(((ndisp - mind - 1 + mindisp)*256 + (d != 0 ? (p-n)*128/d : 0) + 15) >> 4);
可以看到,原始视差在左移8位(256)并且加上⼀个修正值之后⼜右移了4位,最终的结果就是左移4位。
因此,在实际求距离时,cvReprojectTo3D出来的X/W,Y/W,Z/W都要乘以16 (也就是W除以16),才能得到正确的三维坐标信息。”
如何实现视差图的伪彩⾊显⽰?
⾸先要将16位符号整形的视差矩阵转换为8位⽆符号整形矩阵,然后按照⼀定的变换关系进⾏伪彩⾊处理。我的实现代码如下:
// 转换为 CV_8U 格式,彩⾊显⽰
dispLfcv = displf, dispRicv = dispri, disp8cv = disp8;
if (alg == STEREO_GC)
{
cvNormalize( &dispLfcv, &disp8cv, 0, 256, CV_MINMAX );
}
el
{
张开眼睛}
F_Gray2Color(&disp8cv, vdispRGB);
灰度图转伪彩⾊图的代码,主要功能是使灰度图中 亮度越⾼的像素点,在伪彩⾊图中对应的点越趋向于 红⾊;亮度越低,则对应的伪彩⾊越趋向于 蓝⾊;总体上按照灰度值⾼低,由红渐变⾄蓝,中间⾊为绿⾊。其对应关系如
void Gray2Color(CvMat* gray_mat, CvMat* color_mat)
{
if(color_mat)
cvZero(color_mat);
int stype = CV_MAT_TYPE(gray_mat->type), dtype = CV_MAT_TYPE(color_mat->type);
int rows = gray_mat->rows, cols = gray_mat->cols;
// 判断输⼊的灰度图和输出的伪彩⾊图是否⼤⼩相同、格式是否符合要求
if (CV_ARE_SIZES_EQ(gray_mat, color_mat) && stype == CV_8UC1 && dtype == CV_8UC3)
嘴巴周围干燥起皮
{
CvMat* red = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
CvMat* green = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
CvMat* blue = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
CvMat* mask = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
/
/ 计算各彩⾊通道的像素值
cvSubRS(gray_mat, cvScalar(255), blue); // blue(I) = 255 - gray(I)
cvCopy(gray_mat, red); // red(I) = gray(I)
cvCopy(gray_mat, green); // green(I) = gray(I),if gray(I) < 128
cvCmpS(green, 128, mask, CV_CMP_GE ); // green(I) = 255 - gray(I), if gray(I) >= 128
cvSubRS(green, cvScalar(255), green, mask);
cvConvertScale(green, green, 2.0, 0.0);
// 合成伪彩⾊图
cvMerge(blue, green, red, NULL, color_mat);
cvReleaMat( &red );
cvReleaMat( &green );
cvReleaMat( &blue );
cvReleaMat( &mask );
形容心情的诗句
}
}
⽰例代码
#include <opencv2/opencv.hpp>
#include "opencv2/stereo.hpp"
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <io.h> //对系统⽂件进⾏操作的头⽂件 11.26 by zww
using namespace std;
using namespace cv;
using namespace cv::stereo;
/* --------------------------------------------
/* --------------------------------------------
* 功能:获取⽂件夹下所有的⽂件名
* 输⼊:File_Directory 为⽂件夹⽬录
* FileType 为需要查找的⽂件类型
* FilesName 为存放⽂件名的容器
----------------------------------------------*/
void getFilesName(std::string &File_Directory, std::string &FileType, std::vector<std::string>&FilesName) {
std::string buffer = File_Directory + "\\*" + FileType;
_finddata_t c_file; // 存放⽂件名的结构体,需要包含头⽂件#include <io.h>
long hFile;
hFile = _findfirst(buffer.c_str(), &c_file); //找第⼀个⽂件名
if (hFile == -1L) // 检查⽂件夹⽬录下存在需要查找的⽂件
printf("No %s files in current directory!\n", FileType.c_str());
el
{
std::string fullFilePath;
do什么中外
怎么夸美女{
fullFilePath.clear();
fullFilePath = File_Directory + "\\" + c_file.name;
FilesName.push_back(fullFilePath);
} while (_findnext(hFile, &c_file) == 0); //如果找到下个⽂件的名字成功的话就返回0,否则返回-1
_findclo(hFile);
}
}
void CensusTransform(Mat src_image, Mat &dst_image, int window_sizex, int window_sizey)
{
int image_height = ws;
int image_width = ls;
dst_image = Mat::zeros(image_height, image_width, CV_64F);
//-----------census变换 ---------------------------------
int offtx = (window_sizex - 1) / 2;
int offty = (window_sizey - 1) / 2;
for (int j = 0; j < image_width - window_sizex; j++)
{
for (int i = 0; i < image_height - window_sizey; i++)
{
unsigned long census = 0;
uchar current_pixel = src_image.at<uchar>(i + offty, j + offtx); //窗⼝中⼼像素
Rect roi(j, i, window_sizex, window_sizey); //⽅形窗⼝
Mat window(src_image, roi);
for (int a = 0; a < window_sizey; a++)
{
for (int b = 0; b < window_sizex; b++)
{
if (!(a == offty && b == offtx))//中⼼像素不做判断
{
census = census << 1;//左移1位
}
uchar temp_value = window.at<uchar>(a, b);
if (temp_value <= current_pixel) //当前像素⼩于中⼼像素 01
{
census += 1;
}
}
}
dst_image.at<double>(i + offty, j + offtx) = census;
}