openCv学习笔记(七)-漫水填充算法详解

一 漫水填充算法描述

    1..1 、种子填充算法

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

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

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

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

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

     1.2、扫描线种子填充算法

       该算法属于种子填充算法,它是以扫描线上的区段为单位操作。所谓区段,就是一条扫描线上相连着的若干内部象素的集合。
     扫描线种子填充算法思想:首先填
充当前扫描线上的位于给定区域的一区段,然后确定于这一区段相邻的上下两条线上位于该区域内是否存在需要填充的新区段,如果存在,则依次把他们保存起来,反复这个过程,直到所保存的各区段都填充完毕。

        1.2.1、扫描线种子填充算法实现(有演示动画见http://hi.baidu.com/jimmywood1987/blog/item/8410d9d5e621bd209a502740.html
    借助于堆栈,上述算法实现步骤如下:

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

    }

   1.2.2改进算法

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

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

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

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

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


二 漫水填充算法c语言实现

/* 漫水法填充标定实现
   copy from: http://blog.csdn.net/zhjx2314/article/details/1629702 


*/
  //像素值
 unsigned char pixel;
 //种子堆栈及指针
 Seed *Seeds;
 int StackPoint;
 //当前像素位置
 int iCurrentPixelx,iCurrentPixely;
  //分配种子空间 
 Seeds = new Seed[iWidth*iLength];
 
 //计算每个标定值的像素个数
 int count[251];
 for(i=1;i<252;i++)
 {  
	 count[i]=0; //初始化为0
 } 
 
    //滤波的阈值
 int yuzhi = 700;
 

 for (i=0;i<iWidth;i++)
 {
   for (j=0;j<iLength;j++)
   {  
		 if (grey_liantong.GetPixel(i,j)==0)  //当前像素为黑,对它进行漫水法标定
		 {
				//初始化种子
				 Seeds[1].x = i;
				 Seeds[1].y = j;
				 StackPoint = 1;
			 
				while( StackPoint != 0)
				{
				  //取出种子
				  iCurrentPixelx = Seeds[StackPoint].x;
				  iCurrentPixely = Seeds[StackPoint].y;
				  StackPoint--;
				        
				 
				  //取得当前指针处的像素值,注意要转换为unsigned char型
				  pixel = (unsigned char)grey_liantong.GetPixel(iCurrentPixelx,iCurrentPixely);
				 
				  //将当前点标定
				  grey_liantong.SetPixel(iCurrentPixelx,iCurrentPixely,flag);
						count[flag]++; //标定像素计数
				 
				  //判断左面的点,如果为黑,则压入堆栈
				  //注意防止越界
				  if(iCurrentPixelx > 1)
				  {
				   
				       //取得当前指针处的像素值,注意要转换为unsigned char型
					   pixel = (unsigned char)grey_liantong.GetPixel(iCurrentPixelx-1,iCurrentPixely);
					   if (pixel == 0)
					   {
							StackPoint++;
							Seeds[StackPoint].y = iCurrentPixely;
							Seeds[StackPoint].x = iCurrentPixelx - 1;
					   }
				  }
				 
				  //判断上面的点,如果为黑,则压入堆栈
				  //注意防止越界
				  if(iCurrentPixely < iLength - 1)
				  {
				   
					   //取得当前指针处的像素值,注意要转换为unsigned char型
					   pixel = (unsigned char)grey_liantong.GetPixel(iCurrentPixelx,iCurrentPixely+1);
					   if (pixel == 0)
					   {
							StackPoint++;
							Seeds[StackPoint].y = iCurrentPixely + 1;
							Seeds[StackPoint].x = iCurrentPixelx;
					   }
				  }
				 
				  //判断右面的点,如果为黑,则压入堆栈
				  //注意防止越界
				  if(iCurrentPixelx < iWidth - 1)
				  {
				   
					   //取得当前指针处的像素值,注意要转换为unsigned char型
					   pixel = (unsigned char)grey_liantong.GetPixel(iCurrentPixelx+1,iCurrentPixely);
					   if (pixel == 0)
					   {
							StackPoint++;
							Seeds[StackPoint].y = iCurrentPixely;
							Seeds[StackPoint].x = iCurrentPixelx + 1;
					   }
				  }
				 
				  //判断下面的点,如果为黑,则压入堆栈
				  //注意防止越界
				  if(iCurrentPixely > 1)
				  {
				   
					   //取得当前指针处的像素值,注意要转换为unsigned char型
					   pixel = (unsigned char)grey_liantong.GetPixel(iCurrentPixelx,iCurrentPixely-1);
					   if (pixel == 0)
					   {
							StackPoint++;
							Seeds[StackPoint].y = iCurrentPixely - 1;
							Seeds[StackPoint].x = iCurrentPixelx;
					   }
				  }
			  }//end while( StackPoint != 0)
			  flag = (flag + 7)%251+1;  //当前点连通区域标定后,改变标定值
		 }//end if  
   }//end for(i
 }//end for(j
 

 //释放堆栈
 delete Seeds;
    grey_res.Clone(grey_liantong); 


