算法篇 — EDT 欧式距离变换

Posted by Xun on Monday, May 23, 2022

距离变换是计算并标识空间点(对目标点)距离的过程,其中 EDT(Euclidean Distance Transform),欧式距离变换精度高,与实际距离相符,应用更广泛。

简介

  • 在二值图像中,白色代表目标点,黑色代表背景点,EDT 能将二值图转化为灰度图,其中每个栅格的灰度值等于它到最近的背景点的距离。
  • EDT 有多类算法,主要有:
    • 暴力算法
    • 光栅扫描算法
    • 独立扫描算法
    • 传播算法
  • 算法不同,实现效率也不一样,后面会介绍各类算法的实现细节,这里先定义一些通用概念:
    • width : 图像宽度
    • height : 图像高度
    • colorMap : 整个图像的颜色值

暴力算法(Brute Force)

简介

  • 暴力算法,顾名思义,就是计算二值图像中每一个白色点到每一个黑色点的距离,取其中的最小值,则为该点的距离。

伪代码

  • 遍历图像的每一个点,对于点 (i, j)
    • 如果点 (i, j) 为黑色,则该点距离为 0
    • 如果点 (i, j) 为白色,则遍历图像的每一个点,对于点 (m, n)
      • 如果点 (m, n) 为黑色,计算点 (i, j) 到点 (m, n) 的距离
      • 比较计算得到的距离,取最小值,则为点 (i, j) 的距离

算法分析

  • 对于每一个点,都需要遍历一遍所有点,来计算距离。假设图片的宽高都为 n ,则时间复杂度为 O(n^4) 。
  • 暴力算法是最基础的算法,尽管时间复杂度高,但是算法相对简单,对一些离线生成的情况来说,还是在可接受的范围内。

光栅扫描算法(Raster Scanning)

简介

  • EDT 算法中,使用最广泛的算法之一,是 Danielsson 在 1980 年提出的 4SED(4-point Sequential Euclidean Distance Transform)算法。尽管该算法相对简单且有效,但不是准确的。经过后续的各种优化,8SSEDT(8-point Signed Sequential Euclidean Distance Transform)是比较常用的光栅扫描算法。
  • 8SSEDT 算法,对于图像的每一个点,需要记录其到黑色点的相对坐标信息,即
    • dx :水平相对坐标。
    • dy :竖直相对坐标。
  • 距离
    • 通过坐标来计算,即 dx * dx + dy * dy 。
  • 基于邻居点的距离
    • 邻居点的坐标,加上邻居点相对于点 (i, j) 的权重(即偏移坐标),得到的新坐标的距离,则为基于邻居点的距离。当 (0, 0) 点在左上角时,偏移坐标为:
      • 左:(-1, 0)
      • 上:(0, -1)
      • 右:(1, 0)
      • 下:(0, 1)
      • 左上:(-1, -1)
      • 右上:(1, -1)
      • 左下:(-1, 1)
      • 右下:(1, 1)

伪代码

SSEDT.png
4SSEDT(左)和 8SSEDT(右)的权重 Mask

  • 初始化
    • 遍历图像的每一个点,进行初始化,对于点 (i, j)
      • 如果点 (i, j) 为白色,则设置 dx、dy 为无穷大(通常设置为图像的宽高)
      • 如果点 (i, j) 为黑色,则设置 dx、dy 为 0
  • 从上到下扫描
    • 对当前行,从左到右扫描,对每个点进行处理
      • 计算点 (i, j) 的距离
      • 分别计算点 (i, j) 基于邻居点左、上、左上、右上的距离,取其中的最小距离,如果点 (i, j) 的距离大于最小距离,则将点 (i, j) 的坐标更新为最小距离的邻居点的坐标加上权重得到的新坐标。
    • 对当前行,从右到左扫描,对每个点进行处理
      • 计算点 (i, j) 的距离
      • 分别计算点 (i, j) 基于邻居点右的距离,如果点 (i, j) 的距离大于基于邻居点右的距离,则将点 (i, j) 的坐标更新为邻居点右的坐标加上权重得到的新坐标。
  • 从下到上扫描
    • 对当前行,从右到左扫描,对每个点进行处理
      • 计算点 (i, j) 的距离
      • 分别计算点 (i, j) 基于邻居点右、下、左下、右下的距离,取其中的最小距离,如果点 (i, j) 的距离大于最小距离,则将点 (i, j) 的坐标更新为最小距离的邻居点的坐标加上权重得到的新坐标。
    • 对当前行,从左到右扫描,对每个点进行处理
      • 计算点 (i, j) 的距离
      • 分别计算点 (i, j) 基于邻居点左的距离,如果点 (i, j) 的距离大于基于邻居点右的距离,则将点 (i, j) 的坐标更新为邻居点右的坐标加上权重得到的新坐标。

