【OpenCV】漫水填充
漫水填充:也就是用一定颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果;漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
种子填充算法
种子填充算法是从多边形区域内部的一点开始,由此出发找到区域内的所有像素。
种子填充算法采用的边界定义是区域边界上所有像素具有某个特定的颜色值,区域内部所有像素均不取这一特定颜色,而边界外的像素则可具有与边界相同的颜色值。
具体算法步骤:
- 标记种子(x,y)的像素点 ;
- 检测该点的颜色,若他与边界色和填充色均不同,就用填充色填 充该点,否则不填充 ;
- 检测相邻位置,继续 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语言实现
======================================================== 转载请注明出处:http://blog.csdn.net/songzitea/article/details/8763094 ========================================================
- #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()
【OpenCV】漫水填充的更多相关文章
- 【OpenCV新手教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/28261997 作者:毛星云( ...
- OpenCV漫水填充算法示例代码
#include<cv.h> #include<highgui.h> int main(int argc, char** argv) { IplImage* img = cvL ...
- OpenCV——漫水填充
- opencv 4 图像处理(漫水填充,图像金字塔与图片尺寸缩放,阈(yu)值化)
漫水填充 实现漫水填充算法:floodFill函数 简单调用范例 #include <opencv2/opencv.hpp> #include <opencv2/imgproc/im ...
- Opencv之漫水填充效果
下面是opencv的漫水填充效果代码 这篇文章仅限个人的笔记 没有详细的注释 放代码 这是简单的示范 int main()//*******************简单的漫水填充算法实例 { Vide ...
- opencv —— floodFill 漫水填充法 实现证件照换背景
漫水填充:floodFill 函数 简单来说,漫水填充就是自动选中与种子像素相连的区域,利用指定颜色进行区域颜色填充.Windows 画图工具中的油漆桶功能和 Photoshop 的魔法棒选择工具,都 ...
- OpenCV3编程入门笔记(4)腐蚀、膨胀、开闭运算、漫水填充、金字塔、阈值化、霍夫变换
腐蚀erode.膨胀dilate 腐蚀和膨胀是针对图像中的白色部分(高亮部分)而言的,不是黑色的.除了输入输出图像外,还需传入模板算子element,opencv中有三种可以选择:矩形MORPH_RE ...
- 漫水填充算法 - cvFloodFill() 实现
前言 漫水填充算法是用来标记一片区域的:设置一个种子点,然后种子点附近的相似点都被填充同一种颜色. 该算法应用性很广,比如目标识别,photoshop 的魔术棒功能等等,是填充类算法中应用最为广泛的一 ...
- DFS求连通块(漫水填充法)
G - DFS(floodfill),推荐 Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I6 ...
随机推荐
- 简单仿京东导航下拉菜单 javascript
<html xmlns="http://www.w3.org/1999/xhtml"><head runat="server"> ...
- 4.5 HOOK分发函数
4.5 HOOK分发函数 本节开始深入的探讨键盘的过滤与反过滤.有趣的是,无论是过滤还是反过 滤,其原理都是进行过滤.取胜的关键在于:谁将第一个得到信息. 黑客可能会通过修改一个已经存在的驱动对象(比 ...
- 制作Linux(Fedora、Ubuntu、CentOS)优盘启动
随着嵌入式技术的快速发展,Linux快速发展过一段时间.虽然现在不是很热,但是linux在现实社会的使用还是很有用处.而光盘有有些落伍,不仅浪费而且不环保,所以质优价廉的优盘就脱颖而出.所以,用优盘制 ...
- SVProgressHUD的使用
GitHub:https://github.com/samvermette/SVProgressHUD SVProgressHUD和MBProgressHUD效果差点儿相同,只是不须要使用协议,同一时 ...
- JavaDoc的生成规则---ShinePans
使用方法: javadoc [options] [packagenames] [sourcefiles] [@files] -overview <file> 从 HTML ...
- cygrunsrv: Error starting a service: QueryServiceStatus: Win32 error 1062: 解决办法
问题原因:很可能是/var/log的权限设置不正确.首先执行 mkpasswd 和 mkgroup 重新生成权限信息,再删除sshd服务,重新配置 解决办法: $ mkpasswd -l > / ...
- CentOS6.5安全策略设置
应公司内部网站等级测评的需求,正逐渐加强系统安全防护 密码策略设置 检查方法: 使用命令 #cat /etc/login.defs|grep PASS查看密码策略设置 备份方法: cp -p /etc ...
- 获取操作系统版本Asp.Net
/// <summary> /// 获取操作系统版本号 /// </summary> /// <returns></returns> public st ...
- Spring源码解析——如何阅读源码(转)
最近没什么实质性的工作,正好有点时间,就想学学别人的代码.也看过一点源码,算是有了点阅读的经验,于是下定决心看下spring这种大型的项目的源码,学学它的设计思想. 手码不易,转载请注明:xingoo ...
- 服务列表 - Sina App Engine
服务列表 - Sina App Engine 短信服务 新浪无线短信服务是由新浪无线提供的综合性短信服务. 使用服务 下载SDK: php 服务首页 方法 新浪无线短信服务是由新浪无线提供的综合性短信 ...