一、概述
距离变换有很多应用,在图像分割、医学成像、图像处理(如细化)、运动规划等等。
计算源图像的每个像素到最近的零像素的距离。函数 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 3
:a=0.955, b=1.3693
5 x 5
:a=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