算法分析

  • 8SSEDT 算法,进行了4次扫描,每次扫描都会比较部分邻居点,即每次扫描都是在线性时间内完成。假设图片的宽高都为 n ,则时间复杂度为 O(n^2) 。
  • 相比暴力算法,性能上有了较大的提升。通过使用坐标表示,计算基于邻居点的距离时,可以减少大量平方根的运算。

独立扫描算法(Independent Scanning)

简介

  • Saito 和 Toriwaki [Saito and Toriwaki 1994] 设计了一种算法对 k 维图像进行 k 变换来产生EDT,每个坐标方向一个变换。对于 2D 图像,首先沿着每一行计算距离值(第一个变换),然后使用这些值计算每列的最小距离(第二次变换)。
    • 变换一:给定输入图像 F,独立扫描的第一个变换产生图像 G ,则 G(i, j) 为点 (i, j) 到同一行中最近的黑色点的距离,这种变换是通过执行向前扫描(即从左到右),然后在图像的每一行进行向后扫描来有效地实现的。 Math_Independent_1.png

    • 变换二:从变换一的 G 开始,变换产生一个图像 S ,它是 F 的距离映射。对于点 (i,j) 所在的列,点 (i, j) 到当前列的每一个点 (x, j) 的距离(即到每一行的距离),加上 G(x, j) 的值,其中的最小值,则为点 (i, j) 在 F 中的距离,即 S(i, j)。 Math_Independent_2.png

伪代码

Independent_Tranform1.png
Saito 算法 变换一
Independent_Tranform2.png
Saito 算法 变换二

  • 变换一,对图像的每一行,进行两次扫描
    • 从左到右扫描,对于点 (i, j)
      • 如果点 (i, j) 为黑色点,则设置 G(i, j) 为 0 ,记录或更新黑色点的位置。
      • 如果点 (i, j) 为白色点,则设置 G(i, j) 为当前点到左侧黑色点的距离,如果没有黑色点则设置为图像的宽度值。
    • 从右到左扫描,对于点 (i, j)
      • 如果点 (i, j) 为黑色点,则设置 G(i, j) 为 0 ,记录或更新黑色点的位置。
      • 如果点 (i, j) 为白色点,计算当前点到右侧黑色点的距离,如果没有黑色点则设置为图像的宽度值。如果计算得到的距离小于 G(i, j) 的值,则更新 G(i, j) 的值为新的距离。
  • 变换二,遍历图像的每个点,对于点 (i, j)
    • 计算点 (i, j) 到当前列的每一个点 (x, j) 的距离,其中 x 为 [0, height - 1]。
    • 将点 (i, j) 到当前列点 (x, j) 的距离,分别加上 G(x, j) 的值,取其中的最小值,则为点 (i, j) 的距离。

算法分析

  • 假设图片的宽高都为 n ,对于变换一,对于每一行,通过采用两遍扫描的方式,实现在线性时间下,计算每个点的水平距离。而对于变换二,每个点需要计算和当前列每个点的距离,则最终算法的时间复杂度为 O(n^3) 。
  • 相比 8SSEDT 算法,Saito 的算法尽管效率没有那么高,但它很容易编码,并且它很方便扩展到 3D 情况中。

算法对比

A.png

  • 以上图为样本,对各种尺寸的样本进行算法计算,其中
    • 正:原样本图
    • 反:样本图黑白反色
  • 各种算法耗时情况如下表:
图片尺寸 暴力算法(ms) 光栅扫描算法(ms) 独立扫描算法(ms)
64 x 64(正) 28.37 4.99 2.99
64 x 64(反) 136.45 3.99 2.98
128 x 128(正) 359.42 16.95 23.43
128 x 128(反) 2175.82 16.98 24.93
256 x 256(正) 5359.35 67.14 187.76
256 x 256(反) 34540.57 68.25 186.51
512 x 512(正) 87196.00 273.38 1474.67
512 x 512(反) 569392.50 274.32 1478.42
1024 x 1024(正) - 1112.65 11766.52
1024 x 1024(反) - 1109.51 11755.97
2048 x 2048(正) - 5420.58 93695.14
2048 x 2048(反) - 5401.31 93836.86

总结

  • 暴力算法在一定范围内,由于实现简单,一定程度上耗时还是相对能接受的。
  • 独立扫描算法表现较好,但处理较大图片的时候耗时相对还是较高,但是算法相对简单,方便扩展。
  • 光栅扫描算法是其中表现最好的,对于 2048 x 2048 的图片,耗时也相对较低,因此也是使用相对较广泛的算法。

示例工程

参考