/*
   摘自:http://blog.sina.com.cn/s/blog_611a555e0100fcrq.html


	floodfill算法
2008-07-28 11:22
 

+ From wikipedia

Flood fill, also called seed fill, is an algorithm that determines the area connected to a given node in a multi-dimensional array. It is used in the "bucket" fill tool of paint programs to determine which parts of a bitmap to fill with color

+ the algorithms

The flood fill algorithm takes three parameters: a start node, a target color, and a replacement color. The algorithm looks for all nodes in the array which are connected to the start node by a path of the target color, and changes them to the replacement color. There are many ways in which the flood-fill algorithm can be structured, but they all make use of a queue or stack data structure, explicitly or implicitly.

从上面的算法介绍可知,凡是会搜索的同学就能很轻易地掌握floodfill。因为floodfill算法从大体而言可以细分为两种算法思想,一种是DFS,一种是BFS。以下讲介绍两大种常用的算法,并简单分析其中用到的dfs和bfs。

1. per-pixel fill (点点填充)

 

                                                           

recursive flood-fill with 4 directions                      recursive flood-fill with 8 directions

这两个有一个小区别,就是8方向的算法是在4方向的算法的基础上添加了四个方向(左上、左下、右上、右下),因此造成的结果是8方向的算法有可能会“leak through sloped edges of 1 pixel thick”。至于其它方面则是完全一样,下面所有的算法都是基于4方向的。

用dfs的搜索思想(递归)写的代码:

Flood-fill (node, target-color, replacement-color):
1. If the color of node is not equal to target-color, return.
2. Set the color of node to replacement-color.
3. Perform Flood-fill (one step to the west of node, target-color, replacement-color).
    Perform Flood-fill (one step to the east of node, target-color, replacement-color).
    Perform Flood-fill (one step to the north of node, target-color, replacement-color).
    Perform Flood-fill (one step to the south of node, target-color, replacement-color).
4. Return.

用bfs的搜索思想(队列)写的代码:

Flood-fill (node, target-color, replacement-color):
1. Set Q to the empty queue.
2. If the color of node is not equal to target-color, return.
3. Add node to the end of Q.
4. While "Q" is not empty:
5.     Set "n" equal to the first element of "Q"
6.     If the color of n is equal to target-color, set the color of n to replacement-color.
7.     Remove first element from "Q"
8.     If the color of the node to the west of n is target-color, set the color of that node to replacement-color, add that node to the end of Q.
9.     If the color of the node to the east of n is target-color, set the color of that node to replacement-color, add that node to the end of Q.
10.    If the color of the node to the north of n is target-color, set the color of that node to replacement-color, add that node to the end of Q.
11.    If the color of the node to the south of n is target-color, set the color of that node to replacement-color, add that node to the end of Q.
12. Return.

以上两种小算法的问题在于,如果填充的面积较大的话,程序很容易爆掉。究其原因,就是搜索的深度过大或队列的长度不够造成的。因此为了减少搜索深度或进队列的元素个数,可以用线方式代替点方式。而这样做还有一个好处就是填充速度的提高。
*/

	
	
//2. scanline fill (扫描线填充)
 

//stack friendly and fast floodfill algorithm(递归深搜的写法)

void floodFillScanline(int x, int y, int newColor, int oldColor)
{
    if(oldColor == newColor) return;
    if(screenBuffer[x][y] != oldColor) return;
     
    int y1;
   
    //draw current scanline from start position to the top
    y1 = y;
    while(y1 < h && screenBuffer[x][y1] == oldColor)
    {
        screenBuffer[x][y1] = newColor;
        y1++;
    }   
   
    //draw current scanline from start position to the bottom
    y1 = y - 1;
    while(y1 >= 0 && screenBuffer[x][y1] == oldColor)
    {
        screenBuffer[x][y1] = newColor;
        y1--;
    }
   
    //test for new scanlines to the left
    y1 = y;
    while(y1 < h && screenBuffer[x][y1] == newColor)
    {
        if(x > 0 && screenBuffer[x - 1][y1] == oldColor)
        {
            floodFillScanline(x - 1, y1, newColor, oldColor);
        }
        y1++;
    }
    y1 = y - 1;
    while(y1 >= 0 && screenBuffer[x][y1] == newColor)
    {
        if(x > 0 && screenBuffer[x - 1][y1] == oldColor)
        {
            floodFillScanline(x - 1, y1, newColor, oldColor);
        }
        y1--;
    }
   
    //test for new scanlines to the right
    y1 = y;
    while(y1 < h && screenBuffer[x][y1] == newColor)
    {
        if(x < w - 1 && screenBuffer[x + 1][y1] == oldColor)
        {          
            floodFillScanline(x + 1, y1, newColor, oldColor);
        }
        y1++;
    }
    y1 = y - 1;
    while(y1 >= 0 && screenBuffer[x][y1] == newColor)
    {
        if(x < w - 1 && screenBuffer[x + 1][y1] == oldColor)
        {
            floodFillScanline(x + 1, y1, newColor, oldColor);
        }
        y1--;
    }
}

//The scanline floodfill algorithm using our own stack routines, faster(广搜队列的写法)

void floodFillScanlineStack(int x, int y, int newColor, int oldColor)
{
    if(oldColor == newColor) return;
    emptyStack();
   
    int y1;
    bool spanLeft, spanRight;
   
   
  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值