漫水填充:也就是用一定颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果;漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。

种子填充算法

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

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

具体算法步骤:

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

当然在搜索的时候有两种检测相邻像素:四向连通和八向连通。四向连通即从区域上一点出发,通过四个方向上、下、左、右来检索。而八向连通加上了左上、左下、右上、右下四个方向。这种算法的有点是算法简单,易于实现,也可以填充带有内孔的平面区域。但是此算法需要更大的存储空间以实现栈结构,同一个像素多次入栈和出栈,效率低,运算量大。

扫描线种子填充算法

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

借助于堆栈,上述算法实现步骤如下:

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

更进一步算法

原算法中, 种子虽然代表一个区段, 但种子实质上仍是一个象素, 我们必须在种子出栈的时候计算种子区段, 而这里有很大一部分计算是重复的. 而且, 原算法的扫描过程如果不用mask的话, 每次都会重复扫描父种子区段所在的扫描线, 而用mask的话又会额外增加开销。所以, 对原算法的一个改进就是让种子携带更多的信息, 让种子不再是一个象素, 而是一个结构体. 该结构体包含以下信息: 种子区段的y坐标值, 区段的x开始与结束坐标, 父种子区段的方向(上或者下), 父种子区段的x开始与结束坐标.

struct seed{
int y,
int xleft,
int xright,
int parent_xleft,
int parent_xright,
bool is_parent_up
};

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

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

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

c语言实现

//漫水法填充标定实现
//像素值
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);

扫描线填充

//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; if(!push(x, y)) return; while(pop(x, y))
{
y1 = y;
while(y1 >= 0 && screenBuffer[x][y1] == oldColor) y1--;
y1++;
spanLeft = spanRight = 0;
while(y1 < h && screenBuffer[x][y1] == oldColor )
{
screenBuffer[x][y1] = newColor;
if(!spanLeft && x > 0 && screenBuffer[x - 1][y1] == oldColor)
{
if(!push(x - 1, y1)) return;
spanLeft = 1;
}
else if(spanLeft && x > 0 && screenBuffer[x - 1][y1] != oldColor)
{
spanLeft = 0;
}//写这一部分是防止因为同一列上有断开的段而造成的可能的“没填充”
if(!spanRight && x < w - 1 && screenBuffer[x + 1][y1] == oldColor)
{
if(!push(x + 1, y1)) return;
spanRight = 1;
}
else if(spanRight && x < w - 1 && screenBuffer[x + 1][y1] != oldColor)
{
spanRight = 0;
} //写这一部分是防止因为同一列上有断开的段而造成的可能的“没填充”
y1++;
}
}
}
CVAPI(void)  cvFloodFill( CvArr* image, CvPoint seed_point,
CvScalar new_val, CvScalar lo_diff CV_DEFAULT(cvScalarAll(0)),
CvScalar up_diff CV_DEFAULT(cvScalarAll(0)),
CvConnectedComp* comp CV_DEFAULT(NULL),
int flags CV_DEFAULT(4),
CvArr* mask CV_DEFAULT(NULL));

其中函数参数:

  • image为待处理图像
  • seed_point为种子坐标
  • new_val为填充值
  • lo_diff为像素值的下限差值
  • up_diff为像素值的上限差值
  • 从函数形式可看出,该函数可处理多通道图像。
  • mask为掩码, 注意: 设输入图像大小为width * height, 则掩码的大小必须为 (width+2) * (height+2) ,  mask可为输出,也可作为输入 ,由flags决定
  • flags参数 :  0~7位为0x04或者0x08 即 4连通或者8 连通
  • 8~15位为填充mask的值大小 , 若为0 , 则默认用1填充
  • 16~23位为 : CV_FLOODFILL_FIXED_RANGE =(1 << 16), CV_FLOODFILL_MASK_ONLY   =(1 << 17)
  • flags参数通过位与运算处理

当为CV_FLOODFILL_FIXED_RANGE 时,待处理的像素点与种子点作比较,如果满足(s - lodiff , s + updiff)之间(s为种子点像素值),则填充此像素 .  若无此位设置,则将待处理点与已填充的相邻点作此比较

CV_FLOODFILL_MASK_ONLY   此位设置填充的对像,  若设置此位,则mask不能为空,此时,函数不填充原始图像img,而是填充掩码图像.   若无此位设置,则在填充原始图像的时候,也用flags的8~15位标记对应位置的mask.

