接着,来看一个关于Floodfill的简单的调用范例:
//-----------------------------------【头文件包含部分】---------------------------------------// 描述:包含程序所依赖的头文件//----------------------------------------------------------------------------------------------#include <opencv2/opencv.hpp>#include <opencv2/imgproc/imgproc.hpp>//-----------------------------------【命名空间声明部分】---------------------------------------// 描述:包含程序所使用的命名空间//-----------------------------------------------------------------------------------------------using namespace cv;//-----------------------------------【main( )函数】--------------------------------------------// 描述:控制台应用程序的入口函数,我们的程序从这里开始//-----------------------------------------------------------------------------------------------int main( ){ Mat src = imread("1.jpg"); imshow("【原始图】",src); Rect ccomp; floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20)); imshow("【效果图】",src); waitKey(0); return 0;}
运行截图,原始图:

效果图:

五、floodFill函数在OpenCV中的实现源代码
这个部分贴出OpenCV中本文相关函数的源码实现细节,来给想了解实现细节的小伙伴们参考。浅墨暂时不在源码的细节上挖深作详细注释。
5.1 OpenCV2.X中两个版本的floodFill函数源码
第一个,不带mask版本的floodFill:
int cv::floodFill( InputOutputArray _image,Point seedPoint,Scalar newVal, Rect* rect,Scalar loDiff, ScalarupDiff, int flags ){CvConnectedComp ccomp;CvMat c_image = _image.getMat();cvFloodFill(&c_image, seedPoint, newVal, loDiff, upDiff, &ccomp,flags, 0);if( rect )*rect = ccomp.rect;return cvRound(ccomp.area);}
第二个,带mask版本的floodFill:
int cv::floodFill( InputOutputArray _image,InputOutputArray _mask,Point seedPoint, ScalarnewVal, Rect* rect,Scalar loDiff, ScalarupDiff, int flags ){CvConnectedComp ccomp;CvMat c_image = _image.getMat(), c_mask = _mask.getMat();cvFloodFill(&c_image, seedPoint, newVal,loDiff, upDiff, &ccomp, flags, c_mask.data.ptr ? &c_mask : 0);if( rect )*rect = ccomp.rect;return cvRound(ccomp.area);}
我们依然可以发现,其内部实现是基于OpenCV 1.X旧版的cvFloodFill函数,我们再来看看其旧版函数的源码。
5.2 OpenCV2.X中cvFloodFill()函数的实现源码
CV_IMPL voidcvFloodFill( CvArr* arr, CvPointseed_point,CvScalar newVal, CvScalar lo_diff, CvScalar up_diff,CvConnectedComp* comp, int flags, CvArr* maskarr ){cv::Ptr<CvMat> tempMask;std::vector<CvFFillSegment> buffer;if( comp )memset( comp, 0, sizeof(*comp) );int i, type, depth, cn, is_simple;int buffer_size, 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 { cv::Vec3b b; cv::Vec3i i; cv::Vec3f f; } ld_buf, ud_buf;CvMat stub, *img = cvGetMat(arr, &stub);CvMat maskstub, *mask = (CvMat*)maskarr;CvSize size;type = CV_MAT_TYPE( img->type );depth = CV_MAT_DEPTH(type);cn = CV_MAT_CN(type);if( connectivity == 0 )connectivity = 4;else if( connectivity != 4 && connectivity != 8 )CV_Error( CV_StsBadFlag, "Connectivity must be 4, 0(=4) or 8");is_simple = mask == 0 && (flags & CV_FLOODFILL_MASK_ONLY) ==0;for( i = 0; i < cn; i++ ){if( lo_diff.val[i] < 0 || up_diff.val[i] < 0 )CV_Error( CV_StsBadArg, "lo_diff and up_diff must benon-negative" );is_simple &= fabs(lo_diff.val[i]) < DBL_EPSILON &&fabs(up_diff.val[i]) < DBL_EPSILON;}size = cvGetMatSize( img );if( (unsigned)seed_point.x >= (unsigned)size.width ||(unsigned)seed_point.y >=(unsigned)size.height )CV_Error( CV_StsOutOfRange, "Seed point is outside of image");cvScalarToRawData( &newVal, &nv_buf, type, 0 );buffer_size = MAX( size.width, size.height ) * 2;buffer.resize( buffer_size );if( is_simple ){int elem_size = CV_ELEM_SIZE(type);const uchar* seed_ptr = img->data.ptr + img->step*seed_point.y +elem_size*seed_point.x;for(i = 0; i < elem_size; i++)if (seed_ptr[i] != nv_buf.b[i])break;if (i != elem_size){if( type == CV_8UC1 )icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.b[0],comp, flags, &buffer);else if( type == CV_8UC3 )icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3b(nv_buf.b),comp, flags,&buffer);else if( type == CV_32SC1 )icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.i[0],comp, flags,&buffer);else if( type == CV_32FC1 )icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.f[0],comp, flags,&buffer);else if( type == CV_32SC3 )icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3i(nv_buf.i),comp, flags, &buffer);else if( type == CV_32FC3 )icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3f(nv_buf.f),comp, flags,&buffer);elseCV_Error(CV_StsUnsupportedFormat, "" );return;}}if( !mask ){/* created mask will be 8-byte aligned */tempMask = cvCreateMat( size.height + 2, (size.width + 9) & -8,CV_8UC1 );mask = tempMask;}else{mask = cvGetMat( mask, &maskstub );if( !CV_IS_MASK_ARR( mask ))CV_Error( CV_StsBadMask, "" );if( mask->width != size.width + 2 || mask->height != size.height +2 )CV_Error( CV_StsUnmatchedSizes, "mask must be 2 pixel wider ""and 2pixel taller than filled image" );}int width = tempMask ? mask->step : size.width + 2;uchar* mask_row = mask->data.ptr + mask->step;memset(mask_row - mask->step, 1, width );for( i = 1; i <= size.height; i++, mask_row += mask->step ){if( tempMask )memset( mask_row, 0, width );mask_row[0] = mask_row[size.width+1] = (uchar)1;}memset( mask_row, 1, width );if( depth == CV_8U )for( i = 0; i < cn; i++ ){int t = cvFloor(lo_diff.val[i]);ld_buf.b[i] = CV_CAST_8U(t);t = cvFloor(up_diff.val[i]);ud_buf.b[i] = CV_CAST_8U(t);}else if( depth == CV_32S )for( i = 0; i < cn; i++ ){int t = cvFloor(lo_diff.val[i]);ld_buf.i[i] = t;t = cvFloor(up_diff.val[i]);ud_buf.i[i] = t;}else if( depth == CV_32F )for( i = 0; i < cn; i++ ){ld_buf.f[i] = (float)lo_diff.val[i];ud_buf.f[i] = (float)up_diff.val[i];}elseCV_Error( CV_StsUnsupportedFormat, "" );if( type == CV_8UC1 )icvFloodFillGrad_CnIR<uchar, int, Diff8uC1>(img->data.ptr,img->step, mask->data.ptr, mask->step,size, seed_point,nv_buf.b[0],Diff8uC1(ld_buf.b[0],ud_buf.b[0]),comp, flags,&buffer);else if( type == CV_8UC3 )icvFloodFillGrad_CnIR<cv::Vec3b, cv::Vec3i, Diff8uC3>(img->data.ptr,img->step, mask->data.ptr, mask->step,size, seed_point,cv::Vec3b(nv_buf.b),Diff8uC3(ld_buf.b, ud_buf.b),comp, flags,&buffer);else if( type == CV_32SC1 )icvFloodFillGrad_CnIR<int, int, Diff32sC1>(img->data.ptr,img->step, mask->data.ptr, mask->step,size, seed_point,nv_buf.i[0],Diff32sC1(ld_buf.i[0], ud_buf.i[0]),comp, flags, &buffer);else if( type == CV_32SC3 )icvFloodFillGrad_CnIR<cv::Vec3i, cv::Vec3i, Diff32sC3>(img->data.ptr,img->step, mask->data.ptr, mask->step,size, seed_point,cv::Vec3i(nv_buf.i),Diff32sC3(ld_buf.i, ud_buf.i),comp, flags,&buffer);else if( type == CV_32FC1 )icvFloodFillGrad_CnIR<float, float, Diff32fC1>(img->data.ptr,img->step, mask->data.ptr, mask->step,size, seed_point,nv_buf.f[0],Diff32fC1(ld_buf.f[0], ud_buf.f[0]),comp, flags,&buffer);else if( type == CV_32FC3 )icvFloodFillGrad_CnIR<cv::Vec3f, cv::Vec3f, Diff32fC3>(img->data.ptr,img->step, mask->data.ptr, mask->step,size, seed_point,cv::Vec3f(nv_buf.f),Diff32fC3(ld_buf.f,ud_buf.f),comp, flags,&buffer);elseCV_Error(CV_StsUnsupportedFormat, "");}
四、关于SetMouseCallback函数
因为下面示例程序中有用到SetMouseCallback函数,我们在这里讲一讲。SetMouseCallback函数为指定的窗口设置鼠标回调函数。
C++: void setMouseCallback(conststring& winname, MouseCallback onMouse, void* userdata=0 )
第一个参数,const string&类型的winname,为窗口的名字。
第二个参数,MouseCallback类型的onMouse,指定窗口里每次鼠标时间发生的时候,被调用的函数指针。这个函数的原型应该为voidFoo(int event, int x, int y, int flags, void* param);其中event是 CV_EVENT_*变量之一, x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系), flags是CV_EVENT_FLAG的组合, param是用户定义的传递到cvSetMouseCallback函数调用的参数。
第三个参数,void*类型的userdata,用户定义的传递到回调函数的参数,有默认值0。
五、综合示例部分
本次的综合示例为OpenCV文档中自带的一个程序。浅墨将其做了适当的修改并详细注释,放出来供大家消化理解。
操作说明如下:

