简介
连通域标记算法(connected component labeling,CCL)作为连通域分析算法(connected component analysis,CCA)的前置已经迭代了很多年了,从最开始的深度优先或广度优先搜索实现连通域标记到后来两遍扫描法连通域标记,算法的效率也有了极大的提升,虽然是同样的效果但是根据没有免费午餐定理可以大致猜到此算法的效率一定是伴随着空间复杂度的提升而提升。
事实也是如此,从效率最低的深度优先算法来看,两遍扫描的本质就是将深度优先遍历所使用到的栈进行“平铺”、“展开”,借助等价表(equivalence table)来来进行标记的替换。原本的深度优先算法需要进行大量的进栈出栈操作,有着极大的运行开销,等价表的出现正如我们进行拼图归类一般一定不是把某一类找齐再找下一类,而是拿过来一个看看符合哪一类再放到那一类,这是一个典型的空间换时间的做法,否则会非常消耗时间。
由于线性表可以有俩个信息(index,element),连通域的标记也可以设定为顺序标记,所以Rosenfeld证明了一种做法,即我们可以先尝试将所有的前景进行初次的简单分类,然后通过线性表的两个信息实现传递闭包,使得线性表可以很好的模拟深度优先的递归场景,即从递归改为循环。
算法流程
伪代码中使用了一个阶梯状的pattern,每次遍历的时候都会检查当前元素的上面元素和左边元素。考虑一个现实的情况(1代表前景,0代表背景):
0 | 0 | 0 | 0 |
---|---|---|---|
0 | 0 | 0 | 1 |
0 | 0 | 1 | 1 |
0 | 1 | 1 | 1 |
标记矩阵初始化为同形状的全0矩阵,等价表初始化:[0,1,2,3]
当算法扫描到(1,3)时,E[1,3]=1;扫描到(2.2)时,E[2,2]=2;扫描到(2,3)时进入else分支,判断当前元素上面的1和左边的2谁是最小正整数,然后更新等价表为[0,1,1,3],E[2,3]=1;扫描到(3,1)时E[3,1]=3; 扫描到(3,2)时,判断当前元素上面的2和左边的3谁是最小正整数,然后更新等价表为[0,1,1,2],E[3,2]=2;同理扫描到(3,3)时,判断当前元素上面的1和左边的2,E[3,3]=1。此时标记矩阵为:
0 | 0 | 0 | 0 |
---|---|---|---|
0 | 0 | 0 | 1 |
0 | 0 | 2 | 1 |
0 | 3 | 2 | 1 |
等价表为:[0,1,1,2]
通过肉眼可以判断此图中只有一个连通域,但是等价表中除了背景却有两个标记,此时需要进行等价传递(Transitive Closure),更新等价表为:[0,1,1,1],自此第一遍标记已经完成。
第二次标记就比较简单了,只需要根据等价表对E进行重新标记即可。
实现
import cv2
import numpy as np
def Find(e,T):
while T[e]!=e:
e=T[e]
return e
def Union(e1,e2,T):
r1=Find(e1,T)
r2=Find(e2,T)
if r1<r2:
T[r2]=r1
else:
T[r1]=r2
def Transitive_Closure(T,ne):