区域填充算法
一、区域填充概念
区域:指已经表示成点阵形式的填充图形,是象素的集合。
区域填充:将区域内的一点(常称种子点)赋予给定颜色,然后将这种颜色扩展到整个区域内的过程。
区域填充算法要求区域是连通的,因为只有在连通区域中,才可能将种子点的颜色扩展到区域内的其它点。
1、区域有两种表示形式
- 内点表示:
- 枚举出区域内部的所有象素
- 内部所有象素着同一个颜色
- 边界像素着与内部象素不同的颜色。
- 边界表示:
- 枚举出区域外部的所有象素
- 边界上的所有象素着同一个颜色
- 内部像素着与边界象素不同的颜色。
2、区域连通
- 四向连通区域:从区域上一点出发可通过上、下、左、右四个方向移动的组合,在不越出区域的前提下,到达区域内的任意象素。
- 八向连通区域:从区域上一点出发可通过上、下、左、右、左上、右上、左下、右下八个方向移动的组合,在不越出区域的前提下,到达区域内的任意象素。
- 四连通与八连通区域的区别
- 连通性:四连通可以看作八连通的自己,但是对边界有要求
二、简单种子填充算法
1、基本思想
给定区域G一种子点(x, y),首先判断该点是否是区域内的一点,如果是,则将该点填充为新的颜色,然后将该点周围的四个点(四连通)或八个点(八连通)作为新的种子点进行同样的处理,通过这种扩散完成对整个区域的填充。
这里给出一个四连通的种子填充算法(区域填充递归算法),使用【栈结构】来实现
原理算法原理如下:种子像素入栈,当【栈非空】时重复如下三步:
2、算法代码
这里给出八连通的种子填充算法的代码:
void flood_fill_8(int[] pixels, int x, int y, int old_color, int new_color) {
if (x < w && x > 0 && y < h && y > 0) {
// 如果是旧的颜色而且还没有给他填充过
if (pixels[y * w + x] == old_color) {
// 填充为新的颜色
pixels[y * w + x]== new_color);
// 递归
flood_fill_8(pixels, x, y + 1, old_color, new_color);
flood_fill_8(pixels, x, y - 1, old_color, new_color);
flood_fill_8(pixels, x - 1, y, old_color, new_color);
flood_fill_8(pixels, x + 1, y, old_color, new_color);
flood_fill_8(pixels, x + 1, y + 1, old_color, new_color);
flood_fill_8(pixels, x + 1, y - 1, old_color, new_color);
flood_fill_8(pixels, x - 1, y + 1, old_color, new_color);
flood_fill_8(pixels, x - 1, y - 1, old_color, new_color);
}
}
}
3、OpenCV实现
import cv2
def seed_fill(img):
ret, img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)
label = 100
stack_list = []
h, w = img.shape
for i in range(1, h-1, 1):
for j in range(1, w-1, 1):
if (img[i][j] == 255):
img[i][j] = label
stack_list.append((i, j))
while len(stack_list) != 0:
cur_i = stack_list[-1][0]
cur_j = stack_list[-1][1]
img[cur_i][cur_j] = label
stack_list.remove(stack_list[-1])
# 四邻域,可改为八邻域
if (img[cur_i-1][cur_j] == 255):
stack_list.append((cur_i-1, cur_j))
if (img[cur_i][cur_j-1] == 255):
stack_list.append((cur_i, cur_j-1))
if (img[cur_i+1][cur_j] == 255):
stack_list.append((cur_i+1, cur_j))
if (img[cur_i][cur_j+1] == 255):
stack_list.append((cur_i, cur_j+1))
cv2.imwrite('./result.jpg', img)
cv2.imshow('img', img)
cv2.waitKey()
if __name__ == '__main__':
img = cv2.imread('./test.jpeg', 0)
seed_fill(img)
4、简单种子填充算法的优点和缺点
优点:
- 该算法也可以填充有孔区域
缺点:
- 有些像素会多次入栈,降低算法效率,栈结构占空间
- 递归执行,算法简单,但效率不高,区域内每一像素都要进/出栈,费时费内存
- 改进算法,减少递归次数,提高效率
三、扫描线种子填充算法
- 目标:减少递归层次
- 适用于边界表示的4连通区间
1、基本思想
在任意不间断区间中只取一个种子像素(不间断区间指在一条扫描线上一组相邻元素),填充当前扫描线上的该段区间;然后确定与这一区段相邻的上下两条扫描线上位于区域内的区段,并依次把它们保存起来,反复进行这个过程,直到所保存的各个区段都填充完毕。
2、算法步骤
- 初始化:将算法设置的堆栈置为空。将给定的种子点压入堆栈
- 出栈:如果堆栈为空,算法结束;否则取栈顶元素作为种子点
- 区段填充:从种子点开始,沿纵坐标为y的当前扫描线向左右两个方向逐个像素用新的颜色值进行填充,直到边界为止即象素颜色等于边界色。设区间两边界的横坐标分别为xleft 和xright。
- 在与当前扫描线相邻的上下两条扫描线上,以区间[xleft, xright]为搜索范围,求出需要填充的各小区间,把各小区间中最右边的点并作为种子点压入堆栈,转到步骤2。
3、算法的关键原则
-
搜索原则:
从前一个填充的区间(边界之间的范围xleft, xright)作为后一条扫描线种子点寻找的范围。
-
填充原则:
从种子点往左,右填,填到边界
4、实例
上述算法的描述过于抽象,直接看演示
5、算法
Stack stack = new Stack(); //堆栈 pixel_stack初始化
Stack.push(point); //(x,y)是给定的种子像素
while (!stack.empty()) {
p = (Point)(stack.pop()); //出栈,从堆栈中取一像素作种子像素
x = p.x;
y = p.y;
savex = x; //保存种子点的横坐标x的值
while (pixels[y * w + x] != boundary_color) {
pixels[y * w + x] = new_color;
x++;
} //从种子像素开始向右填充到边界
xright = x–1; //保存线段的右端点
x = savex–1; //设定种子点往左填充的起点
while (pixels[y * w + x] != boundary_color) {
pixels[y * w + x] = new_color;
x = x–1;
}
//从种子像素开始向左填充到边界,以上两步完成区间填充。
xleft = x + 1; //保存线段的左端点,加1是因为前面 循环时多减一次
x = xleft; //起点是上次的左端点
y = y + 1; //开始处理上一条扫描线
while (x <= xright) { //在上一条扫描线上检查是否需要填充
span_need_fill = false; //先设定为不需要填充
while (pixels[y * w + x] == old_color && x <= xright) {
//待填充的线段
span_need_fill = true; //发现有旧象素,需要填充
x = x + 1;
} //待填充的线段处理完,即遇到边界色,!=old_color跳出
if (span_need_fill) { //如果区间需要填充,则将其右端点作为种子点压进堆栈
p = new Point(x - 1, y);
stack.push(p); //进栈
span_need_fill = false;
}
//继续向右检查以防有遗漏
while (pixels[y * w + x] != old_color && x <= xright)
x = x + 1;
} //在上一条扫描线上检查完
x=xleft;
y = y–2; //形成下一条扫描线的y值
//在下一条扫描线上从左向右检查位于区间[xleft,xright]上的像素,其方法与在上一条扫描线上检查的情况完全一样,见书。
} //出栈完
6、OpenCV实现
# coding:utf8
import cv2
import numpy as np
frame = cv2.cvtColor(cv2.imread(
'fr_seg_open22_result.pgm'), cv2.COLOR_BGR2GRAY)
ret, frame = cv2.threshold(frame, 127, 255, cv2.THRESH_BINARY)
contour_index = 1
def scan_line_seed(i, j):
stack = []
return_value = 0
if frame[i, j] == 255:
stack.append((i, j))
while len(stack) != 0:
seed = stack.pop()
x, y = seed
while frame[x, y] == 255:
frame[x, y] = contour_index * 10
x += 1
x_right = x-1
x, y = seed
x -= 1
while frame[x, y] == 255:
frame[x, y] = contour_index * 10
x -= 1
x_left = x + 1
# 如果左右端点为空则加入种子点
if frame[x_left, y - 1] == 255:
stack.append((x_left, y-1))
if frame[x_left, y + 1] == 255:
stack.append((x_left, y+1))
if frame[x_right, y - 1] == 255:
stack.append((x_right, y-1))
if frame[x_right, y + 1] == 255:
stack.append((x_right, y+1))
for x in range(x_left, x_right+1):
# 上左
if frame[x, y-1] == 255 and frame[x-1, y-1] != 255:
stack.append((x, y-1))
# 下左
if frame[x, y+1] == 255 and frame[x-1, y+1] != 255:
stack.append((x, y+1))
# 上右
if frame[x, y-1] != 255 and frame[x-1, y-1] == 255:
stack.append((x-1, y-1))
# 下右
if frame[x, y+1] != 255 and frame[x-1, y+1] == 255:
stack.append((x-1, y+1))
return_value = 1
return return_value
for x in range(frame.shape[0]):
for y in range(frame.shape[1]):
if scan_line_seed(x, y) == 1:
contour_index = contour_index + 1
print contour_index
cv2.namedWindow('frame', cv2.WINDOW_NORMAL)
cv2.imshow('frame', frame)
cv2.imwrite('numbered_intel.pgm', frame)
cv2.waitKey(0)
本文作者:我係死肥宅
本文链接:https://www.cnblogs.com/iamfatotaku/p/12496926.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步