漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
floodFill函数详解
在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。
int 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,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
我这里用了浅墨大神的源码:
//-----------------------------------【程序说明】---------------------------------------------- // 程序名称::《【OpenCV入门教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)》 博文配套源码 // 开发所用IDE版本:Visual Studio 2010 // 开发所用OpenCV版本: 2.4.9 // 2014年6月3日 Created by 浅墨 // 配套博文链接: http://blog.csdn.net/poem_qianmo/article/details/28261997 // PS:程序结合配合博文学习效果更佳 // 浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442/profile?topnav=1&wvr=5&user=1 // 浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun // 浅墨的豆瓣:http://www.douban.com/people/53426472/ //---------------------------------------------------------------------------------------------- //-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //---------------------------------------------------------------------------------------------- #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> //-----------------------------------【命名空间声明部分】--------------------------------------- // 描述:包含程序所使用的命名空间 //----------------------------------------------------------------------------------------------- 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函数标识符低八位的连通值 int g_bIsColor = true;//是否为彩色图的标识符布尔值 bool g_bUseMask = false;//是否显示掩膜窗口的布尔值 int g_nNewMaskVal = 255;//新的重新绘制的像素值 //-----------------------------------【ShowHelpText( )函数】---------------------------------- // 描述:输出一些帮助信息 //---------------------------------------------------------------------------------------------- static void ShowHelpText() { //输出一些帮助信息 printf("\n\n\n\t欢迎来到漫水填充示例程序~\n\n"); printf( "\n\n\t按键操作说明: \n\n" "\t\t鼠标点击图中区域- 进行漫水填充操作\n" "\t\t键盘按键【ESC】- 退出程序\n" "\t\t键盘按键【1】- 切换彩色图/灰度图模式\n" "\t\t键盘按键【2】- 显示/隐藏掩膜窗口\n" "\t\t键盘按键【3】- 恢复原始图像\n" "\t\t键盘按键【4】- 使用空范围的漫水填充\n" "\t\t键盘按键【5】- 使用渐变、固定范围的漫水填充\n" "\t\t键盘按键【6】- 使用渐变、浮动范围的漫水填充\n" "\t\t键盘按键【7】- 操作标志符的低八位使用4位的连接模式\n" "\t\t键盘按键【8】- 操作标志符的低八位使用8位的连接模式\n" "\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n" ); } //-----------------------------------【onMouse( )函数】-------------------------------------- // 描述:鼠标消息onMouse回调函数 //--------------------------------------------------------------------------------------------- static void onMouse( int event, int x, int y, int, void* ) { // 若鼠标左键没有按下,便返回 if( event != CV_EVENT_LBUTTONDOWN ) return; //-------------------【<1>调用floodFill函数之前的参数准备部分】--------------- Point seed = Point(x,y); int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nLowDifference int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nUpDifference int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0。 //随机生成bgr值 int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值 int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值 int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值 Rect ccomp;//定义重绘区域的最小边界矩形区域 Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);//在重绘区域像素的新值,若是彩色图模式,取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, CV_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 ) { //改变console字体颜色 system("color 2F"); //载入原图 g_srcImage = imread("1.jpg", 1); if( !g_srcImage.data ) { printf("Oh,no,读取图片image0错误~! \n"); return false; } //显示帮助文字 ShowHelpText(); g_srcImage.copyTo(g_dstImage);//拷贝源图到目标图 cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转换三通道的image0到灰度图 g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);//利用image0的尺寸来初始化掩膜mask namedWindow( "效果图",CV_WINDOW_AUTOSIZE ); //创建Trackbar createTrackbar( "负差最大值", "效果图", &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 & 255) == 27 ) { cout << "程序退出...........\n"; break; } //根据按键的不同,进行各种操作 switch( (char)c ) { //如果键盘“1”被按下,效果图在在灰度图,彩色图之间互换 case '1': if( g_bIsColor )//若原来为彩色,转为灰度图,并且将掩膜mask所有元素设置为0 { cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n"; cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY); g_maskImage = Scalar::all(0); //将mask所有元素设置为0 g_bIsColor = false; //将标识符置为false,表示当前图像不为彩色,而是灰度 } else//若原来为灰度图,便将原来的彩图image0再次拷贝给image,并且将掩膜mask所有元素设置为0 { cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n"; g_srcImage.copyTo(g_dstImage); g_maskImage = Scalar::all(0); g_bIsColor = true;//将标识符置为true,表示当前图像模式为彩色 } break; //如果键盘按键“2”被按下,显示/隐藏掩膜窗口 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; //如果键盘按键“3”被按下,恢复原始图像 case '3': cout << "按键“3”被按下,恢复原始图像\n"; g_srcImage.copyTo(g_dstImage); cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY); g_maskImage = Scalar::all(0); break; //如果键盘按键“4”被按下,使用空范围的漫水填充 case '4': cout << "按键“4”被按下,使用空范围的漫水填充\n"; g_nFillMode = 0; break; //如果键盘按键“5”被按下,使用渐变、固定范围的漫水填充 case '5': cout << "按键“5”被按下,使用渐变、固定范围的漫水填充\n"; g_nFillMode = 1; break; //如果键盘按键“6”被按下,使用渐变、浮动范围的漫水填充 case '6': cout << "按键“6”被按下,使用渐变、浮动范围的漫水填充\n"; g_nFillMode = 2; break; //如果键盘按键“7”被按下,操作标志符的低八位使用4位的连接模式 case '7': cout << "按键“7”被按下,操作标志符的低八位使用4位的连接模式\n"; g_nConnectivity = 4; break; //如果键盘按键“8”被按下,操作标志符的低八位使用8位的连接模式 case '8': cout << "按键“8”被按下,操作标志符的低八位使用8位的连接模式\n"; g_nConnectivity = 8; break; } } return 0; }
————————————————
版权声明:本文为CSDN博主「南山二毛」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16481211/article/details/79633738

