一、简介
由于课题需要在python上实现连通域算法的实现。根据网上的两步扫描法和种子法发现算法对非闭合曲线类型的图形分类效果有问题(比如说一个“U”形)。其原因主要是图像像素分布各向异性的问题,导致按从左到右,再按从上到下的扫描方式与从右到左,从下往上的扫描方式所获得的结果是完全不同的。当然我们可以增加扫描的次数,以将每个扫描窗的最小值赋给整个窗口,但是所需要的扫描次数是难以确定的,因此我认为每次以穷举法为原则,找出属于一个连通域所有像素,再去寻找下一个连通域的像素的方式是最为准确的。
二、代码
import numpy as np
import scipy.io as sio
import matplotlib.pyplot as plt
import datetime
start_time = datetime.datetime.now()
image = np.array([[0, 0, 1, 0, 0, 1],
[1, 0, 1, 0, 1, 0],
[1, 0, 1, 0, 1, 0],
[1, 0, 1, 0, 1, 0],
[1, 0, 1, 0, 1, 1],
[1, 1, 1, 0, 0, 1],])
# 图像
image_row = image.shape[0] + 2
image_col = image.shape[1] + 2
image_con = np.zeros([image_row, image_col], dtype=np.int16)
image_con[1:-1, 1:-1] = image
image_seed_value = np.reshape(image_con, [-1])
image_seed = np.argwhere(image_seed_value == 1).tolist()
cur_id = 2
total_area = []
def connect(window, center, image_col, mode):
# 这个函数是根据原始二值图返回其邻域连通节点的坐标
index_list = []
if mode == '8':
kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])
# 连通域卷积核,主要分为“8”连通和“4”连通
elif mode == '4':
kernel = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]])
else:
print('沙雕,填错了,重新来')
return
result = window * kernel
connect_set = (np.argwhere(result == 1).astype(np.int32) + center + np.array([-1, -1], dtype=np.int32)).tolist()
for index in connect_set:
index_list.append(index[0] * image_col + index[1])
return index_list
while len(image_seed) > 0:
# 循环终止条件判断,既当所有的1都完全分类完成后,循环终止
cur_seed = image_seed[0][0]
cur_center = [cur_seed // image_col, cur_seed % image_col]
image_seed.remove([cur_seed])
cur_id_set = []
cur_id_set.append(cur_seed)
image_con[cur_center[0], cur_center[1]] = cur_id
cur_window = image_con[cur_center[0] - 1:cur_center[0] + 2, cur_center[1] - 1:cur_center[1] + 2]
cur_son = connect(cur_window, cur_center, image_col, mode)
# 修正卷积后的坐标
while len(cur_son) > 0:
cur_seed = cur_son[0]
if cur_seed in cur_id_set:
cur_son.pop(0)
continue
cur_center = [cur_seed // image_col, cur_seed % image_col]
# if [cur_seed] in image_seed:
image_seed.remove([cur_seed])
cur_id_set.append(cur_seed)
cur_son.pop(0)
image_con[cur_center[0], cur_center[1]] = cur_id
cur_window = image_con[cur_center[0] - 1:cur_center[0] + 2, cur_center[1] - 1:cur_center[1] + 2]
cur_son.extend(connect(cur_window, cur_center, image_col, mode))
total_area.append(cur_id_set)
cur_id += 1
image_lab = image_con[1:-1, 1:-1]
end_time0 = datetime.datetime.now()
time_use0 = 'label time use:%dms' % ((end_time0 - start_time).seconds * 1000 + (end_time0 - start_time).microseconds / 1000)
print(time_use0)
简单来说,就是先找出所有为1的元素总列表,然后根据连通特性将总列表的元素划分到不同的子列表中去。
三、结果
效果如图所示,针对连通域特别多时,也能够适用,如下图所示
但是算法的总体速度不算特别快,大概是matlab中的bwlabeln函数的计算时间的8倍,而在我的电脑上,python计算循环的花的时间大概是matlab的4倍,其他时间上的花费我也不太清楚从什么方向上改进了,希望各位大佬帮我多多批评指正。