技术背景

连通性检测是图论中常常遇到的一个问题,我们可以用五子棋的思路来理解这个问题五子棋中,横、竖、斜相邻的两个棋子,被认为是相连接的,而一样的道理,在一个二维的图中,只要在横、竖、斜三个方向中的一个存在相邻的情况,就可以认为图上相连通的。比如以下案例中的python数组,3号元素和5号元素就是相连接的,5号元素和6号元素也是相连接的,因此这三个元素实际上是属于同一个区域的:

array([[0, 3, 0],[0, 5, 0],[6, 0, 0]])

而再如下面这个例子,其中的1、2、3三个元素是相连的,4、5、6三个元素也是相连的,但是这两个区域不存在连接性,因此这个网格被分成了两个区域:

array([[1, 0, 4],[2, 0, 5],[3, 0, 6]])

那么如何高效的检测一张图片或者一个矩阵中的所有连通区域并打上标签,就是我们所关注的一个问题。

Two-Pass算法

一个典型的连通性检测的方案是Two-Pass算法,该算法可以用如下的一张动态图来演示:

该算法的核心在于用两次的遍历,为所有的节点打上分区的标签,如果是不同的分区,就会打上不同的标签。其基本的算法步骤可以用如下语言进行概述:

  1. 遍历网格节点,如果网格的上、左、左上三个格点不存在元素,则为当前网格打上新的标签,同时标签编号加一;
  2. 当上、左、左上的网格中存在一个元素时,将该元素值赋值给当前的网格作为标签;
  3. 当上、左、左上的网格中有多个元素时,取最低值作为当前网格的标签;
  4. 在标签赋值时,留意标签上边和左边已经被遍历过的4个元素,将4个元素中的最低值与这四个元素分别添加到Union的数据结构中(参考链接1);
  5. 再次遍历网格节点,根据Union数据结构中的值刷新网格中的标签值,最终得到划分好区域和标签的元素矩阵。

测试数据的生成

这里我们以Python3为例,可以用Numpy来产生一系列随机的0-1矩阵,这里我们产生一个20*20大小的矩阵:

# two_pass.pyimport numpy as np
import matplotlib.pyplot as pltif __name__ == "__main__":np.random.seed(1)graph = np.random.choice([0,1],size=(20,20))print (graph)plt.figure()plt.imshow(graph)plt.savefig('random_bin_graph.png')

执行的输出结果如下:

$ python3 two_pass.py
[[1 1 0 0 1 1 1 1 1 0 0 1 0 1 1 0 0 1 0 0][0 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 0 0 0][1 1 1 1 1 1 0 1 1 0 0 1 0 0 1 1 1 0 1 0][0 1 1 0 1 1 1 1 0 0 1 1 0 0 0 0 1 1 1 0][1 0 0 1 1 0 1 1 0 1 0 0 1 1 1 0 1 1 0 1][1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0][0 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 1 1 1][1 1 0 1 0 1 0 0 0 1 1 1 0 1 0 0 0 0 1 0][1 0 1 1 1 0 0 0 0 0 0 1 0 0 1 0 0 1 1 0][0 0 1 0 0 0 0 1 0 0 0 0 1 1 0 0 1 1 1 0][0 0 0 0 1 1 1 0 1 1 0 0 0 1 1 0 1 1 1 0][1 1 1 1 0 1 0 0 1 0 1 0 1 1 0 1 1 0 1 1][1 0 1 0 1 0 1 1 1 1 1 1 0 0 1 1 0 0 0 1][1 0 0 0 0 0 1 1 1 1 1 1 1 0 0 1 0 0 0 1][0 1 0 1 0 0 0 0 1 1 0 0 0 1 0 1 1 0 0 1][0 1 0 0 0 1 0 1 0 1 1 1 0 1 0 1 1 1 1 0][0 1 0 0 0 0 1 1 0 1 1 0 0 1 1 1 1 1 1 1][0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 0][1 0 1 0 1 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0][0 1 1 0 1 0 1 0 1 1 0 0 1 0 0 0 0 0 1 1]]

同时会生成一张网格的图片:

其实从这个图片中我们可以看出,图片的上面部分几乎都是连接在一起的,只有最下面存在几个独立的区域。

Two-Pass算法的实现

