console.log(🐂🍺);|

我係死肥宅

园龄:4年11个月粉丝:19关注:3

区域填充算法

一、区域填充概念

区域:指已经表示成点阵形式的填充图形,是象素的集合

区域填充:将区域内的一点(常称种子点)赋予给定颜色,然后将这种颜色扩展到整个区域内的过程。

区域填充算法要求区域是连通的,因为只有在连通区域中,才可能将种子点的颜色扩展到区域内的其它点。

1、区域有两种表示形式

img

  1. 内点表示
    • 枚举出区域内部的所有象素
    • 内部所有象素着同一个颜色
    • 边界像素着与内部象素不同的颜色。
  2. 边界表示
    • 枚举出区域外部的所有象素
    • 边界上的所有象素着同一个颜色
    • 内部像素着与边界象素不同的颜色。

2、区域连通

img

  1. 四向连通区域:从区域上一点出发可通过上、下、左、右四个方向移动的组合,在不越出区域的前提下,到达区域内的任意象素
  2. 八向连通区域:从区域上一点出发可通过上、下、左、右、左上、右上、左下、右下八个方向移动的组合,在不越出区域的前提下,到达区域内的任意象素
  3. 四连通与八连通区域的区别
    • 连通性:四连通可以看作八连通的自己,但是对边界有要求

二、简单种子填充算法

1、基本思想

给定区域G一种子点(x, y),首先判断该点是否是区域内的一点,如果是,则将该点填充为新的颜色,然后将该点周围的四个点(四连通)或八个点(八连通)作为新的种子点进行同样的处理,通过这种扩散完成对整个区域的填充。

这里给出一个四连通的种子填充算法(区域填充递归算法),使用【栈结构】来实现
原理算法原理如下:种子像素入栈,当【栈非空】时重复如下三步:

img

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、简单种子填充算法的优点和缺点

优点:

  1. 该算法也可以填充有孔区域

缺点:

  1. 有些像素会多次入栈,降低算法效率,栈结构占空间
  2. 递归执行,算法简单,但效率不高,区域内每一像素都要进/出栈,费时费内存
  3. 改进算法,减少递归次数,提高效率

三、扫描线种子填充算法

  • 目标:减少递归层次
  • 适用于边界表示的4连通区间

1、基本思想

任意不间断区间中只取一个种子像素(不间断区间指在一条扫描线上一组相邻元素),填充当前扫描线上的该段区间;然后确定与这一区段相邻的上下两条扫描线上位于区域内的区段,并依次把它们保存起来,反复进行这个过程,直到所保存的各个区段都填充完毕。

2、算法步骤

  1. 初始化:将算法设置的堆栈置为空。将给定的种子点(x,y)压入堆栈
  2. 出栈:如果堆栈为空,算法结束;否则取栈顶元素(x,y)作为种子点
  3. 区段填充:从种子点(x,y)开始,沿纵坐标为y的当前扫描线向左右两个方向逐个像素用新的颜色值进行填充,直到边界为止即象素颜色等于边界色。设区间两边界的横坐标分别为xleftxright
  4. 在与当前扫描线相邻的上下两条扫描线上,以区间[xleft, xright]为搜索范围,求出需要填充的各小区间,把各小区间中最右边的点并作为种子点压入堆栈,转到步骤2。

3、算法的关键原则

img

  1. 搜索原则:

    从前一个填充的区间(边界之间的范围xleft, xright)作为后一条扫描线种子点寻找的范围。

  2. 填充原则:

    从种子点往左,右填,填到边界

4、实例

上述算法的描述过于抽象,直接看演示

img

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 中国大陆许可协议进行许可。

posted @   我係死肥宅  阅读(2796)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.