OpenCV 图像分析之 —— 距离变换

本文最后更新于:2022年5月21日 凌晨

函数 cv2.distanceTransform() 用于计算图像中每一个非零点像素与其最近的零点像素之间的距离(Distance Transform, DT算法),本文记录OpenCV 距离变换相关内容。

距离变换

OpenCV中,函数cv2.distanceTransform()用于计算图像中每一个非零点像素与其最近的零点像素之间的距离,输出的是保存每一个非零点与最近零点的距离信息;图像上越亮的点,代表了离零点的距离越远。

  • 图像的距离变换定义为一幅新图像,其中每个输出像素的值被设为输入图像中与最近的零像素的距离一当然得根据某个特定的距离度量。不难看出,距离变换生成的是某种边缘图像。在大多数应用中,距离变换的输入是边缘检测器的输出,就像把Canny边缘检测器反过来(使得边缘变成0,其他非边缘变成非0)。

  • 计算距离变换的方式有两种:

    • 第一种方法是使用通常为3×3或5×5阵列掩膜,数组中的每个点都定义了与掩膜中心相对的特定位置的点的“距离”。更大的距离是作为掩膜成员定义的“移动”序列被构建的(并且因此是近似的)。使用这个方法时,给定一个特定的距离度量,OpenCV就会自动从集合中选择一种近似掩膜。这是Borgefors在1986年提出的“原始”方法。

      原始论文

    • 第二种方法计算精确的距离,由 Felzenszwalb 提出。

      原始论文

两种方法运算复杂度与像素数都是线性关系,但精确的算法会稍慢一点。

原始 DT 算法

参考论文: 《Distance Transformations in Digital Images》

  • 计算二维图像中非特征点距离最近特征点的距离,例如:

  • 其中 * 为特征点,右图记录了非特征点到特征点的距离

  • 算法需要一个距离模板,可以按照 L1, L2 的定义来配置,也可以自定义,中心必须为0

    • 以最简单的模板示例

      1 1 1
      1 0 1
      1 1 1
  • 该模板在特征点或定义过距离的像素上滑动,覆盖到未定义距离的点时,记录当前中心像素值和模板位置的值之和,放到该未定义点的候选距离列表中

  • 之后每个被覆盖到的未定义点从距离和中选择最小的作为自己的距离定义

  • 对定义过的像素遍历完成后即可开启下一轮遍历,表示为:

$$ v_{i, j}^{m}=\operatorname{minimum}_{(k, l) \in \operatorname{mask}}\left(v_{i+k, j+1}^{m-1}+c(k, l)\right) $$
  • 其中$v_{i,j}^m$为第 $m$ 轮迭代时图像中$(i,j)$的像素值,$c(k,l)$ 为模板中对应的值
  • 直到某一轮迭代后没有值被修改,或所有值都被定义过距离
  • 引用原文的示例:

该方法计算出的不是精确的距离,胜在速度较快

OpenCV 实现

cv2.distanceTransform()

为源图像的每个像素计算到最近零像素的距离。

官方文档

  • 函数使用
1
2
3
4
5
6
7
cv2.distanceTransform(
src, # 二通道二值图,uint8 格式
distanceType, # 距离类型
maskSize[, # 距离变换掩码的大小
dst[,
dstType]] # 要生成的标签数组的类型
) -> dst
参数 含义
cv2.DIST_USER 用户自定义距离
cv2.DIST_L1 L1 距离 $
cv2.DIST_L2 L2 距离,欧氏距离
cv2.DIST_C $max(
cv2.DIST_L12 $2(sqrt(1+x*x/2) - 1))$
cv2.DIST_FAIR $ c^2(
cv2.DIST_WELSCH $ c2/2(1-exp(-(x/c)2)), c = 2.9846$
cv2.DIST_HUBER $
参数 含义
cv2.DIST_MASK_3 mask=3
cv2.DIST_MASK_5 mask=5
cv2.DIST_MASK_PRECISE 该函数不支持该参数
  • 示例代码

    图片来自 知乎

1
2
3
4
img_org = mt.cv_rgb_imread('dis_trans.jpg', gray=True)
img = (img_org > 100).astype('uint8')
res = cv2.distanceTransform(img, distanceType=cv2.DIST_L2, maskSize=0)
PIS(img>0, res)

cv2.distanceTransformWithLabels

距离变换的另一个重载,不仅计算最短零距离,也会报告最小距离对应的对象。

官方文档

  • 函数使用
1
2
3
4
5
6
7
8
cv2.distanceTransformWithLabels(
src,
distanceType,
maskSize[,
dst[,
labels[,
labelType]]]) ->
dst, labels
参数 含义
cv2.DIST_LABEL_CCOMP Src 中的每个零点连接元件(图论)(以及所有最接近连接元件(图论)的非零像素)都会被分配相同的标签
cv2.DIST_LABEL_PIXEL 每个零像素(以及离它最近的所有非零像素)都有自己的标签。
  • 示例代码
1
2
3
4
5
img_org = mt.cv_rgb_imread('dis_trans.jpg', gray=True)
img = (img_org > 100).astype('uint8')
res_ccomp = cv2.distanceTransformWithLabels(img, distanceType=cv2.DIST_L2, maskSize=0, labelType=cv2.DIST_LABEL_CCOMP)
res_pixel = cv2.distanceTransformWithLabels(img, distanceType=cv2.DIST_L2, maskSize=0, labelType=cv2.DIST_LABEL_PIXEL)
PIS(img_org, res_ccomp[0], res_ccomp[1] % 17, res_pixel[1] % 17)

参考资料