OpenCV3.4两种立体匹配算法效果对比(包含视差空洞填充代码)

本文对比了OpenCV中BM和SGBM两种立体匹配算法的效果,并详细介绍了两种算法的参数设置、预处理及后处理过程,包括空洞填充方法。

OpenCV3.4两种立体匹配算法效果对比

 

 以OpenCV自带的Aloe图像对为例:

   

1.BM算法(Block Matching)

参数设置如下:

复制代码

    int numberOfDisparities = ((imgSize.width / 8) + 15) & -16;
    cv::Ptr<cv::StereoBM> bm = cv::StereoBM::create(16, 9);
    cv::Rect roi1, roi2;
    bm->setROI1(roi1);
    bm->setROI2(roi2);
    bm->setPreFilterCap(31);
    bm->setBlockSize(9);
    bm->setMinDisparity(0);
    bm->setNumDisparities(numberOfDisparities);
    bm->setTextureThreshold(10);
    bm->setUniquenessRatio(15);
    bm->setSpeckleWindowSize(100);
    bm->setSpeckleRange(32);
    bm->setDisp12MaxDiff(1);
    bm->compute(imgL, imgR, disp);

复制代码

效果如下:

BM算法得到的视差图(左),空洞填充后得到的视差图(右)

  

2.SGBM(Semi-Global Block matching)算法:

参数设置如下:

复制代码

enum { STEREO_BM = 0, STEREO_SGBM = 1, STEREO_HH = 2, STEREO_VAR = 3, STEREO_3WAY = 4 };
    int numberOfDisparities = ((imgSize.width / 8) + 15) & -16;
    cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(0, 16, 3);
    sgbm->setPreFilterCap(63);
    int SADWindowSize = 9;
    int sgbmWinSize = SADWindowSize > 0 ? SADWindowSize : 3;
    sgbm->setBlockSize(sgbmWinSize);
    int cn = imgL.channels();
    sgbm->setP1(8 * cn*sgbmWinSize*sgbmWinSize);
    sgbm->setP2(32 * cn*sgbmWinSize*sgbmWinSize);
    sgbm->setMinDisparity(0);
    sgbm->setNumDisparities(numberOfDisparities);
    sgbm->setUniquenessRatio(10);
    sgbm->setSpeckleWindowSize(100);
    sgbm->setSpeckleRange(32);
    sgbm->setDisp12MaxDiff(1);

    int alg = STEREO_SGBM;
    if (alg == STEREO_HH)
        sgbm->setMode(cv::StereoSGBM::MODE_HH);
    else if (alg == STEREO_SGBM)
        sgbm->setMode(cv::StereoSGBM::MODE_SGBM);
    else if (alg == STEREO_3WAY)
        sgbm->setMode(cv::StereoSGBM::MODE_SGBM_3WAY);
    sgbm->compute(imgL, imgR, disp);

复制代码

 效果如图:

SGBM算法得到的视差图(左),空洞填充后得到的视差图(右)

   

可见SGBM算法得到的视差图相比于BM算法来说,减少了很多不准确的匹配点,尤其是在深度不连续区域,速度上SGBM要慢于BM算法。OpenCV3.0以后没有实现GC算法,可能是出于速度考虑,以后找时间补上对比图,以及各个算法的详细原理分析。

后面我填充空洞的效果不是很好,如果有更好的方法,望不吝赐教。

 


preFilterCap()匹配图像预处理

  •  两种立体匹配算法都要先对输入图像做预处理,OpenCV源码中中调用函数 static void prefilterXSobel(const cv::Mat& src, cv::Mat& dst, int preFilterCap),参数设置中preFilterCap在此函数中用到。函数步骤如下,作用主要有两点:对于无纹理区域,能够排除噪声干扰;对于边界区域,能够提高边界的区分性,利于后续的匹配代价计算:
  1. 先利用水平Sobel算子求输入图像x方向的微分值Value;
  2. 如果Value<-preFilterCap, 则Value=0;
    如果Value>preFilterCap,则Value=2*preFilterCap;
    如果Value>=-preFilterCap &&Value<=preFilterCap,则Value=Value+preFilterCap;
  3. 输出处理后的图像作为下一步计算匹配代价的输入图像。

复制代码

