定义 :

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

基本思想:

所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.

以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子

在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。

实现漫水填充算法:floodFill函数

在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域,连通性由像素值的接近程度来衡量。下面是OpenCV3.X的两个floodFill函数原型:(...\opencv\sources\modules\imgproc\src\floodfill,cpp)

//第一个函数原型
int cv::floodFill( InputOutputArray _image, Point seedPoint,//不带maskScalar newVal, Rect* rect,Scalar loDiff, Scalar upDiff, int flags )
{CV_INSTRUMENT_REGION()return floodFill(_image, Mat(), seedPoint, newVal, rect, loDiff, upDiff, flags);
//可以看到,第一个函数原型内部只是调用了第二个函数
}//第二个函数原型
int cv::floodFill( InputOutputArray _image, InputOutputArray _mask,//带maskPoint seedPoint, Scalar newVal, Rect* rect,Scalar loDiff, Scalar upDiff, int flags )
{CV_INSTRUMENT_REGION()ConnectedComp comp;std::vector<FFillSegment> buffer;if( rect )*rect = Rect();int i, connectivity = flags & 255;union {uchar b[4];int i[4];float f[4];double _[4];} nv_buf;nv_buf._[0] = nv_buf._[1] = nv_buf._[2] = nv_buf._[3] = 0;struct { Vec3b b; Vec3i i; Vec3f f; } ld_buf, ud_buf;Mat img = _image.getMat(), mask;if( !_mask.empty() )mask = _mask.getMat();Size size = img.size();int type = img.type();int depth = img.depth();int cn = img.channels();if ( (cn != 1) && (cn != 3) ){CV_Error( CV_StsBadArg, "Number of channels in input image must be 1 or 3" );}if( connectivity == 0 )connectivity = 4;else if( connectivity != 4 && connectivity != 8 )CV_Error( CV_StsBadFlag, "Connectivity must be 4, 0(=4) or 8" );bool is_simple = mask.empty() && (flags & FLOODFILL_MASK_ONLY) == 0;for( i = 0; i < cn; i++ ){if( loDiff[i] < 0 || upDiff[i] < 0 )CV_Error( CV_StsBadArg, "lo_diff and up_diff must be non-negative" );is_simple = is_simple && fabs(loDiff[i]) < DBL_EPSILON && fabs(upDiff[i]) < DBL_EPSILON;}if( (unsigned)seedPoint.x >= (unsigned)size.width ||(unsigned)seedPoint.y >= (unsigned)size.height )CV_Error( CV_StsOutOfRange, "Seed point is outside of image" );scalarToRawData( newVal, &nv_buf, type, 0);size_t buffer_size = MAX( size.width, size.height ) * 2;buffer.resize( buffer_size );if( is_simple ){size_t elem_size = img.elemSize();const uchar* seed_ptr = img.ptr(seedPoint.y) + elem_size*seedPoint.x;size_t k = 0;for(; k < elem_size; k++)if (seed_ptr[k] != nv_buf.b[k])break;if( k != elem_size ){if( type == CV_8UC1 )floodFill_CnIR(img, seedPoint, nv_buf.b[0], &comp, flags, &buffer);else if( type == CV_8UC3 )floodFill_CnIR(img, seedPoint, Vec3b(nv_buf.b), &comp, flags, &buffer);else if( type == CV_32SC1 )floodFill_CnIR(img, seedPoint, nv_buf.i[0], &comp, flags, &buffer);else if( type == CV_32FC1 )floodFill_CnIR(img, seedPoint, nv_buf.f[0], &comp, flags, &buffer);else if( type == CV_32SC3 )floodFill_CnIR(img, seedPoint, Vec3i(nv_buf.i), &comp, flags, &buffer);else if( type == CV_32FC3 )floodFill_CnIR(img, seedPoint, Vec3f(nv_buf.f), &comp, flags, &buffer);elseCV_Error( CV_StsUnsupportedFormat, "" );if( rect )*rect = comp.rect;return comp.area;}}if( mask.empty() ){Mat tempMask( size.height + 2, size.width + 2, CV_8UC1 );tempMask.setTo(Scalar::all(0));mask = tempMask;}else{CV_Assert( mask.rows == size.height+2 && mask.cols == size.width+2 );CV_Assert( mask.type() == CV_8U );}memset( mask.ptr(), 1, mask.cols );memset( mask.ptr(mask.rows-1), 1, mask.cols );for( i = 1; i <= size.height; i++ ){mask.at<uchar>(i, 0) = mask.at<uchar>(i, mask.cols-1) = (uchar)1;}if( depth == CV_8U )for( i = 0; i < cn; i++ ){ld_buf.b[i] = saturate_cast<uchar>(cvFloor(loDiff[i]));ud_buf.b[i] = saturate_cast<uchar>(cvFloor(upDiff[i]));}else if( depth == CV_32S )for( i = 0; i < cn; i++ ){ld_buf.i[i] = cvFloor(loDiff[i]);ud_buf.i[i] = cvFloor(upDiff[i]);}else if( depth == CV_32F )for( i = 0; i < cn; i++ ){ld_buf.f[i] = (float)loDiff[i];ud_buf.f[i] = (float)upDiff[i];}elseCV_Error( CV_StsUnsupportedFormat, "" );uchar newMaskVal = (uchar)((flags & 0xff00) == 0 ? 1 : ((flags >> 8) & 255));if( type == CV_8UC1 )floodFillGrad_CnIR<uchar, uchar, int, Diff8uC1>(img, mask, seedPoint, nv_buf.b[0], newMaskVal,Diff8uC1(ld_buf.b[0], ud_buf.b[0]),&comp, flags, &buffer);else if( type == CV_8UC3 )floodFillGrad_CnIR<Vec3b, uchar, Vec3i, Diff8uC3>(img, mask, seedPoint, Vec3b(nv_buf.b), newMaskVal,Diff8uC3(ld_buf.b, ud_buf.b),&comp, flags, &buffer);else if( type == CV_32SC1 )floodFillGrad_CnIR<int, uchar, int, Diff32sC1>(img, mask, seedPoint, nv_buf.i[0], newMaskVal,Diff32sC1(ld_buf.i[0], ud_buf.i[0]),&comp, flags, &buffer);else if( type == CV_32SC3 )floodFillGrad_CnIR<Vec3i, uchar, Vec3i, Diff32sC3>(img, mask, seedPoint, Vec3i(nv_buf.i), newMaskVal,Diff32sC3(ld_buf.i, ud_buf.i),&comp, flags, &buffer);else if( type == CV_32FC1 )floodFillGrad_CnIR<float, uchar, float, Diff32fC1>(img, mask, seedPoint, nv_buf.f[0], newMaskVal,Diff32fC1(ld_buf.f[0], ud_buf.f[0]),&comp, flags, &buffer);else if( type == CV_32FC3 )floodFillGrad_CnIR<Vec3f, uchar, Vec3f, Diff32fC3>(img, mask, seedPoint, Vec3f(nv_buf.f), newMaskVal,Diff32fC3(ld_buf.f, ud_buf.f),&comp, flags, &buffer);elseCV_Error(CV_StsUnsupportedFormat, "");if( rect )*rect = comp.rect;return comp.area;
}
//简化版原型:
int cv::floodFill( InputOutputArray _image, InputOutputArray _mask,Point seedPoint,Scalar newVal, Rect* rect = 0,Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4 )

参数详解:两个版本唯一区别就是是否有第二个参数,其他参数都一样

  • 第一个参数,InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。
  • 第二个参数, InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
  • 第三个参数,Point类型的seedPoint,漫水填充算法的起始点。
  • 第四个参数,Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
  • 第五个参数,Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
  • 第六个参数,Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
  • 第七个参数,Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。
  • 第八个参数,int类型的flags,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
  1. 低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
  2. 高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:<1>FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。<2>FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
  3. 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)

一、floodFill简单示例

#include <opencv2/opencv.hpp>
using namespace cv;int main() {Mat srcImage = imread("1.jpg");imshow("原始图", srcImage);Rect ccomp;floodFill(srcImage, Point(300, 300), Scalar(0, 100, 55), &ccomp, Scalar(10, 10, 10), Scalar(10, 10, 10));imshow("效果图", srcImage);waitKey(0);return(0);
}

运行结果:

二、综合实例:漫水填充

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;//全局变量声明部分
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定义原始图、目标图、灰度图、掩膜图
int g_nFillMode = 1;//漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20;//负差最大值,正差最大值
int g_nConnectivity = 4;//表示floodFill函数标识符低八位的连通值
bool g_bIsColor = true;//是否为彩色图的标识符布尔值
bool g_bUseMask = false;//是否显示掩膜窗口的布尔值
int g_nNewMaskVal = 255;//新的重新绘制的像素值//===============【onMouse()函数】=======================
static void onMouse(int event, int x, int y, int, void *) {//若鼠标左键没有按下,便返回if (event != EVENT_LBUTTONDOWN)return;//-----------------【<1>调用floodFill函数之前的参数准备部分】-------------Point seed = Point(x, y);int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);//随机生成BGR值int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值int g = (unsigned)theRNG() & 255;int r = (unsigned)theRNG() & 255;Rect ccomp;//定义重绘区域的最小边界矩阵区域Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g * 0.587 + b * 0.114);Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目标图的赋值int area;//---------------------【<2>正式调用floodFill函数】------------------if (g_bUseMask) {threshold(g_maskImage, g_maskImage, 1, 128, THRESH_BINARY);area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),Scalar(UpDifference, UpDifference, UpDifference),flags);imshow("mask", g_maskImage);}else {area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);}imshow("效果图", dst);cout << area << " 个像素被重新绘制\n";
}//main()函数
int main(int argc, char** argv) {//载入原图g_srcImage = imread("1.jpg", 1);if (!g_srcImage.data) {printf("读取g_srcImage错误!\n");return false;}g_srcImage.copyTo(g_dstImage);//复制原图到目标图cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转为灰度图到g_grayImageg_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);//用原图尺寸初始化掩膜masknamedWindow("效果图", WINDOW_AUTOSIZE);//创建TrackbarcreateTrackbar("负差最大值", "效果图", &g_nLowDifference, 255, 0);createTrackbar("正差最大值", "效果图", &g_nUpDifference, 255, 0);//鼠标回调函数setMouseCallback("效果图", onMouse, 0);//循环轮询按键while (1) {//先显示效果图imshow("效果图", g_bIsColor ? g_dstImage : g_grayImage);//获取按键键盘int c = waitKey(0);//判断ESC是否按下,按下退出if (c == 27) {cout << "程序退出........、\n";break;}//根据按键不同进行不同的操作switch ((char)c) {//如果键盘1被按下,效果图在灰度图和彩色图之间转换case '1':if (g_bIsColor) {//若原来为彩色图,转换为灰度图,并将掩膜mask所有元素设置为0cout << "键盘‘1’按下,切换彩色/灰度模式,当前操作将【彩色模式】切换为【灰度模式】" << endl;cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);g_maskImage = Scalar::all(0);//将mask所有元素设置为0g_bIsColor = false;}else {cout << "键盘‘1’按下,切换彩色/灰度模式,当前操作将【灰度模式】切换为【彩色模式】" << endl;g_srcImage.copyTo(g_dstImage);g_maskImage = Scalar::all(0);g_bIsColor = true;}case '2':if (g_bUseMask) {destroyWindow("mask");g_bUseMask = false;}else {namedWindow("mask", 0);g_maskImage = Scalar::all(0);imshow("mask", g_maskImage);g_bUseMask = true;}break;case '3'://如果键盘3被按下,恢复原始图像cout << "按下键盘‘3’,恢复原始图像\n";g_srcImage.copyTo(g_dstImage);cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);g_maskImage = Scalar::all(0);break;case '4':cout << "键盘‘4’被按下,使用空范围的漫水填充\n";g_nFillMode = 0;break;case '5':cout << "键盘‘5’被按下,使用渐变、固定范围的漫水填充\n";g_nFillMode = 1;break;case '6':cout << "键盘‘6’被按下,使用渐变、浮动范围的漫水填充\n";g_nFillMode = 2;break;case '7':cout << "键盘‘7’被按下,操作标识符的低八位使用4位的连接模式\n";g_nConnectivity = 4;break;case '8':cout << "键盘‘8’被按下,操作标识符的低八位使用8为的连接模式\n";g_nConnectivity = 8;break;}}return 0;}

运行结果:(部分截图)

<1>原始图:

<2>运行截图1

<2>运行截图2

<3>运行截图3

OpenCV3之——漫水填充:floodFill函数相关推荐

  1. OpenCV3学习(7.1)——图像分割之一(漫水填充FloodFill)

    漫水填充算法 漫水填充法是一种用特定的颜色填充算法填充连通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法.基本思想是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色, ...

  2. 漫水填充及Photoshop中魔术棒选择工具的实现

    今天写程序中有一个地方用到了漫水填充(FloodFill).所谓漫水填充,简单来说,如下图中左图,白布上有一块红色的斑点,在这个红色的斑点上点一下,就自动选中了和该点相连的红色的区域,接着将该区域替换 ...

  3. 【OpenCV】漫水填充

    文章目录 1.漫水填充的定义 2.漫水填充法的基本思想 3.实现漫水填充算法:floodFill函数 4.代码详解 1.漫水填充的定义 ​漫水填充法是一种特定的颜色填充连通区域,通过设置可以连通像素的 ...

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

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

  5. 《OpenCV3编程入门》学习笔记6 图像处理(五)漫水填充

    6.5 漫水填充(floodFill) 6.5.1 漫水填充 1.定义:一种用特定的颜色填充连通区域,通过设置可连通像素的上下限及连通方式达到不同填充效果 2.基本思想:自动选中和种子点相连的区域(位 ...

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

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

  7. OpenCV学习笔记(九): 漫水填充:floodFill()

    OpenCV学习笔记(九): 漫水填充:floodFill() 定义: 漫水填充法是一种用特定的颜色填充联通区域(自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色)通过设置可连通像素的上下限 ...

  8. floodfill算法 java_OpenCV 3 floodFill(漫水填充)、图片的放大缩小 pyrUp、pyrDown、Resize JAVA OpenCV专题学习10...

    关于 JAVA 学习 OpenCV 的内容,函数讲解.内容我均整理在 GitHubd的[OpenCV3-Study-JAVA] 下面代码中所需的项目结构,图片,请访问 GitHub 获取. 内容在注释 ...

  9. 【图像处理】漫水填充函数的使用(flags的用法)

    漫水填充的定义及基本思想 相信大家都玩过计算机中画图工具里的油漆桶,鼠标一点,一片相同颜色的区域就会被油漆桶中颜色覆盖,这便是漫水填充实现的.漫水填充的定义是用特定的颜色填充联通区域,通过设置可以连通 ...

最新文章

  1. 点分治问题 ----------- HDU4812 D Tree [点分治 + 乘法逆元]
  2. 下列关于python字典变量的定义中错误的是_python练习题-day14
  3. 彻底关闭windows server 2008 IPv6
  4. c语言 2D-FFT(fft2)及IFFT
  5. Fennec Alpha 1 for Windows Mobile available
  6. 《统计学习方法》代码更新了-(github的star数5300+)
  7. 计算机硬件如何分类,计算机硬件分类教学.doc
  8. webpack4下import()模块按需加载,打包按需切割模块,减少包体积,加快首页请求速度...
  9. 6-2-二叉树(二叉链表存储)-树和二叉树-第6章-《数据结构》课本源码-严蔚敏吴伟民版...
  10. 阿里云云计算 17 块存储的分类
  11. 干货,分享!后台模板hplus 好看的后台纯模板!!!
  12. 醒的越早,越焦虑,马上奔35了
  13. 010Editor的Template安装与使用
  14. U盘提示''这张磁盘有写保护''修复工具
  15. 【HDOJ 5336】XYZ and Drops
  16. Win11电脑系统使用U盘安装的方法分享
  17. 搭建vue脚手架(vue-cli)--基于vue2.0版本
  18. 【协程】MyCoroutine轻量级协程框架代码详细剖解
  19. php论坛整合,Dedecms与Discuz论坛整合方法
  20. 一次kubenetes的rook-ceph创建pv失败的故障排查

热门文章

  1. Git---如何生成patch文件
  2. PHP和Java的区别和用处?答案在这里
  3. oracle取最小值怎么写,【Oracle】oracle取最大值和最小值的几个方法汇总
  4. 记录几种隐藏链接代码,也就是暗链代码
  5. 偷的名表卖掉能查到吗_偷前老板价值百万元名表,欲销赃被时被抓
  6. 正则表达式(六)之替换
  7. MySQL与JDBC基础
  8. 操作系统真象还原--书上完整版代码+工具
  9. 主机支持php的curl扩展,[Windows技术] 使服务器支持PHP curl函数:开启CURL扩展的方法...
  10. 【报告分享】七大平台双十一作战营销宝典-克劳锐(附下载)