漫水填充算法描述

原文:http://m.blog.csdn.net/blog/fangjian1204/10228835#

漫水填充算法描述

1.1、种子填充算法

种子填充算法是从多边形区域内部的一点开始,由此出发找到区域内的所有像素。

种子填充算法采用的边界定义是区域边界上所有像素具有某个特定的颜色值,区域内部所有像素均不取这一特定颜色,而边界外的像素则可具有与边界相同的颜色值。

具体算法步骤:(1)标记种子(x,y)的像素点(2)检测该点的颜色,若他与边界色和填充色均不同,就用填充色填充该点,否则不填充(3)检测相邻位置,继续(2)。这个过程延续到已经检测区域边界范围内的所有像素为止。

当然在搜索的时候有两种检测相邻像素:四向连通和八向连通。四向连通即从区域上一点出发,通过四个方向上、下、左、右来检索。而八向连通加上了左上、左下、右上、右下四个方向。

这种算法的有点是算法简单,易于实现,也可以填充带有内孔的平面区域。但是此算法需要更大的存储空间以实现栈结构,同一个像素多次入栈和出栈,效率低,运算量大。

1.2扫描线填充算法

扫描线种子填充算法不再采用递归的方式处理“4-联通”和“8-联通”的相邻点,而是通过沿水平扫描线填充像素段,一段一段地来处理“4-联通”和“8-联通”的相邻点。这样算法处理过程中就只需要将每个水平像素段的起始点位置压入一个特殊的栈,而不需要象递归算法那样将当前位置周围尚未处理的所有相邻点都压入堆栈,从而可以节省堆栈空间。应该说,扫描线填充算法只是一种避免递归,提高效率的思想,前面提到的注入填充算法和边界填充算法都可以改进成扫描线填充算法,下面介绍的就是结合了边界填充算法的扫描线种子填充算法。

扫描线种子填充算法的基本过程如下:当给定种子点(x,y)时,首先分别向左和向右两个方向填充种子点所在扫描线上的位于给定区域的一个区段,同时记下这个区段的范围[xLeft,xRight],然后确定与这一区段相连通的上、下两条扫描线上位于给定区域内的区段,并依次保存下来。反复这个过程,直到填充结束。

扫描线种子填充算法可由下列四个步骤实现:

(1)初始化一个空的栈用于存放种子点,将种子点(x,y)入栈;

(2)判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x,y)y是当前的扫描线;