static void prefilterXSobel(const cv::Mat& src, cv::Mat& dst, int ftzero)
{
    int x, y;
    const int OFS = 256 * 4, TABSZ = OFS * 2 + 256;
    uchar tab[TABSZ];
    cv::Size size = src.size();

    for (x = 0; x < TABSZ; x++)
        tab[x] = (uchar)(x - OFS < -ftzero ? 0 : x - OFS > ftzero ? ftzero * 2 : x - OFS + ftzero);
    uchar val0 = tab[0 + OFS];

    for (y = 0; y < size.height - 1; y += 2)
    {
        const uchar* srow1 = src.ptr<uchar>(y);
        const uchar* srow0 = y > 0 ? srow1 - src.step : size.height > 1 ? srow1 + src.step : srow1;
        const uchar* srow2 = y < size.height - 1 ? srow1 + src.step : size.height > 1 ? srow1 - src.step : srow1;
        const uchar* srow3 = y < size.height - 2 ? srow1 + src.step * 2 : srow1;
        uchar* dptr0 = dst.ptr<uchar>(y);
        uchar* dptr1 = dptr0 + dst.step;

        dptr0[0] = dptr0[size.width - 1] = dptr1[0] = dptr1[size.width - 1] = val0;
        x = 1;
        for (; x < size.width - 1; x++)
        {
            int d0 = srow0[x + 1] - srow0[x - 1], d1 = srow1[x + 1] - srow1[x - 1],
                d2 = srow2[x + 1] - srow2[x - 1], d3 = srow3[x + 1] - srow3[x - 1];
            int v0 = tab[d0 + d1 * 2 + d2 + OFS];
            int v1 = tab[d1 + d2 * 2 + d3 + OFS];
            dptr0[x] = (uchar)v0;
            dptr1[x] = (uchar)v1;
        }
    }

    for (; y < size.height; y++)
    {
        uchar* dptr = dst.ptr<uchar>(y);
        x = 0;
        for (; x < size.width; x++)
            dptr[x] = val0;
    }
}

复制代码

自己实现的函数如下:

复制代码

void mySobelX(cv::Mat srcImg, cv::Mat dstImg, int preFilterCap)
{
    assert(srcImg.channels() == 1);
    int radius = 1;
    int width = srcImg.cols;
    int height = srcImg.rows;
    uchar *pSrcData = srcImg.data;
    uchar *pDstData = dstImg.data;
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int idx = i*width + j;
            if (i >= radius && i < height - radius && j >= radius && j < width - radius)
            {
                int diff0 = pSrcData[(i - 1)*width + j + 1] - pSrcData[(i - 1)*width + j - 1];
                int diff1 = pSrcData[i*width + j + 1] - pSrcData[i*width + j - 1];
                int diff2 = pSrcData[(i + 1)*width + j + 1] - pSrcData[(i + 1)*width + j - 1];

                int value = diff0 + 2 * diff1 + diff2;
                if (value < -preFilterCap)
                {
                    pDstData[idx] = 0;
                }
                else if (value >= -preFilterCap && value <= preFilterCap)
                {
                    pDstData[idx] = uchar(value + preFilterCap);
                }
                else
                {
                    pDstData[idx] = uchar(2 * preFilterCap);
                }

            }
            else
            {
                pDstData[idx] = 0;
            }
        }
    }
}

复制代码

 函数输入,输出结果如图:

    

 


 filterSpeckles()视差图后处理

  •  两种立体匹配算法在算出初始视差图后会进行视差图后处理,包括中值滤波,连通域检测等。其中中值滤波能够有效去除视差图中孤立的噪点,而连通域检测能够检测出视差图中因噪声引起小团块(blob)。在BM和SGBM中都有speckleWindowSize和speckleRange这两个参数,speckleWindowSize是指设置检测出的连通域中像素点个数,也就是连通域的大小。speckleRange是指设置判断两个点是否属于同一个连通域的阈值条件。大概流程如下:
  1. 判断当前像素点四邻域的邻域点与当前像素点的差值diff,如果diff<speckRange,则表示该邻域点与当前像素点是一个连通域,设置一个标记。然后再以该邻域点为中心判断其四邻域点,步骤同上。直至某一像素点四邻域的点均不满足条件,则停止。
  2. 步骤1完成后,判断被标记的像素点个数count,如果像素点个数count<=speckleWindowSize,则说明该连通域是一个小团块(blob),则将当前像素点值设置为newValue(表示错误的视差值,newValue一般设置为负数或者0值)。否则,表示该连通域是个大团块,不做处理。同时建立标记值与是否为小团块的关系表rtype[label],rtype[label]为0,表示label值对应的像素点属于小团块,为1则不属于小团块。
  3. 处理下一个像素点时,先判断其是否已经被标记:
    如果已经被标记,则根据关系表rtype[label]判断是否为小团块(blob),如果是,则直接将该像素值设置为newValue;如果不是,则不做处理。继续处理下一个像素。
    如果没有被标记,则按照步骤1处理。
  4. 所有像素点处理后,满足条件的区域会被设置为newValue值,后续可以用空洞填充等方法重新估计其视差值。

