函数原型
int floodFill( InputOutputArray image, Point seedPoint, Scalar newVal, CV_OUT Rect* rect = 0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4 ); int floodFill( InputOutputArray image, InputOutputArray mask, Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4 );
参数说明
floodFill函数有两个原型,第二个相比第一个多了一个掩膜区,以第二个为例介绍各个参数。
- InputOutputArray类型的image,输入输出图像。
- InputOutputArray类型的mask,掩膜区,掩膜区非零区域不被填充,且掩膜区的宽高要比输入图像各多两个像素,输入图像中(x,y)位置对应掩膜区(x+1,y+1)位置,掩膜区的具体使用将在下方进行举例演示。
- Point类型的seedPoint,漫水填充的起点。
- Scalar类型的newVal,像素点被填充后呈现的颜色。
- Rect*类型的rect,漫水填充后重绘区域的最小边界矩形区域。
- Scalar类型的loDiff,当前观测像素与种子像素颜色负差的最大值。通俗地讲,就是观测像素各个通道的值都要比种子像素小,且小的数值要低于loDiff,满足该条件才会被填充。具体使用将在下方进行举例演示。
- Scalar类型的upDiff,当前观测像素与种子像素颜色正差的最大值。通俗地讲,就是观测像素各个通道的值都要比种子像素大,且大的数值要低于upDiff,满足该条件才会被填充。具体使用将在下方进行举例演示。
- int类型的flags,操作标识符,是32位二进制数。低八位表示算法连通性,一般取4或者8,4为上下左右,8加上对角四点;高八位可选两种标识符,分别为FLOODFILL_FIXED_RANGE和FLOODFILL_MASK_ONLY,也可以用or(|)组合它们;中八位是填充掩膜的数值,搭配FLOODFILL_MASK_ONLY使用。具体使用将在下方进行举例演示。
漫水填充思想
漫水填充法是一种常见的图像处理方法,通过选中和种子点相连相近的区域,将其转换为指定颜色,以达到标记或者分离图像的目的,进而完成对图像的一些分析和处理。类似PS中的魔术棒选择工具,点击某个点,自动框选相近的区域。
思想并不复杂,但在使用floodFill函数的过程中,有许多细节可以进一步研究和分析,大多数人并未深入了解。为此我写下该篇文章,探讨一下OpenCV自带的漫水填充算法的一些细节。如有补充,欢迎评论留言。
loDiff和upDiff
先看下方代码,loDiff为Scalar(1, 1, 1),upDiff为Scalar(10, 10, 10),表示当前观测点的像素X与周围已被填充的像素点数值Y,需满足X-Y<10,且Y-X<1,才被填充。
Mat src = imread("test.jpg"); Rect roi; int flags = 8; floodFill(src, Point(src.cols / 2, src.rows / 2), Scalar(255, 0, 255), &roi, Scalar(1, 1, 1), Scalar(10, 10, 10), flags);
如下图所示,观察数据的填充情况,Y为种子点,X为当前观测点,符合要求,所以被填充了。而旁边几个点都不满足,所以都保持原状。
图1
再将loDiff为Scalar(10, 10, 10),upDiff为Scalar(1, 1, 1),则需满足X-Y<1,且Y-X<10,才被填充。不难发现,8近邻的像素点被填充了好几个,而被填充后的点将作为新的种子点,继续向外探索可被填充的数据。
图2
掩膜
如函数说明要求,掩膜尺寸为原尺寸的宽高再加2,这应该是算法实现过程中卷积的要求,便于计算。在下方代码中,我们定义了掩膜,并将掩膜的(src.rows/2,src.cols/2)的位置设为255,即不进行填充,而该点位对应原图中的(src.rows/2-1,src.cols/2-1)。
Mat src = imread("test.jpg"); Rect roi; int flags = 8; Mat mask = Mat::zeros(src.rows + 2,src.cols + 2, CV_8UC1); mask.at<uchar>(src.rows / 2, src.cols / 2) = 255; floodFill(src, mask, Point(src.cols / 2, src.rows / 2), Scalar(255, 0, 255), &roi, Scalar(10, 10, 10), Scalar(1, 1, 1), flags);
除了掩膜,其他参数与图2对应的代码一致,让我们看看加了一个掩膜点后,填充的效果图。
图3
与图2相比,中心种子点左上角,32 90 102像素点未被填充,除此之外,左上方原本应该被填充的许多像素也未被填充,说明掩膜作用在填充过程中。换句话说,当某个点受掩膜限制未被填充后,再外圈的点也将不以其为种子点进行分析。这也表明,掩膜可以为漫水填充提供方向的引导,即指挥填充的路线。
操作标识符
从简单的开始说,低八位表示连通性,只需让flags=4或者8即可。若为4,则低八位的二进制数为00000100;若为8,则低八位的二进制数为00001000。其效果分别如下所示。
图4 原图
图5 4连通
图6 8连通
这应该还是比较好理解的。
接下来让flags变为如下形式,表示8或FLOODFILL_FIXED_RANGE。如图7所示,该值等于1<<16,即1左移16位,则是32位的二进制数00000001 00000000 00000000,该值也是65536,2的16次方。那8|FLOODFILL_FIXED_RANGE,就表示00000001 00000000 00001000。这表示漫水填充不仅要用8连通,还要用固定范围的分析。
默认情况下,填充过程的参考种子是邻近的点,也就是用于判断是否进行填充的那个范围是动态波动的。但在FLOODFILL_FIXED_RANGE标识下,参考种子是固定的,也就是开始标记的那个点,那用于判断是否填充的范围也就变成固定的了,即起始标记点像素值的上下限制范围。
int flags = 8 | FLOODFILL_FIXED_RANGE;
图7 枚举
如果你理解了上面的描述,那不难理解,在FLOODFILL_FIXED_RANGE标识下,填充效果将大打折扣,毕竟邻近点作为种子,更容易符合要求些。效果如图8所示。
图8 FLOODFILL_FIXED_RANGE效果图
图8 FLOODFILL_FIXED_RANGE效果图
FLOODFILL_MASK_ONLY同理,二进制数为00000010 00000000 00000000,左移17位,如果和FLOODFILL_FIXED_RANGE取或,则为00000011 00000000 00000000。顾名思义,FLOODFILL_MASK_ONLY表示,不对原图做改动,即重绘操作,而是在掩膜上进行标记,标记的数值由32位二进制数的中间八位决定,如果要用255来标记,则如下写法。
int flags = 8 | FLOODFILL_FIXED_RANGE | FLOODFILL_MASK_ONLY | (255 << 8);
上述写法让255的值左移8位,相当于把11111111移动到中间8位上,那完整的二进制就是00000011 11111111 00001000,该值也就是flags的值,取整是261896。对应的效果如8所示,原图不受影响,掩膜上做出标记。
图9 掩膜标记
以上就是本篇文章所讲漫水填充的完整内容了,希望能给你带来一点帮助。
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!