【OpenCV新手教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)
本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/28261997
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
知乎:http://www.zhihu.com/people/mao-xing-yun
写作当前博文时配套使用的OpenCV版本号: 2.4.9
本篇文章中。我们一起探讨了OpenCV填充算法中漫水填充算法相关的知识点,以及了解了OpenCV中实现漫水填充算法的两个版本号的floodFill函数的用法。此篇博文一共同拥有两个个配套的演示样例程序,其具体凝视过的代码都在文中贴出。且文章最后提供了综合演示样例程序的下载。
先尝鲜一下终于演示样例程序的执行截图吧,这个演示样例程序实现了相似于PhotoShop中魔棒的图像处理效果,我们能够用鼠标对窗体中的图片一顿狂点。得到一片一片的彩色区域:
![]()
效果图 原始图
一、引言 · 漫水填充的定义
漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充常常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也能够用来从输入图像获取掩码区域,掩码会加速处理过程。或仅仅处理掩码指定的像素点,操作的结果总是某个连续的区域。
另外,floodfill官方译作“漫水填充”,可是浅墨总是喜欢说成“水漫填充”,由于受从小到大深入骨髓的“水漫金山”这个成语的影响。囧。
二、漫水填充法的基本思想
所谓漫水填充,简单来说,就是自己主动选中了和种子点相连的区域。接着将该区域替换成指定的颜色。这是个非常实用的功能,常常常使用来标记或者分离图像的一部分进行处理或分析.漫水填充也能够用来从输入图像获取掩码区域,掩码会加速处理过程,或者仅仅处理掩码指定的像素点.
以此填充算法为基础,相似photoshop的魔术棒选择工具就非常easy实现了。
漫水填充(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; //-----------------------------------【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 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 ) { /* 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]; } 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的魔棒工具的。
废话不多说,程序具体凝视的源代码例如以下:
//-----------------------------------【程序说明】---------------------------------------------- // 程序名称::《【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个按键的操作,还能够调滚动栏:
好了,很多其它的功能我们就不在这里示范了。大家下载了程序自己回去玩吧。
本篇文章的配套源代码请点击这里下载: