区域分割概述:从种子点或区域出发,根据相似性准则(如灰度、纹理)生长或分裂区域。
区域分割的经典代表算法有:区域生长与分水岭算法
区域生长
从种子点出发,合并相似的相邻像素。
算法流程:
- 选择种子点:手动指定或自动选择
- 定义相似性准则:如灰度差小于阈值 T
- 区域扩张:
1.检查种子点邻域像素。
2.若满足相似性准则,则将其并入区域,并作为新种子。
3.重复直到没有新像素可加入。
相似性准则是关键,直接影响分割质量。
需要手动或自动选择种子点,这是一个主要局限。
python
def RegionGrowing(img_path, seed_point=(100, 100), threshold=10):
"""
基于区域生长的图像分割算法
参数:
img_path: 输入图像路径
seed_point: 种子点坐标,作为生长起点 (行, 列)
threshold: 生长阈值,控制像素相似度的容忍度(值越小,条件越严格)
"""
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
visited = set() # 记录已访问过的像素,防止重复处理
region = [] # 待处理像素队列 (栈结构实现)
region.append(seed_point) # 将种子点加入待处理队列
segment_image = np.zeros_like(image)
# count = 0
# 区域生长主循环
while len(region) > 0:
current_point = region.pop() # 弹出最后一个元素 (栈的LIFO特性)
visited.add(current_point) # 标记为已访问
# 检查当前点的8邻域像素 (包括对角相邻)
for dx in range(-1, 2):
for dy in range(-1, 2):
x = current_point[0] + dx
y = current_point[1] + dy
# 检查邻域像素是否有效
if ((x, y) not in visited and
0 <= x < gray.shape[0] and # 确保行坐标在图像范围内
0 <= y < gray.shape[1]): # 确保列坐标在图像范围内
# 相似性判断:比较邻域像素与当前点的灰度差
if abs(int(gray[x, y]) - int(gray[current_point])) < threshold:
region.append((x, y))
visited.add((x, y))
segment_image[x, y] = [0, 255, 0]
# print('count', count)
return segment_image
分水岭算法
算法流程:
- 输入准备:计算图像的梯度幅值图(作为地形)。
- 标记提取(最关键步骤):
1.确定的前景标记:通过距离变换、阈值等得到物体内部点。
2.确定的背景标记:通过膨胀等操作得到远离物体的区域。
3.未知区域:前景和背景之间的区域。 - 淹没过程:
1.从标记处(前景)开始注水。
2.水位逐渐上升,从不同标记出发的水域不断扩张。
3.当来自不同标记的水域即将汇合时,筑坝阻止,形成的"水坝"就是分水岭线(分割边界)。
过分割是最大问题:每个局部最小值都会形成一个区域。
解决方案:基于标记的分水岭,只从指定的标记处开始淹没,这已成为标准做法。
python
def watershed_algorithm(img_path, sp=10, sr=100, ksize=3, iteration_open=2, iteration_bg=3):
image = cv2.imread(img_path)
# 去噪与二值化
blur = cv2.pyrMeanShiftFiltering(image, sp=sp, sr=sr) # 在保持边缘清晰的前提下,对图像进行色彩平滑和细节抑制。基于均值漂移算法,采用金字塔加速
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # ret为OTSU(大律动)算法计算存储的最优阈值
print('best threshold: ', ret)
cv2.imshow('blur image', blur)
cv2.imshow('gray image', gray)
cv2.imshow('binary image', binary)
# 形态学流程
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (ksize,ksize)) # 定义结构元,创建一个3*3的矩形"刷子",用于后面的腐蚀、膨胀
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel=kernel, iterations=iteration_open) # 开运算,先腐蚀后膨胀,消除binary图像中的细小白色噪点
surface_bg = cv2.dilate(opening, kernel, iterations=iteration_bg) # 膨胀获取背景, 连续膨胀3次,将真实物体的区域完全包含进来
cv2.imshow('opening image', opening)
cv2.imshow('surface_bg image', surface_bg)
# 距离变换
dist = cv2.distanceTransform(opening, cv2.DIST_L2, ksize) # 计算每个前景像素到最近背景的距离,
dist_out = cv2.normalize(dist, 0, 1.0, cv2.NORMAL_CLONE)
ret, surface = cv2.threshold(dist_out, dist_out.max() * 0.6, 255, cv2.THRESH_BINARY) # 通过阈值,得到距离背景足够远的区域,0.6调低会扩大前景区域,调高则缩小
surface_fg = np.uint8(surface)
unknown = cv2.subtract(surface_bg, surface_fg) # 找到位置区域
cv2.imshow('surface image', surface)
cv2.imshow('unknown image', unknown)
# 标记标签
ret, markers = cv2.connectedComponents(surface_fg) # 连通区域, ret为连通区域数=前景物体数(独立白色块) + 1,markers一个标签图,其中背景像素值为0,第一个物体区域像素值为1,第二个为2,第三个为3,以此类推。
print(ret)
# 分水岭变换
markers = markers + 1 # 分水岭算法要求背景非0
markers[unknown == 255] = 0 # 背景为1,前景为2/3/4...,未知区域为0
markers = cv2.watershed(image, markers=markers)
image[markers == -1] = [0, 0, 255] # 被标记的区域设为红色
cv2.imshow('result', image)
return image