从种子点(x,y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeftxRight

分别检查与当前扫描线相邻的y- 1y+ 1两条扫描线在区间[xLeft,xRight]中的像素,从xLeft开始向xRight方向搜索,若存在非边界且未填充的像素点,则找出这些相邻的像素点中最右边的一个,并将其作为种子点压入栈中,然后返回第(2)步;

借助于堆栈,上述算法实现步骤如下:
  1、初始化堆栈。
  2、种子压入堆栈。
  3while(堆栈非空)
   {
     (1)从堆栈弹出种子象素。
     (2)如果种子象素尚未填充,则:
      a.求出种子区段:xleftxright;
      b.填充整个区段。
      c.检查相邻的上扫描线的xleft<= x <= xright区间内,是否存在需要填充的新区段,如果存在的话,则把每个新区段在xleft<= x <=xright范围内的最右边的象素,作为新的种子象素依次压入堆栈。
      d.检查相邻的下扫描线的xleft<= x <= xright区间内,是否存在需要填充的新区段,如果存在的话,则把每个新区段在xleft<= x <= xright范围内的最右边的象素,作为新的种子象素依次压入堆栈。
    }

这个算法中最关键的是第(4)步,就是从当前扫描线的上一条扫描线和下一条扫描线中寻找新的种子点。这里比较难理解的一点就是为什么只是检查新扫描线上区间[xLeft,xRight]中的像素?如果新扫描线的实际范围比这个区间大(而且不连续)怎么处理?我查了很多计算机图形学的书籍和论文,好像都没有对此做过特殊说明,这使得很多人在学习这门课程时对此有挥之不去的疑惑。本着“毁人”不倦的思想,本文就罗嗦解释一下,希望能解除大家的疑惑。

如果新扫描线上实际点的区间比当前扫描线的[xLeft,xRight]区间大,而且是连续的情况下,算法的第(3)步就处理了这种情况。如图(4)所示:



图(4)新扫描线区间增大且连续的情况

假设当前处理的扫描线是黄色点所在的第7行,则经过第3步处理后可以得到一个区间[6,10]。然后第4步操作,从相邻的第6行和第8行两条扫描线的第6列开始向右搜索,确定红色的两个点分别是第6行和第8行的种子点,于是按照顺序将(6,10)和(8,10)两个种子点入栈。接下来的循环会处理(8,10)这个种子点,根据算法第3步说明,会从(8,10)开始向左和向右填充,由于中间没有边界点,因此填充会直到遇到边界为止,所以尽管第8行实际区域比第7行的区间[6,10]大,但是仍然得到了正确的填充。

如果新扫描线上实际点的区间比当前扫描线的[xLeft,xRight]区间大,而且中间有边界点的情况,算法又是怎么处理呢?算法描述中虽然没有明确对这种情况的处理方法,但是第4步确定上、下相邻扫描线的种子点的方法,以及靠右取点的原则,实际上暗含了从相邻扫描线绕过障碍点的方法。下面以图(5)为例说明:





图(5)新扫描线区间增大且不连续的情况

算法第3步处理完第5行后,确定了区间[7,9],相邻的第4行虽然实际范围比区间[7,9]大,但是因为被(4,6)这个边界点阻碍,使得在确定种子点(4,9)后向左填充只能填充右边的第7列到第10列之间的区域,而左边的第3列到第5列之间的区域没有填充。虽然作为第5行的相邻行,第一次对第4行的扫描根据靠右原则只确定了(4,9)一个种子点。但是对第3行处理完后,第4行的左边部分作为第3行下边的相邻行,再次得到扫描的机会。第3行的区间是[3,9],向左跨过了第6列这个障碍点,第2次扫描第4行的时候就从第3列开始,向右找,可以确定种子点(4,5)。这样第4行就有了两个种子点,就可以被完整地填充了。

1.3漫水填充算法

原算法中,种子虽然代表一个区段,但种子实质上仍是一个象素,我们必须在种子出栈的时候计算种子区段,而这里有很大一部分计算是重复的.而且,原算法的扫描过程如果不用mask的话,每次都会重复扫描父种子区段所在的扫描线,而用mask的话又会额外增加开销

  所以,对原算法的一个改进就是让种子携带更多的信息,让种子不再是一个象素,而是一个结构体.该结构体包含以下信息:种子区段的y坐标值,区段的x开始与结束坐标,父种子区段的方向(上或者下),父种子区段的x开始与结束坐标.
structseed{
int y,
int xleft,
int xright,
int parent_xleft,
intparent_xright,
boolis_parent_up
};

这样算法的具体实现变动如下

  1、初始化堆栈.
  2、将种子象素扩充成为种子区段(y,xleft, xright, xright+1, xrihgt, true), 填充种子区段,并压入堆栈.(这里有一个构造父种子区段的技巧)
  3while(堆栈非空)
   {
     (1)从堆栈弹出种子区段。
     (2)检查父种子区段所在的扫描线的xleft<= x <= parent_xleftparent_xright<= x <= xright两个区间,如果存在需要填充的新区段,则将其填充并压入堆栈.
     (3)检查非父种子区段所在的扫描线的xleft<= x <= xright区间,如果存在需要填充的新区段,则将其填充并压入堆栈.
   }

另外,opencv里的种子填充算法跟以上方法大致相同,不同的地方是opencv用了队列不是堆栈,而且是由固定长度的数组来实现的循环队列,其固定长度为max(img_width,img_height)*2.并且pushpop均使用宏来实现而没有使用函数.用固定长度的数组来实现队列(或堆栈)意义是显然的,能大大减少构造结构,复制结构等操作,可以大大提高效率.




参考文献:http://blog.csdn.net/jiangxinyu/article/details/7911876

                 http://blog.csdn.net/caiye917015406/article/details/7798688

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值