尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenCV计算机视觉开发入门与实践<三十>:漫水填充(floodFill)算法分割图像

OpenCV计算机视觉开发入门与实践<三十>:漫水填充(floodFill)算法分割图像 摘要本文详细介绍了 OpenCV 中 Flood Fill漫水填充算法的原理与实战用法。文章首先解释了漫水填充的基本概念即从种子点出发将颜色相近的连通区域填充为指定颜色并对比了 4 邻域、8 邻域等常见实现方式。随后重点剖析了 OpenCV 中floodFill函数的两个重载版本逐一讲解了image、mask、seedPoint、newVal、loDiff、upDiff、flags等核心参数的含义与注意事项特别是flags中连通性、掩码值及FLOODFILL_FIXED_RANGE、FLOODFILL_MASK_ONLY等标识符的组合用法。最后通过两个完整的 C 示例例 9.7 与例 9.8演示了如何利用floodFill实现图像分割以及如何结合鼠标交互与滑动条构建功能更强大的分割工具。1. 基本概念Flood Fill漫水填充算法是一种在许多图形绘制软件中常用的填充算法。通常情况下该算法会自动选中与种子像素相连的区域并利用指定颜色对该区域进行填充。这个算法常用于标记或分离图像的特定部分以便进行进一步的分析和处理。Windows画图工具中的油漆桶功能和Photoshop的魔术棒选择工具都是Flood Fill算法的改进和延伸。漫水填充算法的原理很简单就是从一个点开始遍历附近的像素点并填充成新的颜色直到封闭区域内所有像素点都被填充成新颜色为止。floodFill的实现方法常见的有4邻域像素填充法、8邻域像素填充法、基于扫描线的像素填充方法等。在OpenCV中漫水填充是填充算法中最通用的方法。使用C重写过的floodFill函数有两个版本一个是不带mask的版本另一个是带mask的版本。这个mask就是用于进一步控制哪些区域将被填充颜色比如对同一图像进行多次填充时。这两个版本的floodFill都必须在图像中选择一个种子点然后把临近区域所有相似点填充上同样的颜色不同之处在于不一定将所有的邻近像素点都染上同一颜色。漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围从loDiff到upDiff内或在原始seedPoint像素值范围内时floodFill函数就会为这个点涂上颜色。2.floodFill函数在OpenCV中漫水填充算法由floodFill函数实现其作用是用指定的颜色从种子点开始填充一个连接域连通性由像素值的接近程度来衡量。OpenCV中使用C重写的两个floodFill函数的声明如下intcv::floodFill(InputOutputArray image,Point seedPoint,Scalar newVal,Rect*rect0,Scalar loDiffScalar(),Scalar upDiffScalar(),intflags4);intcv::floodFill(InputOutputArray image,InputOutputArray mask,Point seedPoint,Scalar newVal,Rect*rect0,Scalar loDiffScalar(),Scalar upDiffScalar(),intflags4);这两个函数除了第二个参数外其他的参数都是共用的。其中参数image是一个输入输出参数表示一通道或三通道、8位或浮点图像具体取值由之后的参数指明。参数mask也是输入输出参数是第二个版本的floodFill独享的表示操作掩码它应该为单通道、8位、长和宽上都比输入图像image大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩码所以这个mask参数我们一定要准备好并填在此处。需要注意的是漫水填充不会填充mask的非零像素区域。例如一个边缘检测算子的输出可以用来作为掩码以防止填充到边缘。同样地也可以在多次的函数调用中使用同一个掩码以保证填充的区域不会重叠。另外需要注意的是mask会比需填充的图像大所以mask中与输入图像(x,y)像素点相对应的点的坐标为(x1,y1)。参数seedPoint表示漫水填充算法的起始点。参数Scalar类型的newVal表示像素点被染色的值即像素在重绘区域的新值。参数rect的有默认值为0一个可选的参数用于设置floodFill函数将要重绘区域的最小边界矩形区域参数loDiff的默认值为Scalar()表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差lower brightness/color difference的最大值。参数upDiff的默认值为Scalar()表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差lower brightness/color difference的最大值。参数flags表示操作标志符此参数包含3个部分1低八位部分第0~7位用于控制算法的连通性可取44为默认值或者8。如果设为4表示填充算法只考虑当前像素水平方向和垂直方向的相邻点如果设为8除上述相邻点外还会包含对角线方向的相邻点。2高八位部分16~23位可以为0或者如下两种选项标识符的组合FLOODFILL_FIXED_RANGE如果设置为这个标识符就会考虑当前像素与种子像素之间的差否则就考虑当前像素与其相邻像素的差。也就是说这个范围是浮动的。FLOODFILL_MASK_ONLY如果设置为这个标识符函数不会去填充改变原始图像也就是忽略第三个参数newVal而是去填充掩码图像mask。这个标识符只对第二个版本的floodFill有用因为第一个版本里面没有mask参数。3中间八位部分8~15位用于指定填充掩码图像的值。如果flags中间八位的值为0则掩码会用1来填充。而所有flags可以用or操作符即“|”连接起来。例如如果想用8邻域填充并填充固定像素值范围即填充掩码而不是填充原图像以及设填充值为38那么输入的参数是这样的flags8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | 388。下面来看一个关于floodfill的简单的调用范例。【例9.7】利用floodfill进行图像分割#includeopencv2/imgproc.hpp#includeopencv2/highgui.hpp#includeopencv2/imgproc/types_c.h#includeiostreamusingnamespacestd;usingnamespacecv;intmain(){Mat srcimread(mysky.jpg);imshow(src,src);Rect ccomp;floodFill(src,Point(50,300),Scalar(155,255,55),ccomp,Scalar(20,20,20),Scalar(20,20,20));imshow(res,src);waitKey(0);return0;}下面再来看一个综合例子它可以根据用户鼠标的单击和滑竿调节来实现不同区域的图像分割。【例9.8】功能更强大的floodFill分割#includeopencv2/imgproc.hpp#includeopencv2/highgui.hpp#includeopencv2/imgproc/types_c.h#includeiostreamusingnamespacestd;usingnamespacecv;//全局变量声明部分Mat g_srcImage,g_dstImage,g_grayImage,g_maskImage;//定义原始图、目标图、灰度图、掩膜图intg_nFillMode1;//漫水填充的模式intg_nLowDifference20,g_nUpDifference20;//负差最大值正差最大值intg_nConnectivity4;//表示floodFill函数标识符低八位的连通值boolg_bIsColortrue;//是否为彩色图的标识符布尔值boolg_bUseMaskfalse;//是否显示掩膜窗口的布尔值intg_nNewMaskVal255;//新的重新绘制的像素值//【onMouse()函数】staticvoidonMouse(intevent,intx,inty,int,void*){//若鼠标左键没有按下便返回if(event!EVENT_LBUTTONDOWN)return;//-----------------【1调用floodFill函数之前的参数准备部分】-------------Point seedPoint(x,y);intLowDifferenceg_nFillMode0?0:g_nLowDifference;intUpDifferenceg_nFillMode0?0:g_nUpDifference;//标识符的0~7位为g_nConnectivity8~15位为g_nNewMaskVal左移8位的值16~23位为CV_FLOODFILL_FIXED_RANGE或者0intflagsg_nConnectivity(g_nNewMaskVal8)(g_nFillMode1?FLOODFILL_FIXED_RANGE:0);//随机生成BGR值intb(unsigned)theRNG()255;//随机返回一个0~255之间的值intg(unsigned)theRNG()255;intr(unsigned)theRNG()255;Rect ccomp;//定义重绘区域的最小边界矩阵区域Scalar newValg_bIsColor?Scalar(b,g,r):Scalar(r*0.299g*0.587b*0.114);Mat dstg_bIsColor?g_dstImage:g_grayImage;//目标图的赋值intarea;//---------------------【2正式调用floodFill函数】------------------if(g_bUseMask){threshold(g_maskImage,g_maskImage,1,128,THRESH_BINARY);areafloodFill(dst,g_maskImage,seed,newVal,ccomp,Scalar(LowDifference,LowDifference,LowDifference),Scalar(UpDifference,UpDifference,UpDifference),flags);imshow(mask,g_maskImage);}else{areafloodFill(dst,seed,newVal,ccomp,Scalar(LowDifference,LowDifference,LowDifference),Scalar(UpDifference,UpDifference,UpDifference),flags);}imshow(res,dst);//qDebug() area 个像素被重新绘制\n;}//main()函数intmain(intargc,char**argv){cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);//载入原图g_srcImageimread(study.jpg,1);if(!g_srcImage.data){printf(Fail to open file.\n);returnfalse;}g_srcImage.copyTo(g_dstImage);//复制原图到目标图cvtColor(g_srcImage,g_grayImage,COLOR_BGR2GRAY);//转为灰度图到g_grayImageg_maskImage.create(g_srcImage.rows2,g_srcImage.cols2,CV_8UC1);//用原图尺寸初始化掩膜masknamedWindow(res,WINDOW_AUTOSIZE);//创建TrackbarcreateTrackbar(负差最大值,res,g_nLowDifference,255,0);createTrackbar(正差最大值,res,g_nUpDifference,255,0);//鼠标回调函数setMouseCallback(res,onMouse,0);//循环轮询按键while(1){//先显示效果图imshow(res,g_bIsColor?g_dstImage:g_grayImage);//获取按键键盘intcwaitKey(0);//判断ESC是否按下按下退出if(c27){coutgame over.\n;break;}//根据按键不同进行不同的操作switch((char)c){//如果键盘1被按下效果图在灰度图和彩色图之间转换case1:if(g_bIsColor){//若原来为彩色图转换为灰度图并将掩膜mask所有元素设置为0//qDebug() 键盘‘1’按下切换彩色/灰度模式当前操作将【彩色模式】切换为【灰度模式】;cvtColor(g_srcImage,g_grayImage,COLOR_BGR2GRAY);g_maskImageScalar::all(0);//将mask所有元素设置为0g_bIsColorfalse;}else{//qDebug() 键盘‘1’按下切换彩色/灰度模式当前操作将【灰度模式】切换为【彩色模式】;g_srcImage.copyTo(g_dstImage);g_maskImageScalar::all(0);g_bIsColortrue;}case2:if(g_bUseMask){destroyWindow(mask);g_bUseMaskfalse;}else{namedWindow(mask,0);g_maskImageScalar::all(0);imshow(mask,g_maskImage);g_bUseMasktrue;}break;case3://如果键盘3被按下恢复原始图像//qDebug() 按下键盘‘3’恢复原始图像\n;g_srcImage.copyTo(g_dstImage);cvtColor(g_srcImage,g_grayImage,COLOR_BGR2GRAY);g_maskImageScalar::all(0);break;case4://qDebug() 键盘‘4’被按下使用空范围的漫水填充\n;g_nFillMode0;break;case5://qDebug() 键盘‘5’被按下使用渐变、固定范围的漫水填充\n;g_nFillMode1;break;case6://qDebug() 键盘‘6’被按下使用渐变、浮动范围的漫水填充\n;g_nFillMode2;break;case7://qDebug() 键盘‘7’被按下操作标识符的低八位使用4位的连接模式\n;g_nConnectivity4;break;case8://qDebug() 键盘‘8’被按下操作标识符的低八位使用8为的连接模式\n;g_nConnectivity8;break;}}return0;}
返回列表