这里需要说明的是,因为我们并没有使用Union的数据结构,而是只使用了Python的字典数据结构,因此代码写起来会比较冗余而且不是那么美观,但是这里我们主要的目的是先用代解决这一实际问题,因此代码乱就乱一点吧。

# two_pass.pyimport numpy as np
import matplotlib.pyplot as plt
from copy import deepcopydef first_pass(g) -> list:graph = deepcopy(g)height = len(graph)width = len(graph[0])label = 1index_dict = {}for h in range(height):for w in range(width):if graph[h][w] == 0:continueif h == 0 and w == 0:graph[h][w] = labellabel += 1continueif h == 0 and graph[h][w-1] > 0:graph[h][w] = graph[h][w-1]continueif w == 0 and graph[h-1][w] > 0:if graph[h-1][w] <= graph[h-1][min(w+1, width-1)]:graph[h][w] = graph[h-1][w]index_dict[graph[h-1][min(w+1, width-1)]] = graph[h-1][w]elif graph[h-1][min(w+1, width-1)] > 0:graph[h][w] = graph[h-1][min(w+1, width-1)]index_dict[graph[h-1][w]] = graph[h-1][min(w+1, width-1)]continueif h == 0 or w == 0:graph[h][w] = labellabel += 1continueneighbors = [graph[h-1][w], graph[h][w-1], graph[h-1][w-1], graph[h-1][min(w+1, width-1)]]neighbors = list(filter(lambda x:x>0, neighbors))if len(neighbors) > 0:graph[h][w] = min(neighbors)for n in neighbors:if n in index_dict:index_dict[n] = min(index_dict[n], min(neighbors))else:index_dict[n] = min(neighbors)continuegraph[h][w] = labellabel += 1return graph, index_dictdef remap(idx_dict) -> dict:index_dict = deepcopy(idx_dict)for id in idx_dict:idv = idx_dict[id]while idv in idx_dict:if idv == idx_dict[idv]:breakidv = idx_dict[idv]index_dict[id] = idvreturn index_dictdef second_pass(g, index_dict) -> list:graph = deepcopy(g)height = len(graph)width = len(graph[0])for h in range(height):for w in range(width):if graph[h][w] == 0:continueif graph[h][w] in index_dict:graph[h][w] = index_dict[graph[h][w]]return graphdef flatten(g) -> list:graph = deepcopy(g)fgraph = sorted(set(list(graph.flatten())))flatten_dict = {}for i in range(len(fgraph)):flatten_dict[fgraph[i]] = igraph = second_pass(graph, flatten_dict)return graphif __name__ == "__main__":np.random.seed(1)graph = np.random.choice([0,1],size=(20,20))graph_1, idx_dict = first_pass(graph)idx_dict = remap(idx_dict)graph_2 = second_pass(graph_1, idx_dict)graph_3 = flatten(graph_2)print (graph_3)plt.subplot(131)plt.imshow(graph)plt.subplot(132)plt.imshow(graph_3)plt.subplot(133)plt.imshow(graph_3>0)plt.savefig('random_bin_graph.png')

完整代码的输出如下所示:

$ python3 two_pass.py
[[1 1 0 0 1 1 1 1 1 0 0 1 0 1 1 0 0 1 0 0][0 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 0 0 0][1 1 1 1 1 1 0 1 1 0 0 1 0 0 1 1 1 0 1 0][0 1 1 0 1 1 1 1 0 0 1 1 0 0 0 0 1 1 1 0][1 0 0 1 1 0 1 1 0 1 0 0 1 1 1 0 1 1 0 1][1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0][0 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 1 1 1][1 1 0 1 0 1 0 0 0 1 1 1 0 1 0 0 0 0 1 0][1 0 1 1 1 0 0 0 0 0 0 1 0 0 1 0 0 1 1 0][0 0 1 0 0 0 0 1 0 0 0 0 1 1 0 0 1 1 1 0][0 0 0 0 1 1 1 0 1 1 0 0 0 1 1 0 1 1 1 0][1 1 1 1 0 1 0 0 1 0 1 0 1 1 0 1 1 0 1 1][1 0 1 0 1 0 1 1 1 1 1 1 0 0 1 1 0 0 0 1][1 0 0 0 0 0 1 1 1 1 1 1 1 0 0 1 0 0 0 1][0 1 0 2 0 0 0 0 1 1 0 0 0 1 0 1 1 0 0 1][0 1 0 0 0 1 0 1 0 1 1 1 0 1 0 1 1 1 1 0][0 1 0 0 0 0 1 1 0 1 1 0 0 1 1 1 1 1 1 1][0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 0][3 0 3 0 4 0 0 0 0 0 0 5 0 0 0 1 0 1 1 0][0 3 3 0 4 0 6 0 7 7 0 0 5 0 0 0 0 0 1 1]]

