几种视差图后处理方法,包括滤波去噪(中值滤波或双边滤波)、连通域检测和左右一致性检测。
1)滤波去噪
滤波去躁主要用于去除视差图中由于误匹配造成的孤立噪点,视差图后处理中常用的两种滤波方法有中值滤波和双边滤波。
中值滤波是一种典型的非线性滤波技术,其基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值,该方法在去除脉冲噪声、椒盐噪声的同时又能保留图像的边缘细节。如下图所示:
双边滤波是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。双边滤波器顾名思义比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。如下图所示:
2)连通域检测
连通域检测主要用于去除视差图中由于误匹配造成的小团块。其算法大概流程如下:
0.首先设定speckleRange和speckleWindowSize,speckleWindowSize是指设置检测出的连通域中像素点个数,也就是连通域的大小。speckleRange是指设置判断两个点是否属于同一个连通域的阈值条件。
1.判断当前像素点四邻域的邻域点与当前像素点的差值diff,如果diff<speckleRange,则表示该邻域点与当前像素点是一个连通域,设置一个标记。然后再以该邻域点为中心判断其四邻域点,步骤同上。直至某一像素点四邻域的点均不满足条件,则停止。
2.步骤1完成后,判断被标记的像素点个数count,如果像素点个数count<=speckleWindowSize,则说明该连通域是一个小团块(blob),则将当前像素点值设置为newValue(表示错误的视差值,newValue一般设置为负数或者0值)。否则,表示该连通域是个大团块,不做处理。同时建立标记值与是否为小团块的关系表rtype[label],rtype[label]为0,表示label值对应的像素点属于小团块,为1则不属于小团块。
3.处理下一个像素点时,先判断其是否已经被标记:如果已经被标记,则根据关系表rtype[label]判断是否为小团块(blob),如果是,则直接将该像素值设置为newValue;如果不是,则不做处理。继续处理下一个像素。如果没有被标记,则按照步骤1处理。
4.所有像素点处理后,满足条件的区域会被设置为newValue值。
代码:
typedef cv::Point_<short> Point2s;
void myFilterSpeckles(cv::Mat &img, int newVal, int maxSpeckleSize, int maxDiff)
{
int width = img.cols;
int height = img.rows;
int imgSize = width * height;
int *pLabelBuf = (int*)malloc(sizeof(int)*imgSize);//标记值buffer
Point2s *pPointBuf = (Point2s*)malloc(sizeof(short)*imgSize);//点坐标buffer
uchar *pTypeBuf = (uchar*)malloc(sizeof(uchar)*imgSize);//blob判断标记buffer
//初始化Labelbuffer
int currentLabel = 0;
memset(pLabelBuf, 0, sizeof(int)*imgSize);
for (int i = 0; i < height; i++)
{
float *pData = img.ptr<float>(i);
int *pLabel = pLabelBuf + width * i;
for (int j = 0; j < width; j++)
{
if ((pData[j] - newVal) > 1e-1)
{
if (pLabel[j])
{
if (pTypeBuf[pLabel[j]])
{
pData[j] = newVal;
}
}
else
{
Point2s *pWave = pPointBuf;
Point2s curPoint(j, i);
currentLabel++;
int count = 0;
pLabel[j] = currentLabel;
while (pWave >= pPointBuf)
{
count++;
float *pCurPos = &img.at<float>(curPoint.y, curPoint.x);
float curValue = *pCurPos;
int *pCurLabel = pLabelBuf + width * curPoint.y + curPoint.x;
//bot
if (curPoint.y < height - 1 && !pCurLabel[+width] && pCurPos[+width] != newVal && abs(curValue - pCurPos[+width]) <= maxDiff)
{
pCurLabel[+width] = currentLabel;
*pWave++ = Point2s(curPoint.x, curPoint.y + 1);
}
//top
if (curPoint.y > 0 && !pCurLabel[-width] && pCurPos[-width] != newVal && abs(curValue - pCurPos[-width]) <= maxDiff)
{
pCurLabel[-width] = currentLabel;
*pWave++ = Point2s(curPoint.x, curPoint.y - 1);
}
//right
if (curPoint.x < width - 1 && !pCurLabel[+1] && pCurPos[+1] != newVal && abs(curValue - pCurPos[+1]) <= maxDiff)
{
pCurLabel[+1] = currentLabel;
*pWave++ = Point2s(curPoint.x + 1, curPoint.y);
}
//left
if (curPoint.x > 0 && !pCurLabel[-1] && pCurPos[-1] != newVal && abs(curValue - pCurPos[-1]) <= maxDiff)
{
pCurLabel[-1] = currentLabel;
*pWave++ = Point2s(curPoint.x - 1, curPoint.y);
}
--pWave;
curPoint = *pWave;
}
if (count <= maxSpeckleSize)
{
pTypeBuf[pLabel[j]] = 1;
pData[j] = (float)newVal;
}
else
{
pTypeBuf[pLabel[j]] = 0;
}
}
}
}
}
free(pLabelBuf);
free(pPointBuf);
free(pTypeBuf);
}
3)左右一致性检测
左右一致性检测是用于遮挡检测的,具体做法:
根据左右两幅输入图像,分别得到左右两幅视差图。对于左图中的一个点p,求得的视差值是d1,那么p在右图里的对应点应该是(p-d1),(p-d1)的视差值记作d2。若|d1-d2|>threshold,p标记为遮挡点(occluded point)。