漫水填充算法,是根据选定的种子点,用一种自定义的颜色填充种子点的联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果。
漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析。
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色。
漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点。
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。
OpenCV中的函数原型如下:
int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
int floodFill(InputOutputArray image, 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会比需填充的图像大2个像素,所以 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,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
a。低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
b。
中间八位
部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
c。
高八位
部分(16~23位)可以为0 或者如下两种选项标识符的组合:
FLOODFILL_FIXED_RANGE
- 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为47,那么输入的参数是这样:
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (47<<8)
Mat srcImage=imread("M:/图像处理实验/floodFill/test_.bmp"); Rect ccomp; floodFill(srcImage, Point(1, 1), CV_RGB(205, 205, 205), &ccomp, Scalar(15, 15, 15), Scalar(15, 15, 15), 8 | FLOODFILL_FIXED_RANGE ); imwrite("M:/图像处理实验/floodFill/test_dst.bmp", srcImage);种子点为(1,1)。下面为原图与漫水填充后的结果对比:
eg。
Mat srcImage=imread("M:/图像处理实验/floodFill/云.bmp"); Mat mask; mask.create(((srcImage).rows + 2), ((srcImage).cols + 2), CV_8UC1); mask = Scalar::all(0); Mat ImageROI; ImageROI = mask(Rect(1, 1, (srcImage).cols, (srcImage).rows)); Mat dstImage; Mat dstImage_canny; srcImage.copyTo(dstImage_canny); cvtColor(dstImage_canny, dstImage_canny, CV_RGB2GRAY); medianBlur(dstImage_canny, dstImage_canny, 7); Canny(dstImage_canny, dstImage, 3, 3 * 3, 3); dstImage.copyTo(ImageROI); Rect ccomp; //选择了三个种子点,分别赋予了三种填充颜色。 //第一次调用floodFill时,未添加canny边缘检测后的掩模,所以云的边缘被腐蚀掉了一部分。结果就是部分云消失了。 //后两次调用floodFill时,添加了canny边缘检测后的掩模,云的边缘得到了很好的保留,但是部分颜色变化较大的区域,canny后,检验出了边缘,边缘的原来颜色得到了保留 floodFill(srcImage, Point(223, 184), CV_RGB(88,123,165), &ccomp, Scalar(35, 35, 35), Scalar(30, 30, 30), 8 | FLOODFILL_FIXED_RANGE ); floodFill(srcImage, mask, Point(48, 507), CV_RGB(108,148,184), &ccomp, Scalar(25, 25, 25), Scalar(15, 15, 15), 8 | FLOODFILL_FIXED_RANGE ); floodFill(srcImage, mask, Point(609, 582), CV_RGB(137,173,197), &ccomp, Scalar(25, 25, 25), Scalar(15, 15, 15), 8 | FLOODFILL_FIXED_RANGE ); imwrite("M:/图像处理实验/floodFill/云_dst.bmp", srcImage);
下面为原图与漫水填充后的输出结果:
canny边缘检测后的输出,也是floodFill函数中的掩模。黑色区域可以被填充,白色部分保留原来的颜色。
EmguCV中的函数原型如下:
Public Shared Function FloodFill(src As Emgu.CV.IInputOutputArray, mask As Emgu.CV.IInputOutputArray, seedPoint As System.Drawing.Point, newVal As Emgu.CV.Structure.MCvScalar, ByRef rect As System.Drawing.Rectangle, loDiff As Emgu.CV.Structure.MCvScalar, upDiff As Emgu.CV.Structure.MCvScalar, Optional connectivity As Emgu.CV.CvEnum.Connectivity = FourConnected, Optional flags As Emgu.CV.CvEnum.FloodFillType = Default) As Integer
- 第一个参数,Emgu.CV.IInputOutputArray类型的image。
- 第二个参数, Emgu.CV.IInputOutputArray类型的mask,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。与OpenCV不同,emguCV貌似没有第二个版本的FloodFill函数,emgu提供的FloodFill与OpenCV的第二个函数作用相似。漫水填充算法不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大2个像素,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
- 第三个参数,Point类型的seedPoint,漫水填充算法的起始点。
- 第四个参数,Emgu.CV.Structure.MCvScalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
- 第五个参数,Rect*类型的rect,用于设置floodFill函数将要重绘区域的最小边界矩形区域。与OpenCV不同,没有默认值。
- 第六个参数,Emgu.CV.Structure.MCvScalar类型的loDiff,表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
- 第七个参数,Emgu.CV.Structure.MCvScalar类型的upDiff,表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。
- 第八个参数,Emgu.CV.CvEnum.Connectivity类型的connectivity。控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
- 第九个参数,Emgu.CV.CvEnum.FloodFillType类型的flags。可以为0(0为缺省值) 或者以下两种选项标识符的组合:
FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。
Dim bkGrayWhite As New Gray(255) Dim bkGrayBlack As New Gray(0) Dim img As Image(Of Bgr, Byte) = New Image(Of Bgr, Byte)("M:\图像处理实验\FloodFill\云1.bmp") Dim img_MedianBlur As Image(Of Bgr, Byte) = New Image(Of Bgr, Byte)(img.Width, img.Height) img.CopyTo(img_MedianBlur) CvInvoke.MedianBlur(img, img_MedianBlur, 7) Dim mask As Image(Of Gray, Byte) = New Image(Of Gray, Byte)(img.Width + 2, img.Height + 2, bkGrayBlack) 'BGR CvInvoke.FloodFill(img, mask, New System.Drawing.Point(2, 2), New MCvScalar(165, 123, 88), New System.Drawing.Rectangle(0, 0, 0, 0), New MCvScalar(5, 5, 5), New MCvScalar(5, 5, 5), Emgu.CV.CvEnum.Connectivity.EightConnected, Emgu.CV.CvEnum.FloodFillType.FixedRange ) CvInvoke.cvSetImageROI(mask, New System.Drawing.Rectangle(1, 1, img.Width, img.Height)) Dim img_canny As Image(Of Gray, Byte) = New Image(Of Gray, Byte)(img.Width, img.Height, bkGrayBlack) CvInvoke.Canny(img_MedianBlur, img_canny, 5, 5 * 3) img_canny.CopyTo(mask) CvInvoke.cvResetImageROI(mask) CvInvoke.FloodFill(img, mask, New System.Drawing.Point(668, 570), New MCvScalar(197, 173, 137), New System.Drawing.Rectangle(0, 0, 0, 0), New MCvScalar(25, 25, 25), New MCvScalar(5, 5, 5), Emgu.CV.CvEnum.Connectivity.EightConnected, Emgu.CV.CvEnum.FloodFillType.FixedRange ) img.Save("M:\图像处理实验\FloodFill\云1_result.bmp")
本文中的OpenCv与EmguCV均用的是3.0以上的版本。
参考文献:
Bradski & Kaebler ·《学习OpenCV(中文版)》· 清华大学出版社 · 2009