任务描述
本关任务:学习并完成基于区域生长的图像分割算法。
相关知识
为了完成本关任务,你需要掌握:1.区域生长算法,2.如何实现区域生长算法。
区域生长算法
区域生长算法是一种基于像素相似性的图像分割算法,旨在将图像中相似的像素点组合成连通的区域。该算法从一个或多个种子点开始,逐步生长扩展,将与种子点相似的像素点归为同一个区域。
以下是区域生长算法的基本步骤:
选择种子点:从图像中选择一个或多个像素点作为种子点。通常情况下,种子点是根据特定的准则或用户交互选择的。
定义相似性准则:确定像素点之间的相似性度量标准。这通常是基于像素的灰度值、颜色值、纹理特征等。
定义生长准则:确定像素生长的条件。这个条件通常是基于像素之间的相似性度量和阈值。例如,当两个像素的相似性大于阈值时,它们被认为是同一个区域的一部分。
初始化标记图像:创建一个与原始图像大小相同的标记图像,用于记录每个像素点所属的区域。
开始生长扩展:从种子点开始,逐步扩展区域生长。一般情况下,使用队列或栈数据结构来管理待生长的像素点。
扩展过程:对于当前的生长像素点,根据生长准则和相似性度量,将其与相邻像素点进行比较。如果相邻像素点与当前像素点相似且尚未标记,则将其标记为同一区域,并加入待生长队列或栈中。
继续生长:重复步骤6,直到没有更多的待生长像素点。
输出结果:最终得到标记图像,每个像素点被分配一个唯一的标签,表示其所属的区域。
区域生长算法的优点是简单且易于实现,对于具有均匀纹理、明显边界或明显对比度的图像分割效果较好。然而,该算法对于光照变化、噪声或复杂背景的图像可能会产生较差的分割结果。因此,在实际应用中,可能需要结合其他算法或改进区域生长算法以适应不同的图像场景。
如何实现区域生长算法
区域生长的基本思想是将具有相似性质的像素集合起来构成区域。具体先对每个需要分割的区域找一个种子像素作为生长的起点,然后将种子像素周围邻域中与种子像素具有相同或相似性质的像素(根据某种事先确定的生长或相似准则来判定)合并到种子像素所在的区域中。将这些新像素当做新的种子像素继续进行上面的过程,直到再没有满足条件的像素可被包括进来,这样,一个区域就长成了。
区域生长的算法实现:
根据图像的不同应用选择一个或一组种子,它或者是最亮或最暗的点,或者是位于点簇中心的点,当然也可以手动选择种子点。
选择一个描述符(条件),常见的有基于区域灰度差、基于区域灰度分布统计性质。
从该种子开始向外扩张,首先把种子像素加入结果集合,然后不断将与集合中各个像素连通、且满足描述符的像素加入集合。
上一过程进行到不再有满足条件的新结点加入集合为止。
实现案例
# 计算种子点和其领域的像素值之差
def getGrayDiff(gray, current_seed, tmp_seed):
return abs(int(gray[current_seed[0], current_seed[1]]) - int(gray[tmp_seed[0], tmp_seed[1]]))
# 区域生长算法
def regional_growth(gray, seeds):
# 八领域
connects = [(-1, -1), (0, -1), (1, -1), (1, 0), \
(1, 1), (0, 1), (-1, 1), (-1, 0)]
seedMark = np.zeros((gray.shape))
height, width = gray.shape
threshold = 6
seedque = deque()
label = 255
seedque.extend(seeds)
while seedque:
# 队列具有先进先出的性质。所以要左删
current_seed = seedque.popleft()
seedMark[current_seed[0], current_seed[1]] = label
for i in range(8):
tmpX = current_seed[0] + connects[i][0]
tmpY = current_seed[1] + connects[i][1]
# 处理边界情况
if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= width:
continue
grayDiff = getGrayDiff(gray, current_seed, (tmpX, tmpY))
if grayDiff < threshold and seedMark[tmpX, tmpY] != label:
seedque.append((tmpX, tmpY))
seedMark[tmpX, tmpY] = label
return seedMark
getGrayDiff(gray, current_seed, tmp_seed): 这个函数用于计算当前种子点和其领域像素点之间的灰度值差异。它接收三个参数:gray 表示灰度图像,current_seed 表示当前种子点的坐标,tmp_seed 表示待比较的邻域像素点的坐标。函数内部通过索引访问灰度图像的对应像素值,并计算其差的绝对值。最后返回两个像素值之间的差异。
regional_growth(gray, seeds): 这个函数实现了区域生长算法。它接受以下参数:
gray: 灰度图像,表示待处理的图像。
seeds: 种子点的坐标列表。
函数首先定义了八领域的连接关系,即每个像素点周围八个相邻像素的相对位置。然后,它创建一个与原始图像大小相同的标记图像 seedMark,用于记录每个像素点所属的区域。
接下来,函数初始化了一些变量,包括图像的高度和宽度、生长阈值、种子点队列等。然后,它开始对种子点进行生长扩展。
在生长过程中,函数使用队列 seedque 来管理待处理的像素点。它从队列中取出当前的种子点,将其标记为指定的标签,并遍历种子点的八个相邻像素。对于每个相邻像素,函数检查其是否满足生长条件:灰度差异小于阈值,并且未被标记为已处理。如果满足条件,邻域像素将被添加到队列中,并被标记为同一区域。
该过程重复进行,直到队列中没有更多待处理的像素点。最终,函数返回标记图像 seedMark,其中每个像素点被赋予一个唯一的标签,表示其所属的区域。
这两个函数共同实现了区域生长算法的关键步骤:计算像素差异并扩展区域生长。通过调用这两个函数,可以对图像进行区域生长分割,并得到分割后的结果。
编程要求
根据提示,在右侧编辑器补充代码,实现图像分割——区域生长法。
测试说明
平台会对你编写的代码进行测试:
测试输入:
预先给定的种子列表
seed = [(129, 593), (117, 641), (111, 667), (118, 680), (132, 713), (133, 680), (127, 651), (163, 746), (164, 773),
(169, 807), (171, 829), (202, 856), (227, 869), (250, 888), (290, 879), (336, 878), (384, 882), (433, 890),
(478, 894), (491, 885), (517, 838), (563, 833), (596, 823), (636, 805), (658, 800), (735, 769), (769, 787),
(809, 799), (851, 801), (843, 788), (901, 798), (927, 820), (975, 844), (1008, 842), (1057, 834), (1106, 833),
(1148, 808), (1145, 798), (1136, 799), (1110, 803), (1074, 792), (1035, 784), (975, 759), (895, 736),
(860, 719), (793, 694), (726, 682), (688, 673), (700, 636), (772, 628), (832, 627), (895, 624), (925, 625),
(972, 640), (1024, 632), (1070, 620), (1096, 628), (1110, 618), (1118, 563), (1119, 499), (1113, 514),
(1100, 544), (1088, 584), (1044, 580), (992, 563), (923, 544), (896, 535), (858, 525), (831, 526), (797, 560),
(738, 591), (682, 590), (647, 600), (650, 582), (732, 541), (760, 526), (753, 551), (620, 637), (515, 629),
(434, 572), (473, 543), (540, 515), (595, 477), (666, 454), (694, 455), (636, 441), (585, 450), (533, 483),
(483, 507), (431, 513), (399, 513), (369, 517), (344, 543), (342, 618), (286, 615), (255, 562), (236, 560),
(204, 556), (177, 565), (161, 586), (138, 635), (195, 618), (222, 648), (226, 586), (207, 627), (271, 633),
(222, 730), (342, 720), (358, 644), (413, 588), (436, 678), (454, 642), (524, 659), (437, 678), (571, 621),
(630, 599), (657, 708), (555, 760), (428, 764), (494, 721), (603, 731), (651, 737), (399, 757), (400, 756)]
预期输出:
开始你的任务吧,祝你成功!
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from collections import deque
##########begin##########
# 计算种子点和其领域的像素值之差
def getGrayDiff(gray, current_seed, tmp_seed):
return abs(int(gray[current_seed[0], current_seed[1]]) - int(gray[tmp_seed[0], tmp_seed[1]]))
# 区域生长算法
def regional_growth(gray, seeds):
# 八领域
connects = [(-1, -1), (0, -1), (1, -1), (1, 0), \
(1, 1), (0, 1), (-1, 1), (-1, 0)]
seedMark = np.zeros((gray.shape))
height, width = gray.shape
threshold = 6
seedque = deque()
label = 255
seedque.extend(seeds)
while seedque:
# 队列具有先进先出的性质。所以要左删
current_seed = seedque.popleft()
seedMark[current_seed[0], current_seed[1]] = label
for i in range(8):
tmpX = current_seed[0] + connects[i][0]
tmpY = current_seed[1] + connects[i][1]
# 处理边界情况
if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= width:
continue
grayDiff = getGrayDiff(gray, current_seed, (tmpX, tmpY))
if grayDiff < threshold and seedMark[tmpX, tmpY] != label:
seedque.append((tmpX, tmpY))
seedMark[tmpX, tmpY] = label
return seedMark
##########end##########
def Region_Grow(img,seed):
for i in seed:
# 在图像上画出实心点位
cv.circle(img, center=(i[-1],i[0]), radius=5,color=(0, 0, 255), thickness=-1)
# 进行区域生长分割
astronaut = cv.imread('imgs/astronaut.png', 1)
seedMark = np.uint8(regional_growth(cv.cvtColor(astronaut, cv.COLOR_BGR2GRAY), seed))
# 绘制并保存
plt.figure(figsize=(16, 6))
plt.subplot(131), plt.imshow(cv.cvtColor(astronaut, cv.COLOR_BGR2RGB))
plt.axis('off'), plt.title(f'$input\_image$')
plt.subplot(132), plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.axis('off'), plt.title(f'$seeds\_image$')
plt.subplot(133), plt.imshow(seedMark, cmap='gray', vmin=0, vmax=255)
plt.axis('off'), plt.title(f'$segmented\_image$')
plt.savefig('img_user/astronaut.png')