OpenCV中有对应的API函数,void filterSpeckles(InputOutputArray img, double newVal, int maxSpeckleSize, double maxDiff, InputOutputArray buf=noArray() ) 

函数源码如下,使用时根据视差图或者深度图数据类型设置模板中的数据类型:

复制代码

typedef cv::Point_<short> Point2s;
template <typename T> void filterSpecklesImpl(cv::Mat& img, int newVal, int maxSpeckleSize, int maxDiff, cv::Mat& _buf)
{
    using namespace cv;

    int width = img.cols, height = img.rows, npixels = width*height;
    size_t bufSize = npixels*(int)(sizeof(Point2s) + sizeof(int) + sizeof(uchar));
    if (!_buf.isContinuous() || _buf.empty() || _buf.cols*_buf.rows*_buf.elemSize() < bufSize)
        _buf.create(1, (int)bufSize, CV_8U);

    uchar* buf = _buf.ptr();
    int i, j, dstep = (int)(img.step / sizeof(T));
    int* labels = (int*)buf;
    buf += npixels * sizeof(labels[0]);
    Point2s* wbuf = (Point2s*)buf;
    buf += npixels * sizeof(wbuf[0]);
    uchar* rtype = (uchar*)buf;
    int curlabel = 0;

    // clear out label assignments
    memset(labels, 0, npixels * sizeof(labels[0]));

    for (i = 0; i < height; i++)
    {
        T* ds = img.ptr<T>(i);
        int* ls = labels + width*i;

        for (j = 0; j < width; j++)
        {
            if (ds[j] != newVal)   // not a bad disparity
            {
                if (ls[j])     // has a label, check for bad label
                {
                    if (rtype[ls[j]]) // small region, zero out disparity
                        ds[j] = (T)newVal;
                }
                // no label, assign and propagate
                else
                {
                    Point2s* ws = wbuf; // initialize wavefront
                    Point2s p((short)j, (short)i);  // current pixel
                    curlabel++; // next label
                    int count = 0;  // current region size
                    ls[j] = curlabel;

                    // wavefront propagation
                    while (ws >= wbuf) // wavefront not empty
                    {
                        count++;
                        // put neighbors onto wavefront
                        T* dpp = &img.at<T>(p.y, p.x); //current pixel value
                        T dp = *dpp;
                        int* lpp = labels + width*p.y + p.x; //current label value

                        //bot
                        if (p.y < height - 1 && !lpp[+width] && dpp[+dstep] != newVal && std::abs(dp - dpp[+dstep]) <= maxDiff)
                        {
                            lpp[+width] = curlabel;
                            *ws++ = Point2s(p.x, p.y + 1);
                        }
                        //top
                        if (p.y > 0 && !lpp[-width] && dpp[-dstep] != newVal && std::abs(dp - dpp[-dstep]) <= maxDiff)
                        {
                            lpp[-width] = curlabel;
                            *ws++ = Point2s(p.x, p.y - 1);
                        }
                        //right
                        if (p.x < width - 1 && !lpp[+1] && dpp[+1] != newVal && std::abs(dp - dpp[+1]) <= maxDiff)
                        {
                            lpp[+1] = curlabel;
                            *ws++ = Point2s(p.x + 1, p.y);
                        }
                        //left
                        if (p.x > 0 && !lpp[-1] && dpp[-1] != newVal && std::abs(dp - dpp[-1]) <= maxDiff)
                        {
                            lpp[-1] = curlabel;
                            *ws++ = Point2s(p.x - 1, p.y);
                        }
                        

                        // pop most recent and propagate
                        // NB: could try least recent, maybe better convergence
                        p = *--ws;
                    }

                    // assign label type
                    if (count <= maxSpeckleSize)   // speckle region
                    {
                        rtype[ls[j]] = 1;   // small region label
                        ds[j] = (T)newVal;
                    }
                    else
                        rtype[ls[j]] = 0;   // large region label
                }
            }
        }
    }
}

