OpenCV每日函数 杂项图像转换模块 (3) distanceTransform函数

一、概述

        距离变换有很多应用,在图像分割、医学成像、图像处理(如细化)、运动规划等等。

        计算源图像的每个像素到最近的零像素的距离。函数 cv::distanceTransform 计算从每个二值图像像素到最近的零像素的近似或精确距离。 对于零图像像素,距离显然为零。        

        当 maskSize == DIST_MASK_PRECISE 和 distanceType == DIST_L2 时,该函数运行(Pedro Felzenszwalb 和 Daniel Huttenlocher。 采样函数的距离变换。康奈尔大学,2004 年) 中描述的算法。 该算法与 TBB 库并行化。

        在其他情况下,使用算法(古尼拉·博尔格福斯。 数字图像中的距离变换。 计算机视觉、图形和图像处理,34(3):344–371, 1986)。 这意味着对于一个像素,该函数会找到到最近的零像素的最短路径,其中包括基本移位: 水平、垂直、对角线或骑士的移动(最新可用于 5×5 mask)。

        总距离计算为这些基本距离的总和。 由于距离函数应该是对称的,所有水平和垂直移动必须具有相同的成本(表示为 a),所有对角线移动必须具有相同的成本(表示为 b),并且所有骑士的移动必须具有相同的成本 (记为 c)。对于 DIST_C 和 DIST_L1 类型,距离是精确计算的,而对于 DIST_L2(欧几里得距离),距离只能用相对误差计算(5×5 掩码给出更准确的结果)。 对于 a、b 和 c,OpenCV 使用原始论文中建议的值:

  • DIST_L1: a = 1, b = 2
  • DIST_L2:
    • 3 x 3a=0.955, b=1.3693
    • 5 x 5a=1, b=1.4, c=2.1969
  • DIST_C: a = 1, b = 1

        通常,对于快速、粗略的距离估计 DIST_L2,使用 3×3 掩码。 为了更准确的距离估计 DIST_L2,使用 5×5 掩码或精确算法。 请注意,精确算法和近似算法在像素数上都是线性的。

        该函数的这个变体不仅计算每个像素 (x,y) 的最小距离,还识别由零像素 (labelType==DIST_LABEL_CCOMP) 或最近的零像素 (labelType==DIST_LABEL_PIXEL) 组成的最近连接分量。 组件/像素的索引存储在标签(x,y)中。 当 labelType==DIST_LABEL_CCOMP 时,该函数自动查找输入图像中零像素的连通分量,并用不同的标签标记它们。 当 labelType==DIST_LABEL_PIXEL 时,该函数扫描输入图像并用不同的标签标记所有零像素。

        在这种模式下,复杂度仍然是线性的。 也就是说,该函数提供了一种非常快速的方法来计算二值图像的 Voronoi 图。 目前,第二种变体只能使用近似距离变换算法,即尚不支持 maskSize=DIST_MASK_PRECISE。

二、distanceTransform函数

1、函数原型

void 	cv::distanceTransform (InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP)
void 	cv::distanceTransform (InputArray src, OutputArray dst, int distanceType, int maskSize, int dstType=CV_32F)

2、参数详解

src 8 位、单通道(二进制)源图像。
dst 输出具有计算距离的图像。 它是与 src 大小相同的 8 位或 32 位浮点单通道图像。
labels 输出二维标签数组(离散 Voronoi 图)。 它的类型为 CV_32SC1,大小与 src 相同。
distanceType 距离类型,请参阅距离类型
maskSize 距离变换蒙版的大小,请参阅 DistanceTransformMasks。 此变体不支持 DIST_MASK_PRECISE。 在 DIST_L1 或 DIST_C 距离类型的情况下,该参数被强制为 3,因为 3×3 蒙版与 5×5 或任何更大的光圈给出相同的结果。
labelType 要构建的标签数组的类型,请参阅 DistanceTransformLabelTypes。

三、OpenCV源码

1、源码路径

opencv\modules\imgproc\src\distransform.cpp

2、源码代码

