本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/28261997
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
知乎:http://www.zhihu.com/people/mao-xing-yun
邮箱: [email protected]
写作当前博文时配套使用的OpenCV版本: 2.4.9
本篇文章中,我们一起探讨了OpenCV填充算法中漫水填充算法相关的知识点,以及了解了OpenCV中实现漫水填充算法的两个版本的floodFill函数的使用方法。此篇博文一共有两个个配套的示例程序,其详细注释过的代码都在文中贴出,且文章最后提供了综合示例程序的下载。
先尝鲜一下最终示例程序的运行截图吧,这个示例程序实现了类似于PhotoShop中魔棒的图像处理效果,我们可以用鼠标对窗口中的图片一顿狂点,得到一片一片的彩色区域:
效果图 原始图
一、引言 · 漫水填充的定义
漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
另外,floodfill官方译作“漫水填充”,但是浅墨总是喜欢说成“水漫填充”,因为受从小到大深入骨髓的“水漫金山”这个成语的影响,囧。
二、漫水填充法的基本思想
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
三、floodFill函数详解
在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。
第一个版本的floodFill:
- int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
第二个版本的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,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
- 低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
- 高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
- FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
- FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
- 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:
- flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
接着,来看一个关于Floodfill的简单的调用范例:
-
-
-
- #include <opencv2/opencv.hpp>
- #include <opencv2/imgproc/imgproc.hpp>
-
-
-
-
- using namespace cv;
-
-
-
-
- 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 void
- cvFloodFill( 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);
- else
- CV_Error(CV_StsUnsupportedFormat, "" );
- return;
- }
- }
-
- if( !mask )
- {
-
- 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];
- }
- else
- CV_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);
- else
- CV_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的魔棒工具的。
废话不多说,程序详细注释的源码如下:
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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;
- int g_bIsColor = true;
- bool g_bUseMask = false;
- int g_nNewMaskVal = 255;
-
-
-
-
-
- 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"
- );
- }
-
-
-
-
-
- static void onMouse( int event, int x, int y, int, void* )
- {
-
- if( event != CV_EVENT_LBUTTONDOWN )
- return;
-
-
- Point seed = Point(x,y);
- int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;
- int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;
- int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
- (g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);
-
-
- int b = (unsigned)theRNG() & 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;
-
-
- 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";
- }
-
-
-
-
-
- int main( int argc, char** argv )
- {
-
- 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);
- g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);
-
- namedWindow( "效果图",CV_WINDOW_AUTOSIZE );
-
-
- 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);
-
- if( (c & 255) == 27 )
- {
- cout << "程序退出...........\n";
- break;
- }
-
-
- switch( (char)c )
- {
-
- case '1':
- if( g_bIsColor )
- {
- cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
- cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
- g_maskImage = Scalar::all(0);
- g_bIsColor = false;
- }
- else
- {
- cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
- g_srcImage.copyTo(g_dstImage);
- g_maskImage = Scalar::all(0);
- g_bIsColor = true;
- }
- break;
-
- 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':
- cout << "按键“3”被按下,恢复原始图像\n";
- g_srcImage.copyTo(g_dstImage);
- cvtColor(g_dstImage, 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;
- }
一些运行截图,首先是运行后的原始图:
点鼠标啊点鼠标:
滑滚动条啊滑滚动条:
地球已经阻止不了我们的鼠标了,点出来的图,已经有点恐怖。。。。。看妹子的手。。。。
如果鼠标点到妹子脸上的话。。。。呃,更多惊悚的图,浅墨不放出了,免得。。。。。
随着我们鼠标的点击,程序会记下我们的操作:
接着看一张灰度图模式的漫水填充效果和掩码图:
再来一张彩色窗户:
程序功能还是很多的,有鼠标操作,键盘8个按键的操作,还可以调滚动条:
好了,更多的功能我们就不在这里示范了,大家下载了程序自己回去玩吧。
本篇文章的配套源代码请点击这里下载:
【浅墨OpenCV入门教程之十五】配套源代码下载
本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/28261997
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
知乎:http://www.zhihu.com/people/mao-xing-yun
邮箱: [email protected]
写作当前博文时配套使用的OpenCV版本: 2.4.9
本篇文章中,我们一起探讨了OpenCV填充算法中漫水填充算法相关的知识点,以及了解了OpenCV中实现漫水填充算法的两个版本的floodFill函数的使用方法。此篇博文一共有两个个配套的示例程序,其详细注释过的代码都在文中贴出,且文章最后提供了综合示例程序的下载。
先尝鲜一下最终示例程序的运行截图吧,这个示例程序实现了类似于PhotoShop中魔棒的图像处理效果,我们可以用鼠标对窗口中的图片一顿狂点,得到一片一片的彩色区域:
效果图 原始图
一、引言 · 漫水填充的定义
漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
另外,floodfill官方译作“漫水填充”,但是浅墨总是喜欢说成“水漫填充”,因为受从小到大深入骨髓的“水漫金山”这个成语的影响,囧。
二、漫水填充法的基本思想
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
三、floodFill函数详解
在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。
第一个版本的floodFill:
- int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
第二个版本的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,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
- 低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
- 高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
- FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
- FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
- 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:
- flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
接着,来看一个关于Floodfill的简单的调用范例:
-
-
-
- #include <opencv2/opencv.hpp>
- #include <opencv2/imgproc/imgproc.hpp>
-
-
-
-
- using namespace cv;
-
-
-
-
- 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 void
- cvFloodFill( 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);
- else
- CV_Error(CV_StsUnsupportedFormat, "" );
- return;
- }
- }
-
- if( !mask )
- {
-
- 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];
- }
- else
- CV_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);
- else
- CV_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的魔棒工具的。
废话不多说,程序详细注释的源码如下:
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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;
- int g_bIsColor = true;
- bool g_bUseMask = false;
- int g_nNewMaskVal = 255;
-
-
-
-
-
- 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"
- );
- }
-
-
-
-
-
- static void onMouse( int event, int x, int y, int, void* )
- {
-
- if( event != CV_EVENT_LBUTTONDOWN )
- return;
-
-
- Point seed = Point(x,y);
- int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;
- int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;
- int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
- (g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);
-
-
- int b = (unsigned)theRNG() & 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;
-
-
- 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";
- }
-
-
-
-
-
- int main( int argc, char** argv )
- {
-
- 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);
- g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);
-
- namedWindow( "效果图",CV_WINDOW_AUTOSIZE );
-
-
- 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);
-
- if( (c & 255) == 27 )
- {
- cout << "程序退出...........\n";
- break;
- }
-
-
- switch( (char)c )
- {
-
- case '1':
- if( g_bIsColor )
- {
- cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
- cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
- g_maskImage = Scalar::all(0);
- g_bIsColor = false;
- }
- else
- {
- cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
- g_srcImage.copyTo(g_dstImage);
- g_maskImage = Scalar::all(0);
- g_bIsColor = true;
- }
- break;
-
- 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':
- cout << "按键“3”被按下,恢复原始图像\n";
- g_srcImage.copyTo(g_dstImage);
- cvtColor(g_dstImage, 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;
- }
一些运行截图,首先是运行后的原始图:
点鼠标啊点鼠标:
滑滚动条啊滑滚动条:
地球已经阻止不了我们的鼠标了,点出来的图,已经有点恐怖。。。。。看妹子的手。。。。
如果鼠标点到妹子脸上的话。。。。呃,更多惊悚的图,浅墨不放出了,免得。。。。。
随着我们鼠标的点击,程序会记下我们的操作:
接着看一张灰度图模式的漫水填充效果和掩码图:
再来一张彩色窗户:
程序功能还是很多的,有鼠标操作,键盘8个按键的操作,还可以调滚动条:
好了,更多的功能我们就不在这里示范了,大家下载了程序自己回去玩吧。
本篇文章的配套源代码请点击这里下载:
【浅墨OpenCV入门教程之十五】配套源代码下载