OpenCV语言实现

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <iostream> using namespace std; int main() {
cvNamedWindow("source");
cvNamedWindow("dest1");
cvNamedWindow("dest2");
cvNamedWindow("mask0");
cvNamedWindow("mask1");
IplImage * src = cvLoadImage("f:\\images\\test.jpg");
IplImage * img=cvCreateImage(cvGetSize(src), 8, 3);
IplImage *img2=cvCreateImage(cvGetSize(src),8,3);
IplImage *pMask = cvCreateImage(cvSize(src->width +2 ,src->height +2),8,1);
cvSetZero(pMask);
cvCopyImage(src, img);
cvCopyImage(src,img2); cvFloodFill(
img,
cvPoint(300,310),
CV_RGB(255,0,0),
cvScalar(20,30,40,0),
cvScalar(5,30,40,0),
NULL,
CV_FLOODFILL_FIXED_RANGE | (0x9f<<8),
pMask
);
cvShowImage("mask0",pMask);
cvSetZero(pMask);
cvFloodFill(
img2,
cvPoint(80,80),
CV_RGB(255,0,0),
cvScalarAll(29),
cvScalarAll(10),
NULL,
CV_FLOODFILL_MASK_ONLY | (47<<8) ,
pMask
); cvShowImage("source",src);
cvShowImage("dest1", img);
cvShowImage("dest2",img2);
cvShowImage("mask1",pMask);
cvWaitKey(0);
cvReleaseImage(&src);
cvReleaseImage(&img);
cvReleaseImage(&img2);
cvReleaseImage(&pMask);
cvDestroyAllWindows();
return 0;
}

Python语言实现

#decoding:utf-8
import numpy as np
import cv2
import random help_message ='''''USAGE: floodfill.py [<image>]
Click on the image to set seed point Keys:
f - toggle floating range
c - toggle 4/8 connectivity
ESC - exit
''' if __name__ == '__main__':
import sys
try: fn = sys.argv[1]
except: fn = '../test.jpg'
print help_message img = cv2.imread(fn, True)
h, w = img.shape[:2] #得到图像的高和宽
mask = np.zeros((h+2, w+2), np.uint8)#掩码单通道8比特,长和宽都比输入图像多两个像素点,满水填充不会超出掩码的非零边缘
seed_pt = None
fixed_range = True
connectivity = 4 def update(dummy=None):
if seed_pt is None:
cv2.imshow('floodfill', img)
return
flooded = img.copy() #以副本的形式进行填充,这样每次
mask[:] = 0 #掩码初始为全0
lo = cv2.getTrackbarPos('lo', 'floodfill')#观察点像素邻域负差最大值(也就是与选定像素多少差值内的归为同一区域)
hi = cv2.getTrackbarPos('hi', 'floodfill')#观察点像素邻域正差最大值
flags = connectivity #低位比特包含连通值, 4 (缺省) 或 8
if fixed_range:
flags |= cv2.FLOODFILL_FIXED_RANGE #考虑当前象素与种子象素之间的差(高比特也可以为0)
#以白色进行漫水填充
cv2.floodFill(flooded, mask, seed_pt, (random.randint(0,255), random.randint(0,255), random.randint(0,255)), (lo,)*3, (hi,)*3, flags) cv2.circle(flooded, seed_pt, 2, (0, 0, 255), -1)#选定基准点用红色圆点标出
cv2.imshow('floodfill', flooded) def onmouse(event, x, y, flags, param): #鼠标响应函数
global seed_pt
if flags & cv2.EVENT_FLAG_LBUTTON: #鼠标左键响应,选择漫水填充基准点
seed_pt = x, y
update() update()
cv2.setMouseCallback('floodfill', onmouse)
cv2.createTrackbar('lo', 'floodfill', 20, 255, update)
cv2.createTrackbar('hi', 'floodfill', 20, 255, update) while True:
ch = 0xFF & cv2.waitKey()
if ch == 27:
break
if ch == ord('f'):
fixed_range = not fixed_range #选定时flags的高位比特位0,也就是邻域的选定为当前像素与相邻像素的的差,这样的效果就是联通区域会很大
print 'using %s range' % ('floating', 'fixed')[fixed_range]
update()
if ch == ord('c'):
connectivity = 12-connectivity #选择4方向或则8方向种子扩散
print 'connectivity =', connectivity
update()
cv2.destroyAllWindows()
========================================================
转载请注明出处:http://blog.csdn.net/songzitea/article/details/8763094
========================================================

