opencv floodFill 漫水填充

来源:互联网 发布:淘宝基金理财怎么样 编辑:程序博客网 时间:2024/04/30 05:01
所谓的floodFill 漫水填充就是在一张图片中,和种子点像素相差在[-loDiff,+upDiff]的时候就用newVal来填充这个点。

在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。

int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 ) 


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)。
  • 补充自己的理解说明:mask当为非0的时候,就不会填充
  • 第三个参数,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设置了的时候,原图不会改变,只会用中间八位的值填冲mask

  • 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
下面是代码演示图:
#include "opencv2/imgproc/imgproc.hpp"  #include "opencv2/highgui/highgui.hpp"  #include <iostream>  using namespace cv;  using namespace std;  int loDiff = 20, upDiff = 20;  Mat image0, image, mask,dst;  int newMaskVal = 155; static void onMouse( int event, int x, int y, int, void* ){  // 若鼠标左键没有按下,便返回  if( event != CV_EVENT_LBUTTONDOWN )  return;  dst = image;  Point seed = Point(x,y);  Scalar newVal = Scalar(0, 0, 255);  Rect ccomp;  int lo = loDiff;  int up = upDiff;      int flags = 8 + (newMaskVal << 8) +  CV_FLOODFILL_FIXED_RANGE ;//+ CV_FLOODFILL_MASK_ONLY;//int flags = 8 + (newMaskVal << 8) + CV_FLOODFILL_FIXED_RANGE;  cout<<floodFill(dst, mask, seed, newVal, &ccomp, Scalar(lo, lo, lo), Scalar(up, up, up), flags)<<" 个点\n";//int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值  //int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值  //int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值  //rectangle(dst,ccomp,Scalar(b,g,r),2);imshow("22", mask); imshow("image", dst);  }  int main( int argc, char** argv ){  char* filename = "C:\\Users\\Administrator\\Desktop\\工作\\testp\\3.jpg";//argv[1];  image0 = imread(filename, 1);  image0.copyTo(image);  mask.create(image0.rows+2, image0.cols+2, CV_8UC1);  mask=Scalar::all(0);for(int i=0;i<60;i++)for(int j=0;j<60;j++){mask.at<uchar>(i,j)=255;} namedWindow( "image",CV_WINDOW_AUTOSIZE);  createTrackbar( "lo_diff", "image", &loDiff, 255, 0 );  createTrackbar( "up_diff", "image", &upDiff, 255, 0 );  setMouseCallback("image", onMouse, 0 );      //只是登记回调函数//imshow("22", mask);    //如果mask在这里显示就会是原图  需要放到回调函数里才能显示每次的//imwrite("mask.jpg",mask);waitKey(0);  return 0;  }  


从这里可以看到mask掩膜左上角我置为255,在对应的图中就不变化了,即不改变。也就是说掩膜非0就不变化。 上面的 int flags = 8 + (newMaskVal << 8) +  CV_FLOODFILL_FIXED_RANGE ;  没有设置CV_FLOODFILL_MASK_ONLY,但是掩膜同样的也有操作显示,说明一般情况下二者是同时有的,只是当设置了CV_FLOODFILL_MASK_ONLY,根据only也可以看出就只有在淹膜上显示,原图上不变。还有就是我点一个像素点好多次,就只有第一次会有反应,也就是说当点第一次的时候就更新了像素点,同时在掩膜上也记录了使其为newmaskValue,非0,再操作的话就没反应了,这就是掩膜的一大作用。

还有再谈谈Rect*类型的rect。下面是代码演示:
int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值  int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值  int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值 rectangle(dst,ccomp,Scalar(b,g,r),2);
只是把上面代码注释去掉就ok了,首先说下基础的RNg()&255一开始我认为这里写错了,因为一般情况下我看到的都是%255,这很好理解,就是不得大于255呗,值的范围是0~255,但是现在这个,问小庞,他一看就知道说oxff。嗯,原来如此,我用计算器看了下,255的二进制是1111,1111. 唉,好基础。。。只保留低八位,高八位为0. 下面是运行截图:

可以看出是外接矩形。
还有最后floodfill返回的是每次填充的area像素点个数。

还有就是那个回调函数啊,一开始我把
setMouseCallback("image", onMouse, 0 );  
imshow("22", mask);  
掩膜的显示放在setMousecallback后面,发现怎么就是显示原来的图,没变化,后来问小曹,小曹知道回调函数,但是就是他不知道怎么讲,然后打断点调试运行,我才看到是顺序执行到waitkey那里的,也就是说一开始就执行到最后了,然后等待左键按下,才显示。要是直接放在setMousecallback后面显示掩膜就是刚开始顺序执行的时候执行了一次,然后不会在执行。但是放在回调函数里面,每次触发鼠标左键的时候就会刷新一次。。

下面是回调函数知乎上面有人的解释:
 

什么是回调函数?

我们绕点远路来回答这个问题。

编程分为两类:系统编程(system programming)和应用编程(application programming)。所谓系统编程,简单来说,就是编写;而应用编程就是利用写好的各种库来编写具某种功用的程序,也就是应用。系统程序员会给自己写的库留下一些接口,即API(application programming interface,应用编程接口),以供应用程序员使用。所以在抽象层的图示里,库位于应用的底下。

当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。

打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。如下图所示(图片来源:维基百科):


可以看到,回调函数通常和应用处于同一抽象层(因为传入什么样的回调函数是在应用级别决定的)。而回调就成了一个高层调用底层,底层再过头来用高层的过程。(我认为)这应该是回调最早的应用之处,也是其得名如此的原因。

回调机制的优势

从上面的例子可以看出,回调机制提供了非常大的灵活性。请注意,从现在开始,我们把图中的库函数改称为中间函数了,这是因为回调并不仅仅用在应用和库之间。任何时候,只要想获得类似于上面情况的灵活性,都可以利用回调。

这种灵活性是怎么实现的呢?乍看起来,回调似乎只是函数间的调用,但仔细一琢磨,可以发现两者之间的一个关键的不同:在回调中,我们利用某种方式,把回调函数像参数一样传入中间函数。可以这么理解,在传入一个回调函数之前,中间函数是不完整的。换句话说,程序可以在运行时,通过登记不同的回调函数,来决定、改变中间函数的行为。这就比简单的函数调用要灵活太多了。


   





1 0
原创粉丝点击