上一节我们学习了用如何用findContours函数对图像查找轮廓,以及用drawContours函数对查找的轮廓进行绘制,相信大家学习之后,对图像轮廓已经有了基本的认识,本节呢,我们对漫水填充算法(floodFill)进行详细的理解!
一、漫水填充函数详解
1、函数原型
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);
2、函数功能
用指定的颜色从种子点(参数中 seedPoint 为种子点)开始填充连通的元素;
在以下情况下,(x,y) 处的像素被视为重新绘制的区域:
(1)在灰度图像和浮动范围的情况下

(2)在灰度图像和固定范围的情况下

(3)在彩色图像和浮动范围的情况下

(4)在彩色图像和固定范围的情况下

其中 src(x′,y′) 已知属于该元素的像素邻域之一的值 。也就是说,要添加到连接的元素中,像素的颜色/亮度应该足够接近于:
(1)在浮动范围内,已属于已连接元素的邻域之一的颜色/亮度;
(2)在固定范围内种子点的颜色/亮度。
使用这些函数标记具有指定颜色的连接要素,或构建掩码,然后提取轮廓,或将区域复制到其他图像,以此类推。
3、参数详解
-
第一个参数,InputOutputArray image,输入/输出图像,这个图像可以是单通道或者三通道图像,也可以是8位或者32位浮点图像;图像会被函数修改,如果在重载的函数中设置FLOODFILL_MASK_ONLY标志,图像不会被修改;
-
第二个参数,InputOutputArray mask,操作的掩码图像,一般为8位单通道图像,比图像(image)宽两个像素,高两个像素;由于这个mask输一个输入/输出参数,所以必须对这个参数进行初始化,即必须创建一个掩码图像;漫水填充不能跨越输入掩码中的非零像素;例如,边缘检测器输出可以用作掩码,以停止边缘填充;在输出时,对应于图像中填充像素的掩码中的像素设置为1或设置为在标志中指定的值;此外,该函数填充掩码的边框,以简化内部处理;因此,可以在函数的多次调用中使用相同的掩码,以确保填充的区域不重叠;
-
第三个参数,Point seedPoint,漫水填充的种子点;
-
第四个参数,Scalar newVal,重新绘制漫水填充区域所用的颜色;
-
第五个参数,CV_OUT Rect* rect = 0,函数将可选输出参数设置为重绘域的最小边界矩形;
-
第六个参数,Scalar loDiff = Scalar(),当前选定像素与其连通区中相邻像素中的一个像素,或者与加入该连通区的一个seedPoint像素,二者之间的最大下行差异值。
-
第七个参数,Scalar upDiff = Scalar(),当前选定像素与其连通区中相邻像素中的一个像素,或者与加入该连通区的一个seedPoint像素,二者之间的最大上行差异值。
-
第八个参数,int flags = 4,操作标记,前8位包含一个连通的值;默认值4意味着只考虑四个最近邻像素(共享边缘的像素);连通的值为8意味着将考虑8个最近的相邻像素(共享一个角的像素);接下来的8位(8-16)包含一个介于1到255之间的值,用来填充掩码(默认值是1);例如,4 |(255<8)将考虑4个最近的邻居,并填充值为255的掩码。以下附加选项占用较高的位,因此可能会进一步结合使用按位进行填充的连接和掩码填充值;
floodfill填充模式
1、FLOODFILL_FIXED_RANGE ,如果设置,则考虑当前像素与种子像素之间的差异;否则,考虑相邻像素之间的差异(即范围是浮动的)。
2、FLOODFILL_MASK_ONLY ,如果设置,函数不会更改图像(忽略newVal),并且只使用上面描述的标志位8-16中指定的值填充掩码;此选项仅在具有掩码参数的函数变体中才有意义。
二、综合实例
1、实验案例
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
static void help()
{
cout << "键盘: \n"
"\tESC - 退出程序\n"
"\tc - 切换彩色与灰度图像\n"
"\tm - 切换掩码图像\n"
"\tr - 恢复为原始图像\n"
"\ts - 不使用\n"
"\tf - 绝对梯度\n"
"\tg - 相对梯度\n"
"\t4 - 4连通域\n"
"\t8 - 8连通域\n" << endl;
}
// 图像
Mat image0, image, gray, mask;
// 漫水填充的模式
int ffillMode = 1;
int loDiff = 20, upDiff = 20;
// 连通域
int connectivity = 4;
// 是否是彩色图像
int isColor = true;
// 是否使用掩码图像
bool useMask = false;
// 掩码值
int newMaskVal = 255;
static void onMouse( int event, int x, int y, int, void* )
{
// 如果不是左键按下事件,则不处理
if( event != EVENT_LBUTTONDOWN )
return;
// 获取鼠标点击位置的坐标
Point seed = Point(x,y);
int lo = ffillMode == 0 ? 0 : loDiff;
int up = ffillMode == 0 ? 0 : upDiff;
// 判断使用哪种方式进行漫水填充
int flags = connectivity + (newMaskVal << 8) +
(ffillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
// 随机取值&255,使得值在0-255之间
int b = (unsigned)theRNG() & 255;
int g = (unsigned)theRNG() & 255;
int r = (unsigned)theRNG() & 255;
// 图像的颜色,彩色还是灰色
Scalar newVal = isColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);
// 彩色图像还是灰度图像
Mat dst = isColor ? image : gray;
int area;
Rect ccomp;
// 是否使用掩码图像
if( useMask )
{
threshold(mask, mask, 1, 128, THRESH_BINARY);
// 进行漫水填充
area = floodFill(dst, mask, seed, newVal, &ccomp, Scalar(lo, lo, lo),
Scalar(up, up, up), flags);
// 显示掩码图像
imshow( "mask", mask );
}
else
{
// 进行漫水填充
area = floodFill(dst, seed, newVal, &ccomp, Scalar(lo, lo, lo),
Scalar(up, up, up), flags);
}
// 显示图像
imshow("image", dst);
cout << area << " pixels were repainted\n";
}
int main( int argc, char** argv )
{
// 载入图像
image0 = imread("lena.png", 1);
// 判断图像是否为空
if( image0.empty() )
{
cout << "Image empty !\n";
return 0;
}
// 帮助信息
help();
// 备份图像
image0.copyTo(image);
// 彩色图像转换为灰度图像
cvtColor(image0, gray, COLOR_BGR2GRAY);
// 创建掩码图像
mask.create(image0.rows+2, image0.cols+2, CV_8UC1);
// 创建窗体
namedWindow( "image", 0 );
// 创建滑动条
createTrackbar( "lo_diff", "image", &loDiff, 255, 0 );
createTrackbar( "up_diff", "image", &upDiff, 255, 0 );
// 设置鼠标触发事件
setMouseCallback( "image", onMouse, 0 );
// 死循环,等待键盘输入
for(;;)
{
// 显示图像
imshow("image", isColor ? image : gray);
// 接收键盘输入
char c = (char)waitKey(0);
if( c == 27 )
{
cout << "Exiting ...\n";
break;
}
switch( c )
{
// 切换彩色与灰度图像
case 'c':
if( isColor )
{
cout << "Grayscale mode is set\n";
cvtColor(image0, gray, COLOR_BGR2GRAY);
mask = Scalar::all(0);
isColor = false;
}
else
{
cout << "Color mode is set\n";
image0.copyTo(image);
mask = Scalar::all(0);
isColor = true;
}
break;
// 是否使用掩码图像
case 'm':
if( useMask )
{
destroyWindow( "mask" );
useMask = false;
}
else
{
namedWindow( "mask", 0 );
mask = Scalar::all(0);
imshow("mask", mask);
useMask = true;
}
break;
// 恢复图像为原始图像
case 'r':
cout << "Original image is restored\n";
image0.copyTo(image);
cvtColor(image, gray, COLOR_BGR2GRAY);
mask = Scalar::all(0);
break;
case 's':
cout << "Simple floodfill mode is set\n";
ffillMode = 0;
break
case 'f':
cout << "Fixed Range floodfill mode is set\n";
ffillMode = 1;
break;
case 'g':
cout << "Gradient (floating range) floodfill mode is set\n";
ffillMode = 2;
break;
case '4':
cout << "4-connectivity mode is set\n";
connectivity = 4;
break;
case '8':
cout << "8-connectivity mode is set\n";
connectivity = 8;
break;
}
}
return 0;
}
2、实验结果



我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!
网友评论