求连通域算法

前言

近些年来,图像领域的发展不管是学术界还是商业界基本上都被人工智能的方法统治了,我个人也是把工作和学习的重心也转移到深度学习上去了。 忽然回过头来看一些传统的图像处理方法,反而感觉有点生疏了。不过这也是很正常的事情,因为毕竟传统的图像处理算法发展了这么多年也算是一门大学科领域了,因此我希望通过整理那些我还不够熟悉但是又足够基础的图像处理算法,来加深我对图像处理算法的理解,本文算是这一系列的开始吧。

求连通域作用

连通域获取算是图像处理中,一步非常基础又十分有用的操作了,它能够使用在如:OCR识别中字符分割提取(车牌识别、文本识别、字幕识别等)、视觉跟踪中的运动前景目标分割与提取(行人入侵检测、遗留物体检测、基于视觉的车辆检测与跟踪等)、医学图像处理(感兴趣目标区域提取)等等各个方面。但是很奇怪我不知道为什么,我在实际的工作中就完全没有使用过连通域的方法,也许是由于在我的项目中并没有怎么涉及到识别与提取感兴趣区域的任务吧。

由于求连通区域算是一个比较基本简单的算法,所以本文会先使用openCV提供的对应接口函数检查效果,然后自己设计算法实现。

使用OpenCV的findContours函数

在OpenCV中提供了findContours来求图像中所有的连通区域,使用findContours能够得到不同区域包含点的集合和拓扑信息。由于求连通域是基于二值图,所以输入的图像要是一个二值图。

在找到区域后,可以使用drawContours来画区域的外轮廓,使用fillConvexPoly来标记连通区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np
import matplotlib.pyplot as plt

def pltshow(im):
plt.figure(figsize=(15,6))
if im.ndim == 3:
plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
else:
plt.imshow(im, 'gray')
plt.show()

img = cv2.imread('Image/bin.jpg', cv2.IMREAD_GRAYSCALE)
_, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY )
pltshow(img)
img.shape

找到连通曲,并对不同的连通区使用不同的颜色进行标记显示

1
2
3
4
5
6
image, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
col_img = np.repeat(image, 3).reshape(image.shape[0], image.shape[1], 3)
for i in range(len(contours)):
col = i*35
cv2.fillConvexPoly(col_img, contours[i], (col,255-col,255-col))
pltshow(col_img)

运行结果如下:

下面代码是对连通区的外轮廓进行提取、标记

1
2
3
col_img = np.repeat(image, 3).reshape(image.shape[0], image.shape[1], 3)
cv2.drawContours(col_img, contours, -1, (255, 0, 255), 2)
pltshow(col_img)

运行结果如下:

连通域查找简单实现

连通域查找有两种方法:

  1. Two-Pass(两遍扫描法),就扫描图像两次,第一次扫描将为1的像素点设置一个label,label的取值由其相邻像素是否有label标记决定,如果有,使用相邻像素最小的label作为这个像素点的标记,如果没有使用一个新的标记值。第二遍的扫描将所有相邻的标记设置为相同的label(通常是这个区域label的最小值)。

  2. seed filling(种子填充法),选取前景中的一个像素点作为种子,根据连通的条件逐步的向外扩张,直到将区域内的像素点去不照完,同时以相同的方法搜寻其他的区域,直到图像上所有的区域都已经找完。

以下用python代码实现第二种方案,先不考虑连通域相互包含的层次关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def isConnected4Neb(img, r, c):
ret = []
i = r - 1
j = c
if i >= 0 and img[i, j] > 0: ret.append((i, j))
i = r + 1
j = c
if i < img.shape[0] and img[i, j] > 0: ret.append((i, j))
i = r
j = c - 1
if j > 0 and img[i, j] > 0: ret.append((i, j))
i = r
j = c + 1
if i < img.shape[1] and img[i, j] > 0: ret.append((i, j))
return ret


def findOneArea(img, img_pro, r, c):
area = []
use_stack = [(r, c)]
while len(use_stack) != 0:
i, j = use_stack.pop()
ret = isConnected4Neb(img_pro , i, j)
for item in ret:
img_pro[i, j] = 0
area.append(item)
use_stack.append(item)
return area


def findArea(img):
img_pro = img.copy()
areas = []
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if img_pro[i,j] > 0:
area = findOneArea(img, img_pro, i, j)
if len(area) > 0:
areas.append(area)
return areas

def drowArea(im, areas, color):
for area in areas:
for i ,j in area:
im[i, j] = color

areas = findArea(img)
col_img = np.repeat(image, 3).reshape(image.shape[0], image.shape[1], 3)
drowArea(col_img, areas, (255,255, 0))
pltshow(col_img)

以上代码运行结果如下:

参考

  1. OpenCV-二值图像连通域分析
Compartir