同样的我们可以看看此时得到的新的图像:

这里我们并列的画了三张图,第一张图是原图,第二张图是划分好区域和标签的图,第三张是对第二张图进行二元化的结果,以确保在运算过程中没有丢失原本的信息。经过确认这个标签的结果划分是正确的,但是因为涉及到一些算法实现的细节,这里我们还是需要展开来介绍一下。

算法的执行流程

if __name__ == "__main__":np.random.seed(1)graph = np.random.choice([0,1],size=(20,20))graph_1, idx_dict = first_pass(graph)idx_dict = remap(idx_dict)graph_2 = second_pass(graph_1, idx_dict)graph_3 = flatten(graph_2)

这个部分是算法的核心框架,在本文中的算法实现流程为:先用first_pass遍历一遍网格节点,按照上一个章节中介绍的Two-Pass算法打上标签,并获得一个映射关系;然后用remap将上面得到的映射关系做一个重映射,确保每一个级别的映射都对应到了最根部(可以联系参考链接1的内容进行理解,虽然这里没有使用Union的数据结构,但是本质上还是一个树形的结构,需要做一个重映射);然后用second_pass执行Two-Pass算法的第二次遍历,得到一组打上了新的独立标签的网格节点;最后需要用flatten将标签进行压平,因为前面映射的关系,有可能导致标签不连续,所以我们这里又做了一次映射,确保标签是连续变化的,实际应用中可以不使用这一步。

标签的重映射

关于节点的遍历,大家可以直接看算法代码,这里需要额外讲解的是标签的重映射模块的代码:

def remap(idx_dict) -> dict:index_dict = deepcopy(idx_dict)for id in idx_dict:idv = idx_dict[id]while idv in idx_dict:if idv == idx_dict[idv]:breakidv = idx_dict[idv]index_dict[id] = idvreturn index_dict

这里的算法是先对得到的标签进行遍历,在字典中获取当前标索引所对应的值,作为新的索引,直到键跟值一致为止,相当于在一个树形的数据结构中重复寻找父节点直到找到根节点。

其他的测试用例

这里我们可以再额外测试一些案例,比如增加几个0元素使得网格节点更加稀疏:

graph = np.random.choice([0,0,0,1],size=(20,20))

得到的结果图片如下所示:

还可以再稀疏一些:

graph = np.random.choice([0,0,0,0,0,1],size=(20,20))

得到的结果如下图所示:

越是稀疏的图,得到的分组结果就越分散。

总结概要

在本文中我们主要介绍了利用Two-Pass的算法来检测区域连通性,并给出了Python3的代码实现,当然在实现的过程中因为没有使用到Union这样的数据结构,仅仅用了字典来存储标签之间的关系,因此效率和代码可读性都会低一些,单纯作为用例的演示和小规模区域划分的计算是足够用了。在该代码实现方案中,还有一点与原始算法不一致的是,本实现方案中打新的标签是读取上、上左和左三个方向的格点,但是存储标签的映射关系时,是读取了上、上左、上右和左这四个方向的格点。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/two-pass.html

作者ID:DechinPhy

更多原著文章请参考:https://www.cnblogs.com/dechinphy/

打赏专用链接:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

腾讯云专栏同步:https://cloud.tencent.com/developer/column/91958

参考链接

  1. https://blog.csdn.net/lichengyu/article/details/13986521
  2. https://www.cnblogs.com/riddick/p/8280883.html