【OpenCV】漫水填充的更多相关文章

  1. 【OpenCV新手教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)

    本系列文章由@浅墨_毛星云 出品,转载请注明出处.    文章链接: http://blog.csdn.net/poem_qianmo/article/details/28261997 作者:毛星云( ...

  2. OpenCV漫水填充算法示例代码

    #include<cv.h> #include<highgui.h> int main(int argc, char** argv) { IplImage* img = cvL ...

  3. OpenCV——漫水填充

  4. opencv 4 图像处理(漫水填充,图像金字塔与图片尺寸缩放,阈(yu)值化)

    漫水填充 实现漫水填充算法:floodFill函数 简单调用范例 #include <opencv2/opencv.hpp> #include <opencv2/imgproc/im ...

  5. Opencv之漫水填充效果

    下面是opencv的漫水填充效果代码 这篇文章仅限个人的笔记 没有详细的注释 放代码 这是简单的示范 int main()//*******************简单的漫水填充算法实例 { Vide ...

  6. opencv —— floodFill 漫水填充法 实现证件照换背景

    漫水填充:floodFill 函数 简单来说,漫水填充就是自动选中与种子像素相连的区域,利用指定颜色进行区域颜色填充.Windows 画图工具中的油漆桶功能和 Photoshop 的魔法棒选择工具,都 ...

  7. OpenCV3编程入门笔记(4)腐蚀、膨胀、开闭运算、漫水填充、金字塔、阈值化、霍夫变换

    腐蚀erode.膨胀dilate 腐蚀和膨胀是针对图像中的白色部分(高亮部分)而言的,不是黑色的.除了输入输出图像外,还需传入模板算子element,opencv中有三种可以选择:矩形MORPH_RE ...

  8. 漫水填充算法 - cvFloodFill() 实现

    前言 漫水填充算法是用来标记一片区域的:设置一个种子点,然后种子点附近的相似点都被填充同一种颜色. 该算法应用性很广,比如目标识别,photoshop 的魔术棒功能等等,是填充类算法中应用最为广泛的一 ...

  9. DFS求连通块(漫水填充法)

    G - DFS(floodfill),推荐 Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I6 ...

随机推荐

  1. ios改变系统默认样式

    iso系统下默认不能修改submit样式,于是加上如下属性就可以修改: -webkit-appearance:none; -moz-appearance:none; 先记录下.

  2. Android 开发 AirPlay Server

    安卓上开发  AirPlay Server  主要是参考了和修改了 DroidAirPlay项目 , 和Airplay 协议 1, 将DroidAirPlay 下载下来 2, Eclipse 新建一个 ...

  3. java排序方法中的插入排序方法

    插入排序方法就是:将一个数据插入到已经排好序的有序数据中,从而得到一个新的.个数加一的有序数据. package Array; //插入排序方法 import java.until.Scanner; ...

  4. 【转】LINUX下一款不错的网站压力测试工具webbench

    原文链接:http://blog.csdn.net/xinqingch/article/details/8618704 安装: wget http://blog.s135.com/soft/linux ...

  5. codeigniter ,看完这些,就可以用它做项目了

    一.MVC 1,入口文件 唯一一个让浏览器直接请求的脚本文件 2,控制器 controller 负责协调模型和视图 3,模型 model 只负责提供数据,保存数据 4,视图 只负责显示,以及搜集用户的 ...

  6. Struts1的处理流程

    本文从收到一个请求开始讲述,忽略之前的filter等工作. 处理工作的主要承担者为RequestProcessor 1.处理请求的url. RequestProcessor.processPath(r ...

  7. ZOJ 2972 Hurdles of 110m 【DP 背包】

    一共有N段过程,每段过程里可以选择 快速跑. 匀速跑 和 慢速跑 对于快速跑会消耗F1 的能量, 慢速跑会集聚F2的能量 选手一开始有M的能量,即能量上限 求通过全程的最短时间 定义DP[i][j] ...

  8. POJ 2187 旋转卡壳 + 水平序 Graham 扫描算法 + 运算符重载

    水平序 Graham 扫描算法: 计算二维凸包的时候可以用到,Graham 扫描算法有水平序和极角序两种. 极角序算法能一次确定整个凸包, 但是计算极角需要用到三角函数,速度较慢,精度较差,特殊情况较 ...

  9. Ural 1079 - Maximum

    Consider the sequence of numbers ai, i = 0, 1, 2, …, which satisfies the following requirements: a0  ...

  10. OpenBlas编译方法(体验msys下使用MingW)

    OpenBlas是一个优化的Blas库,基于GotoBlas21.13 BSD版,安装步骤如下: Windows下安装: 1. 在SourgeForge下载最新的OpenBlas库:http://so ...