可以看到,此程序着不少的按键功能。而我们拿着鼠标对窗口中的图形一顿狂点,就可以得到类似PhotoShop中魔棒的效果,当然,就这短短的两百来行代码写出来的东西,体验是比不上PS的魔棒工具的。
废话不多说,程序详细注释的源码如下:
//-----------------------------------【程序说明】----------------------------------------------// 程序名称::《【OpenCV入门教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)》 博文配套源码// 开发所用IDE版本:Visual Studio 2010// 开发所用OpenCV版本: 2.4.9// 2014年6月3日 Created by 浅墨// 浅墨的微博:@浅墨_毛星云 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;}
一些运行截图,首先是运行后的原始图:

点鼠标啊点鼠标:

滑滚动条啊滑滚动条:

地球已经阻止不了我们的鼠标了,点出来的图,已经有点恐怖。。。。。看妹子的手。。。。

如果鼠标点到妹子脸上的话。。。。呃,更多惊悚的图,浅墨不放出了,免得。。。。。
随着我们鼠标的点击,程序会记下我们的操作:

接着看一张灰度图模式的漫水填充效果和掩码图:

再来一张彩色窗户:

程序功能还是很多的,有鼠标操作,键盘8个按键的操作,还可以调滚动条:

好了,更多的功能我们就不在这里示范了,大家下载了程序自己回去玩吧。
本篇文章的配套源代码请点击这里下载:
【浅墨OpenCV入门教程之十五】配套源代码下载