Python3实现Two-Pass算法检测区域连通性相关推荐

  1. Kosaraju 算法检测有向图的强连通性

    给定一个有向图 G = (V, E) ,对于任意一对顶点 u 和 v,有 u --> v 和 v --> u,亦即,顶点 u 和 v 是互相可达的,则说明该图 G 是强连通的(Strong ...

  2. 用yolo-v3检测算法检测图片中的apple和damaged_apple

    Workflow 1.前言 2.前期准备 2.1 数据集加标签 2.2 GPU和python环境 2.3 下一个预训练模型 3.跑代码 1.前言 好久没发博客了,最近简单复现了yolo-v3算法检测好 ...

  3. 使用无监督算法检测DGA域名(一)

    1.K-means的基本使用 import numpy as np import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot ...

  4. 【教程】yolov5_deepsort目标跟踪算法检测人流量计数

    这个专栏更新各种AI,以及各种有趣的教程,有兴趣的小伙伴可以订阅一下. 今天介绍的目标跟踪算法对人流量统计计数的使用,这里我们可以在界面中自选区域进行检测人流量.效果如下: 在视频的开始第一帧逆时针选 ...

  5. 0x66.图论 - Tarjan算法与无向图连通性

    目录 一.无向图的割点与桥 割点 桥/割边 时间戳 搜索树 追溯值 二.割边判定法则 三.割点判定法则 1.luogu P3388 [模板]割点(割顶) 2.luogu P3469 [POI2008] ...

  6. Python+opencv 机器视觉 - 基于霍夫圈变换算法检测图像中的圆形实例演示

    Python+opencv 机器视觉 - 基于霍夫圈变换算法检测图像中的圆形实例演示 第一章:霍夫变换检测圆 ① 实例演示1 ② 实例演示2 ③ 霍夫变换函数解析 第二章:Python + openc ...

  7. OpenCV使用基本算法检测颜色检查器

    OpenCV使用基本算法检测颜色检查器 使用基本算法检测颜色检查器 简介 建造Building 示例的源代码 解释 设置标题和名称空间 创建检测器对象 运行检测器 获取ColorCheckers列表 ...

  8. linux加密框架 crypto 算法管理 - 算法检测

    参考链接 Linux加密框架的算法管理(四)_家有一希的博客-CSDN博客 函数介绍 如前所述,无论是静态算法还是动态算法,算法注册的最后一步都是进行算法正确性检验,一般流程是先调用__crypto_ ...

  9. 评价目标检测区域的准确性——IoU

    交并比 https://blog.csdn.net/red_stone1/article/details/79028058 交并比:Intersection Over Union(IoU) 即交集与并 ...

最新文章

  1. php 该throw还是该404,前端魔法堂——异常不仅仅是try/catch
  2. 解惑(三)----- 深入理解Python中的self参数和__init__(self)方法--通过类比Java语言
  3. 【Java】Java枚举类型示例
  4. java jstat gc_分析JVM GC及内存情况的方法
  5. 最有创意的万圣节借势海报都在这里
  6. github超简教程
  7. 把一个web项目改名后复制,再把改名后的web项目发布到tomcat,访问出现404错误的解决办法
  8. Kali最新2020.1版本之U盘启动加密与持久化存储制作流程
  9. 新增汉字英文数字标点转区位码api
  10. linux连win7打印机,如何在CentOS 6.5下安装Win7下的共享打印机服务
  11. 基于Python的BOSS直聘Python岗位数据分析
  12. Android开发 个人开发者几大后端云比较
  13. 单核cpu多线程有必要吗?
  14. 微信支付出现故障,程序员的高薪理所当然
  15. 16数码管静态显示(74hc_595)
  16. Bootstrap typeahead自动补全插件的坑
  17. 服务器维护10月11魔兽,魔兽世界怀旧服10月11日免费转服第五期开启公告 免费转服详情...
  18. Fractions to Decimals
  19. js电影票预订座位网页js特效
  20. 文件上传漏洞之——远程文件包含漏洞(RFI)

热门文章

  1. 【SpringBoot】SpringBoot更改项目编码集
  2. Makefile wildcard函数说明
  3. [django]从前端返回字符串,后端转换为字典,执行数据添加操作
  4. 最近插入法和最近邻点法
  5. 性能测试基础培训 - PPT下载
  6. GitHub爆火 金九银十巨作:拼多多/蚂蚁/百度面经分享
  7. virtualbox与windows共享文件夹
  8. managed, unmanaged
  9. python断点续传下载_Python 3 爬虫|第12章:并发下载大文件 支持断点续传
  10. c语言注释不能嵌套什么意思,C语言嵌套注释问题