以下内容不涉及原理,仅为工程性内容:
经典的立体匹配算法主要由:BM(Block Matching),SGBM(Semi-Global Block matching),GC。更高级的就直接用上了深度学习,这里就不在考虑了。
上述三种算法速度:BM > SGBM > GC,效果:BM < SGBM < GC;暂取折中的SGBM算法研究。
SGBM及相关程序参考博客:
SGBM算法参数众多,而且对于不同场景默认参数表现不好,这个问题也出现在了BM算法里面,由于接触的不多,并不清楚这个问题是不是立体匹配的通病。
我使用了KITTI截取的两张图片作为输入,但是在前几次输入时发现一个小问题,我的表现效果和博客上表现的相差极大:
这是我输出的时差图,可以明显看出这个图片表述的一塌糊涂,一开始我还以为是我参数的问题,所以我调了半天的参数,但是情况并没有本质的改变。于是我向沈老师请教。
沈老师给了我一个很有用的表格:
但是效果仍然改变不大。这时候老师突然想起来:你是不是左右图颠倒了?这个算法对于左右视图顺序是有要求的。
我试了一下,果然。下图是ndisparities为64、80、128时:
可以看出随着ndisparities逐渐增加,右侧的碎片数量逐渐减少。到128时基本符合我们的要求。当然,亮度有所下降,但是因为距离拉大了,这个倒是很正常。
值得注意的一点是disp这个参数,opencv解释
disp – Output disparity map. It is a 16-bit signed single-channel image of the same size as the input image. It contains disparity values scaled by 16. So, to get the floating-point disparity map, you need to divide each disp element by 16.
这个意思是:disp每个元素包含16位,其中12位整数,4位小数。如果转化为可读形式的,需要对1.0/16;
对应类型和比特的博客参考:
https://blog.csdn.net/YunLaowang/article/details/86583351
为了验证得到的时差图是否正确,我们需要手工标注验证一下:找出左图和右图中对应的一组特征点,打开画图工具得到他们的坐标,一般来说纵坐标是相同的。将横坐标相减得到时差,在时差图中找到改点(位置同左图),访问该点的元素。
当然,获得视差图仅仅是第一步,后面我们需要通过视差图来获得深度图。
获得深度图之前需要先对视差图进行预处理,我们预处理选择的是之前引用博客中作者给出的方法,直接copy来测试,然后利用视差公式求解深度。
这里有5点注意的地方:
视差图左边黑条不能去:左边黑条代表右侧相机没有采集到的位置,因此是没有深度的。
深度图的表示问题:如果仅仅使用8UC1还是没有问题的,但是万一需要使用16UC1及更高的32FC1等时,显示范围扩大到了255以上,这样在imshow基本就是一片黑,无法验证我们的猜想。有一个巧妙的思路是利用.xml文件采集图片的矩阵元素,这样可以看整体的情况,虽不是很直观,但是比一抹黑强很多。关于xml文件的使用详见博客https://blog.csdn.net/YunLaowang/article/details/86583351。另一种方法是对图像像素值进行缩放,至0-255之前,这样可以之间观察像素变化,但是缺点是由于缩放原因,像素差值不大。两种方法各有利弊,综合使用效果更好。
在对深度值缩放至0-255之间以方便进行表示时,删去空洞填充中高斯模糊部分。因为深度缩放后差距极小,很容易全部变成一种颜色。
一开始我模仿博客代码选择16UC1,但是由于不便于直接观察,因此我试图将depthMap选择我8UC1格式,但是结果显示,道路部分深度误差较大问题,在时差图上表现良好的道路在深度图中出现了明显的层次,详见下面:
经过老师沟通,发现是范围出现了问题,8U表示范围在2的8次方,255,而最大深度值f*b/1=718.856*35=25159.96,远超8位depthMap所能表示的数据范围,因此被截断了,出现了奇怪的现象。修正方法详见第二点,修正后的图片如下:
测试代码如下:
#include
#include
#include
#include
#include
#include
#include
//#include
//#include
using namespace std;
using namespace cv;
void insertDepth32f(cv::Mat& depth);
float fx = 718.856;
float baseline = 35;
int main(int argc, char const *argv[])
{
cv::Mat imgR,imgL;
imgL = cv::imread( argv[1],IMREAD_GRAYSCALE );
imgR = cv::imread( argv[2],IMREAD_GRAYSCALE );
int mindisparity = 0;
int ndisparities = 128;
int SADWindowSize = 11;
//SGBM
cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(mindisparity, ndisparities, SADWindowSize);
int P1 = 4 * imgL.channels() * SADWindowSize* SADWindowSize;
int P2 = 32 * imgR.channels() * SADWindowSize* SADWindowSize;
sgbm->setP1(P1);
sgbm->setP2(P2);
sgbm->setPreFilterCap(63);
sgbm->setUniquenessRatio(10);
sgbm->setSpeckleRange(32);
sgbm->setSpeckleWindowSize(100);
sgbm->setDisp12MaxDiff(1);
//sgbm->setMode(cv::StereoSGBM::MODE_HH);
cv::Mat disp32F;
sgbm->compute(imgL, imgR, disp32F);
disp32F.convertTo(disp32F, CV_32F, 1.0/16);
//float* inData = disp32F.ptr(284);
//cout << float(inData[322]) << endl;
//insertDepth32f(disp32F);
Mat disp8U = Mat(disp32F.rows, disp32F.cols, CV_8UC1);
//normalize(disp8U, disp32F, 0, 255, NORM_MINMAX, CV_8UC1);
disp32F.convertTo(disp8U, CV_8UC1);
imshow("disparity", disp8U);
waitKey(0);
cv::Mat depthMap = cv::Mat::zeros(disp32F.size(), CV_32FC1);
int height = disp32F.rows;
int width = disp32F.cols;
for(int k = 0;k < height; k++)
{
const float* inData = disp32F.ptr<float>(k);
float* outData = depthMap.ptr<float>(k);
for(int i = 0; i < width; i++)
{
if(!inData[i]) continue;
outData[i] = float(fx *baseline / inData[i]);
}
}
FileStorage fswrite("test.xml", FileStorage::WRITE);// 新建文件,覆盖掉已有文件
fswrite << "src1" << depthMap;
fswrite.release();
Mat depthMap8U = Mat(depthMap.rows, depthMap.cols, CV_8UC1);
normalize(depthMap, depthMap8U, 0, 255, NORM_MINMAX, CV_8U);
imshow("depth:8U",depthMap8U);
waitKey(0);
return 0;
}
void insertDepth32f(cv::Mat& depth)
{
const int width = depth.cols;
const int height = depth.rows;
float* data = (float*)depth.data;
cv::Mat integralMap = cv::Mat::zeros(height, width, CV_64F);
cv::Mat ptsMap = cv::Mat::zeros(height, width, CV_32S);
double* integral = (double*)integralMap.data;
int* ptsIntegral = (int*)ptsMap.data;
memset(integral, 0, sizeof(double) * width * height);
memset(ptsIntegral, 0, sizeof(int) * width * height);
for (int i = 0; i < height; ++i)
{
int id1 = i * width;
for (int j = 0; j < width; ++j)
{
int id2 = id1 + j;
if (data[id2] > 1e-3)
{
integral[id2] = data[id2];
ptsIntegral[id2] = 1;
}
}
}
// 积分区间
for (int i = 0; i < height; ++i)
{
int id1 = i * width;
for (int j = 1; j < width; ++j)
{
int id2 = id1 + j;
integral[id2] += integral[id2 - 1];
ptsIntegral[id2] += ptsIntegral[id2 - 1];
}
}
for (int i = 1; i < height; ++i)
{
int id1 = i * width;
for (int j = 0; j < width; ++j)
{
int id2 = id1 + j;
integral[id2] += integral[id2 - width];
ptsIntegral[id2] += ptsIntegral[id2 - width];
}
}
int wnd;
double dWnd = 2;
while (dWnd > 1)
{
wnd = int(dWnd);
dWnd /= 2;
for (int i = 0; i < height; ++i)
{
int id1 = i * width;
for (int j = 0; j < width; ++j)
{
int id2 = id1 + j;
int left = j - wnd - 1;
int right = j + wnd;
int top = i - wnd - 1;
int bot = i + wnd;
left = max(0, left);
right = min(right, width - 1);
top = max(0, top);
bot = min(bot, height - 1);
int dx = right - left;
int dy = (bot - top) * width;
int idLeftTop = top * width + left;
int idRightTop = idLeftTop + dx;
int idLeftBot = idLeftTop + dy;
int idRightBot = idLeftBot + dx;
int ptsCnt = ptsIntegral[idRightBot] + ptsIntegral[idLeftTop] - (ptsIntegral[idLeftBot] + ptsIntegral[idRightTop]);
double sumGray = integral[idRightBot] + integral[idLeftTop] - (integral[idLeftBot] + integral[idRightTop]);
if (ptsCnt <= 0)
{
continue;
}
data[id2] = float(sumGray / ptsCnt);
}
}
int s = wnd / 2 * 2 + 1;
if (s > 201)
{
s = 201;
}
//cv::GaussianBlur(depth, depth, cv::Size(s, s), s, s);
}
}