void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labels,
                            int distType, int maskSize, int labelType )
{
    CV_INSTRUMENT_REGION();

    Mat src = _src.getMat(), labels;
    bool need_labels = _labels.needed();

    CV_Assert( src.type() == CV_8UC1);

    _dst.create( src.size(), CV_32F);
    Mat dst = _dst.getMat();

    if( need_labels )
    {
        CV_Assert( labelType == DIST_LABEL_PIXEL || labelType == DIST_LABEL_CCOMP );

        _labels.create(src.size(), CV_32S);
        labels = _labels.getMat();
        maskSize = CV_DIST_MASK_5;
    }

    float _mask[5] = {0};

    if( maskSize != CV_DIST_MASK_3 && maskSize != CV_DIST_MASK_5 && maskSize != CV_DIST_MASK_PRECISE )
        CV_Error( CV_StsBadSize, "Mask size should be 3 or 5 or 0 (precise)" );

    if( distType == CV_DIST_C || distType == CV_DIST_L1 )
        maskSize = !need_labels ? CV_DIST_MASK_3 : CV_DIST_MASK_5;
    else if( distType == CV_DIST_L2 && need_labels )
        maskSize = CV_DIST_MASK_5;

    if( maskSize == CV_DIST_MASK_PRECISE )
    {

#ifdef HAVE_IPP
        CV_IPP_CHECK()
        {
#if IPP_DISABLE_PERF_TRUE_DIST_MT
            if(cv::getNumThreads()<=1 || (src.total()<(int)(1<<14)))
#endif
            {
                IppStatus status;
                IppiSize roi = { src.cols, src.rows };
                Ipp8u *pBuffer;
                int bufSize=0;

                status = ippiTrueDistanceTransformGetBufferSize_8u32f_C1R(roi, &bufSize);
                if (status>=0)
                {
                    pBuffer = (Ipp8u *)CV_IPP_MALLOC( bufSize );
                    status = CV_INSTRUMENT_FUN_IPP(ippiTrueDistanceTransform_8u32f_C1R, src.ptr<uchar>(), (int)src.step, dst.ptr<float>(), (int)dst.step, roi, pBuffer);
                    ippFree( pBuffer );
                    if (status>=0)
                    {
                        CV_IMPL_ADD(CV_IMPL_IPP);
                        return;
                    }
                    setIppErrorStatus();
                }
            }
        }
#endif

        trueDistTrans( src, dst );
        return;
    }

    CV_Assert( distType == CV_DIST_C || distType == CV_DIST_L1 || distType == CV_DIST_L2 );

    getDistanceTransformMask( (distType == CV_DIST_C ? 0 :
        distType == CV_DIST_L1 ? 1 : 2) + maskSize*10, _mask );

    Size size = src.size();

    int border = maskSize == CV_DIST_MASK_3 ? 1 : 2;
    Mat temp( size.height + border*2, size.width + border*2, CV_32SC1 );

    if( !need_labels )
    {
        if( maskSize == CV_DIST_MASK_3 )
        {
#if defined (HAVE_IPP) && (IPP_VERSION_X100 >= 700) && 0  // disabled: https://github.com/opencv/opencv/issues/15904
            CV_IPP_CHECK()
            {
                IppiSize roi = { src.cols, src.rows };
                if (CV_INSTRUMENT_FUN_IPP(ippiDistanceTransform_3x3_8u32f_C1R, src.ptr<uchar>(), (int)src.step, dst.ptr<float>(), (int)dst.step, roi, _mask) >= 0)
                {
                    CV_IMPL_ADD(CV_IMPL_IPP);
                    return;
                }
                setIppErrorStatus();
            }
#endif

            distanceTransform_3x3(src, temp, dst, _mask);
        }
        else
        {
#if defined (HAVE_IPP) && (IPP_VERSION_X100 >= 700)
            CV_IPP_CHECK()
            {
                IppiSize roi = { src.cols, src.rows };
                if (CV_INSTRUMENT_FUN_IPP(ippiDistanceTransform_5x5_8u32f_C1R, src.ptr<uchar>(), (int)src.step, dst.ptr<float>(), (int)dst.step, roi, _mask) >= 0)
                {
                    CV_IMPL_ADD(CV_IMPL_IPP);
                    return;
                }
                setIppErrorStatus();
            }
#endif

            distanceTransform_5x5(src, temp, dst, _mask);
        }
    }
    else
    {
        labels.setTo(Scalar::all(0));

        if( labelType == CV_DIST_LABEL_CCOMP )
        {
            Mat zpix = src == 0;
            connectedComponents(zpix, labels, 8, CV_32S, CCL_WU);
        }
        else
        {
            int k = 1;
            for( int i = 0; i < src.rows; i++ )
            {
                const uchar* srcptr = src.ptr(i);
                int* labelptr = labels.ptr<int>(i);

                for( int j = 0; j < src.cols; j++ )
                    if( srcptr[j] == 0 )
                        labelptr[j] = k++;
            }
        }

       distanceTransformEx_5x5( src, temp, dst, labels, _mask );
    }
}

void cv::distanceTransform( InputArray _src, OutputArray _dst,
                            int distanceType, int maskSize, int dstType)
{
    CV_INSTRUMENT_REGION();

    if (distanceType == CV_DIST_L1 && dstType==CV_8U)
        distanceTransform_L1_8U(_src, _dst);
    else
        distanceTransform(_src, _dst, noArray(), distanceType, maskSize, DIST_LABEL_PIXEL);

}

四、效果图像示例

1、距离变换效果示例1

原图
距离变换可视化

 2、距离变换效果示例2

原图
距离变换可视化

猜你喜欢

转载自blog.csdn.net/bashendixie5/article/details/125277681
今日推荐