复制代码

 或者下面博主自己整理一遍的代码:

复制代码

typedef cv::Point_<short> Point2s;
template <typename T> 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++)
    {
        T *pData = img.ptr<T>(i);
        int *pLabel = pLabelBuf + width*i;
        for (int j = 0; j < width; j++)
        {
            if (pData[j] != newVal)
            {
                if (pLabel[j])
                {
                    if (pTypeBuf[pLabel[j]])
                    {
                        pData[j] = (T)newVal;
                    }
                }
                else
                {
                    Point2s *pWave = pPointBuf;
                    Point2s curPoint((T)j, (T)i);
                    currentLabel++;
                    int count = 0;
                    pLabel[j] = currentLabel;
                    while (pWave >= pPointBuf)
                    {
                        count++;
                        T *pCurPos = &img.at<T>(curPoint.y, curPoint.x);
                        T 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] = (T)newVal;
                    }
                    else
                    {
                        pTypeBuf[pLabel[j]] = 0;
                    }
                }
            }
        }
    }

    free(pLabelBuf);
    free(pPointBuf);
    free(pTypeBuf);
}

复制代码

 

如下视差图中左上角部分有7个小团块,设置speckleWindowSize和speckleRange分别为50和32,连通域检测后结果为如下图右,小团块能够全部检测出来,方便后续用周围视差填充。当然还有一个缺点就是,图像中其他地方尤其是边界区域也会被检测为小团块,后续填充可能会对边界造成平滑。

    

 

视差孔洞填充代码:https://www.cnblogs.com/riddick/p/8486223.html

<think> 我们正在处理立体视觉中的视差图后处理问题。根据引用,我们知道: 1. SGBM算法得到的视差图相比于BM算法更优,但可能存在一些空洞(不准确的匹配点)。 2. 引用[3]展示了如何将视差图转换为深度图(C++代码)。 3. 引用[4]提供了一个C++的空洞填充示例(用于二值图像)。 用户需求:使用Python和OpenCV对SGBM算法得到的视差图进行空洞填充。 分析: 视差图是单通道图像,通常为16位有符号整数(CV_16S)或32位浮点数,但SGBM输出的视差图通常是16位有符号整数(每个像素值乘以16,所以实际视差值需要除以16)。视差图中的空洞(无效像素)通常用0表示(或者一个特定的最小值)。 空洞填充方法: 1. 我们可以先将视差图中的无效像素(0值)标记出来,然后进行填充。 2. 填充方法有多种,例如:使用邻近有效像素的平均值、中值,或者使用图像修复算法(如inpaint)等。 注意:引用[4]中的空洞填充方法是针对二值图像的(比如前景和背景),但我们的视差图是灰度图(实际上是视差值),所以不能直接使用二值图像的空洞填充方法。 我们可以考虑: 方法1:使用OpenCV的inpaint函数进行图像修复,将0值(空洞)作为掩膜中的待修复区域。 方法2:使用形态学操作(如膨胀)来填充,但可能不太准确。 方法3:使用邻近像素的插值(例如,线性插值或最近邻插值)。 根据引用[4]中的方法思路,它是填充二值图像中的空洞,即找到背景中的孔洞并填充。但视差图空洞不是二值图像中的孔洞,而是灰度图像中值为0的像素,我们需要将这些0值替换为合理的视差值。 因此,我们选择方法1:使用inpaint函数。 步骤: 1.视差图转换为8位或32位浮点数?注意:inpaint函数要求目标图像是8位或32位浮点数。 - 由于视差图通常为16位,我们首先将16位视差图转换为浮点型(或32位整型)以便处理,同时注意保留视差值。 2. 创建一个掩膜,其中视差图中的无效像素(值为0)设置为255(表示需要修复),其他设置为0。 3. 使用inpaint函数进行修复。可以选择两种算法:INPAINT_NS(基于Navier-Stokes的方法)或INPAINT_TELEA(基于快速行进方法)。 但是,需要注意的是:视差图中的0值可能不仅仅是空洞,也可能是视差为0的像素(即距离无限远)。在立体匹配中,通常将0视为无效值。 另外,由于视差图是离散的,而且可能包含噪声,我们需要谨慎选择修复方法。 还有一种常用的方法是使用左右一致性检查(left-right consistency check)来检测遮挡区域(这些区域可能产生空洞),然后使用插值方法填充。但用户已经使用了SGBM算法,该算法本身可能已经包含一些后处理,我们这里只针对其输出的视差图进行空洞填充。 因此,我们给出一个简单的基于inpaint的填充方法。 代码步骤(Python): 1. 读取视差图(假设是16位有符号整数,实际视差值为disp/16.0)。 2.视差图转换为浮点型(或32位浮点)以便处理,并创建一个掩膜(mask),其中视差图中值为0的位置设为255(需要修复),非0位置设为0。 3. 使用inpaint函数填充。 但是,我们需要注意:inpaint函数通常用于彩色图像或灰度图像,它根据周围像素的梯度信息来填充。对于视差图,由于它代表的是距离信息,我们可以尝试用这种方法。 示例代码: ```python import cv2 import numpy as np # 假设视差图文件名为 &#39;disparity.png&#39; # 注意:SGBM输出的视差图是16位有符号整数(CV_16S),但实际存储时可能转换为16位无符号整数(需要转换回来)?读取时注意保持原格式。 disp = cv2.imread(&#39;disparity.png&#39;, cv2.IMREAD_UNCHANGED) # 保持原始格式读取 # 将16位有符号整数转换为浮点型,同时注意:SGBM的输出是乘以16的,所以实际视差需要除以16,但这里我们只填充,可以不除,因为填充后还要用。 # 但是,为了避免数值溢出,我们转换为32位浮点数 disp_float = disp.astype(np.float32) # 创建一个掩膜,标记无效像素(值为0) mask = np.zeros(disp.shape[:2], dtype=np.uint8) mask[disp == 0] = 255 # 注意:视差图中的0值被认为是空洞 # 使用inpaint函数 # 参数:目标图像,掩膜,修复半径(这里设为1),修复方法(INPAINT_NS 或 INPAINT_TELEA) inpaint_method = cv2.INPAINT_TELEA # 或者 cv2.INPAINT_NS filled_disp = cv2.inpaint(disp_float, mask, inpaintRadius=1, flags=inpaint_method) # 注意:填充后的图像是32位浮点数,我们可以根据需要转换回16位整数 # 例如,乘以16并转换为整数(如果原始视差图是乘以16的,那么填充后我们也保持这个关系) filled_disp_int = np.rint(filled_disp).astype(np.int16) # 四舍五入为整数 # 或者,如果后续需要用于深度图转换,可以直接使用浮点型的视差图。 # 保存填充后的视差图 cv2.imwrite(&#39;filled_disparity.png&#39;, filled_disp_int) ``` 然而,需要注意的是,inpaint函数可能对大的空洞区域填充效果不佳。另外,修复半径(inpaintRadius)也可以根据实际情况调整。 另一种方法是使用最近邻插值(或双线性插值)来填充空洞。我们可以通过寻找空洞区域,然后用最近的已知视差填充OpenCV中可以使用dilate操作(形态学膨胀)来快速填充,但可能会造成边缘扩张。 替代方法:基于最近邻的填充(使用形态学操作和掩膜): 1. 将原始视差图复制一份。 2. 创建一个掩膜,标记有效像素(非0)。 3. 利用距离变换,找到每个空洞像素到最近有效像素的位置,然后赋值(这可以通过形态学膨胀实现,但需要迭代直到所有空洞填充)。 我们也可以使用scipy的最近邻插值,但这里我们使用OpenCV的形态学操作来实现一种简单的填充: 步骤: a. 创建一个与视差图相同大小的图像,其中已知像素保留原值,未知像素(空洞)设置为0。 b. 创建一个掩膜,标记已知像素(非0)为1(True),未知为0(False)。 c. 使用距离变换计算每个未知像素到最近已知像素的距离,然后找到最近已知像素的值?或者使用迭代膨胀,每次用邻域内的非零像素的平均值(或最小值/最大值)来填充空洞。 但是,OpenCV有一个函数cv2.ximgproc.guidedFilter(),但这里我们不展开。 考虑到简单性,我们先给出inpaint的方法。如果效果不好,可以尝试其他方法。 因此,我们推荐使用inpaint方法进行尝试。 注意:由于视差图可能存在噪声和离散值,填充后可能会引入一些误差。因此,实际应用中需要根据场景调整。 根据引用[4]中提到的空洞填充代码(用于二值图像),我们可能需要的是灰度图像的空洞填充,所以不能直接使用。但是,我们可以将视差图归一化到0-255(转换为8位)进行填充,然后再转换回去?但是这样会损失精度。 所以,我们使用32位浮点数的inpaint。 最后,我们提醒用户:空洞填充只是后处理的一种,可能还需要其他后处理步骤(如滤波)来优化视差图。 相关问题: 1. 除了inpaint,还有哪些方法可以填充视差图中的空洞? 2. 如何评估视差图填充效果3. 视差图转换为深度图的原理是什么? 4. SGBM算法中有哪些参数可以调整以减少空洞的产生? 根据用户的需求,我们提供了基于inpaint的Python实现。 </think> ### 使用 Python 和 OpenCV 对 SGBM 视差图进行空洞填充的方法 要实现 SGBM 视差图空洞填充,核心是将无效视差值(通常表示为 0)替换为合理的估计值。以下是完整的解决方案: #### 步骤 1: 空洞填充原理 视差图中的空洞主要由以下原因造成: 1. 遮挡区域(无法匹配的像素) 2. 纹理缺乏区域(特征不足) 3. 深度不连续区域 填充方法使用**图像修复技术**,基于有效像素的空间连续性进行插值[^1][^2]。 #### 步骤 2: Python 实现代码 ```python import cv2 import numpy as np def fill_disparity_holes(disp_map, max_distance=5): """ 填充视差图中的空洞 :param disp_map: SGBM输出的视差图 (16位有符号整数) :param max_distance: 最大搜索距离(像素) :return: 填充后的视差图 """ # 创建掩膜:标记无效像素(0值) mask = np.uint8(disp_map == 0) # 转换为浮点数以便修复操作 disp_float = disp_map.astype(np.float32) # 使用快速行进法进行空洞填充(推荐用于深度图) filled_disp = cv2.inpaint( disp_float, mask, inpaintRadius=max_distance, flags=cv2.INPAINT_TELEA ) # 可选:使用中值滤波消除小噪声 # filled_disp = cv2.medianBlur(filled_disp, 3) return filled_disp.astype(np.int16) # 使用示例 if __name__ == "__main__": # 读取SGBM生成的原始视差图 raw_disp = cv2.imread("sgbm_disparity.png", cv2.IMREAD_UNCHANGED) # 执行空洞填充 filled_disp = fill_disparity_holes(raw_disp) # 保存结果 cv2.imwrite("filled_disparity.png", filled_disp) ``` #### 关键参数说明: 1. **`inpaintRadius`**:修复半径(建议 3-10 像素) - 值越大,填充范围越广,但可能模糊细节 - 值过小可能导致填充不完整 2. **填充算法选择**: - `cv2.INPAINT_TELEA`:基于快速行进法,适合深度图修复(推荐) - `cv2.INPAINT_NS`:基于流体力学模型,适合纹理修复 #### 效果优化技巧: 1. **预处理**: ```python # 移除小噪声区域 kernel = np.ones((3, 3), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) ``` 2. **后处理**: ```python # 加权中值滤波(保边) filled_disp = cv2.ximgproc.weightedMedianFilter( filled_disp.astype(np.uint8), None, 7 ) ``` 3. **多尺度修复**(适用于大面积空洞): ```python for radius in [10, 5, 3]: filled_disp = cv2.inpaint(filled_disp, mask, radius, cv2.INPAINT_TELEA) ``` > **注意**:SGBM 输出的视差图精度通常是实际值的 16 倍(`int16` 格式),填充后需保持此特性以便后续深度计算[^3]。 ### 不同方法的性能对比 | 方法 | 速度 | 精度 | 适用场景 | |------|------|------|----------| | `INPAINT_TELEA` | 快 | 中 | 小空洞、实时系统 | | `INPAINT_NS` | 慢 | 高 | 复杂边界、静态场景 | | 加权中值滤波 | 中 | 高 | 噪声较多场景 | | 多尺度修复 | 慢 | 最高 | 大面积空洞 | ### 实际应用建议 1. 对于实时应用(如机器人导航),建议组合使用: ```python # 快速填充+保边滤波 filled = fill_disparity_holes(disp, max_distance=5) result = cv2.ximgproc.guidedFilter(guide, filled, radius=8, eps=1000) ``` 2. 离线处理可尝试深度学习方案(如 GA-Net),但需训练数据支持[^2]。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值