6.5 漫水填充(floodFill)

6.5.1 漫水填充

1.定义:一种用特定的颜色填充连通区域,通过设置可连通像素的上下限及连通方式达到不同填充效果
2.基本思想:自动选中和种子点相连的区域(位于给定范围(从LowDiff到UpDiff)或在原始seedPoint像素值范围内),将该区域所有相似点填充指定的相同颜色
3.作用:标记或分离图像一部分,从输入图像获取掩码区域
4.封装函数:floodFill()函数
5.函数原型(有2个版本,另一个版本无参数2):

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

6.参数说明
(1)输入/输出1通道或3通道,8位或浮点图像
(2)操作掩模,8位单通道,长和宽都比原图像大两个像素点,漫水填充不会填充掩模mask的非零像素区域,如,边缘检测算子输出做掩模可以防止填充边缘、多次函数调用中使用同一个掩模可以保证填充区域不重叠。注意:掩模比须填充图像大,输入图像(x,y)点对应掩模(x+1,y+1)点
(3)算法起始点
(4)像素点被染色的值,即在重绘区域像素的新值
(5)可选参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域,默认0
(6)表示当前观察像素值与其附近邻域像素值或待加入的种子像素值之间的亮度或颜色之负差(lower brightness/color difference)的最大值,默认Scalar()
(7)表示当前观察像素值与其附近邻域像素值或待加入的种子像素值之间的亮度或颜色之正差(lower brightness/color difference)的最大值,默认Scalar()
(8)操作标识符,包含三个部分(用”|”连接):
  1)0~7位用于控制算法连通性,取4(默认)表示填充算法只考虑当前像素水平方向或垂直方向的相邻点,取8表示还会包含对角线方向的临近点
  2)16~23位可以为0或如下两种选项标识符的组合:FLOODFILL_RANGE表示会考虑当前像素与种子像素的差,否则会考虑当前像素与其相邻像素的差;FLOODFILL_MASK_ONLY表示不会填充改变原始图像(即忽略第3个参数newVal)而是去填充掩模图像
  3)8~15位用于指定填充掩码图像的值(后8位标识符为FLOODFILL_MASK_ONLY时),0表示掩模用1填充
Eg. 8邻域,固定填充像素值范围,填充掩模不填充原图像,填充值为38:
Flags=8|FLOODFILL_MASK_ONLY|FLOODFILL_FIXED_RANGE|(38<<8)

6.5.2 漫水填充示例

1.漫水填充简单示例

#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main()
{
	//载入原图并显示
	Mat srcImage = imread("love.jpg");
	imshow("【原始图】", srcImage);
	//漫水填充
	Rect ccomp;
	floodFill(srcImage, Point(50, 300), Scalar(155, 255, 55), &ccomp, Scalar(20, 20, 20), Scalar(20, 20, 20));
	//显示效果图
	imshow("【效果图】", srcImage);

	waitKey(0);
	return 0;
}

运行效果:
opencv 画填充矩形框 opencv图像填充_漫水填充opencv 画填充矩形框 opencv图像填充_标识符_02
2.综合示例

/*
效果:
	鼠标对窗口多次点击得到类似PS的魔棒效果
	键盘8个按键操作切换漫水填充模式
*/
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;

//全局变量
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定义原始图、目标图、灰度图、掩模图
int g_nFillMode = 1;//漫水填充模式(0为空范围,1为固定填充像素值范围)
int g_nLowDifference = 20;//负差最大值
int g_nUpDifference = 20;//正差最大值
int g_nConnectivity = 4;//表示floodFill函数标识符低八位的连通值
int g_nNewMaskVal = 255;//新的重新绘制的像素值
int g_bIsColor = true;//是否为彩色图的标识符布尔值
bool g_bUseMask = false;//是否显示掩模窗口的布尔值

//---------------------------------【onMouse()函数】----------------------
//				描述:鼠标消息onMouse回调函数
//------------------------------------------------------------------------
static void onMouse(int event, int x, int y, int, void*)
{
	//若鼠标左键没有按下,便返回
	if (event != EVENT_LBUTTONDOWN)
		return;

	//--------------------【<1>调用floodFill函数之前的参数准备】-------------
	Point seed = Point(x, y);
	int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空范围的漫水填充设为0,否则设为全局的g_nLowDifference
	int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;  //空范围的漫水填充设为0,否则设为全局的g_nUpDifference
	int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED+RANGE或0

	//随机生成bgr值
	int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	//定义重绘区域的最小边界矩形区域
	Rect ccomp;
	//重绘区域像素的新值,若是彩色图模式,取Scalar(b,g,r);若是灰度图模式,取Scalar(r*0.299+b*0.587+b*0.114)
	Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g * 0.587 + b * 0.114);
	//目标图的赋值
	Mat dst = g_bIsColor ? g_dstImage : g_grayImage;
	int area;
	
	//---------------------【<2>正式调用floodFill函数】---------------------
	if (g_bUseMask)
	{
		threshold(g_maskImage, g_maskImage, 1, 128, THRESH_BINARY);
		area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
	}
	else
	{
		area=floodFill(dst,seed,newVal,&ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
	}
	imshow("效果图", dst);
	cout.width(7);
	cout <<area << " 个像素被重绘\n";
}
//---------------------------------【ShowHelpText()函数】----------------------
//				描述:键盘操作说明
//------------------------------------------------------------------------

static void ShowHelpText()
{
	printf("--------------------------------------------------------------------\n");
	printf("请鼠标点击图像观察漫水填充效果~\n");
	printf("按键操作说明:\n");
	printf("\t\t键盘按键[ESC]-退出程序\n");
	printf("\t\t键盘按键[1]-切换彩色图/灰度图模式\n");
	printf("\t\t键盘按键[2]-显示/隐藏掩模模式\n");
	printf("\t\t键盘按键[3]-恢复原始图像\n");
	printf("\t\t键盘按键[4]-使用空范围的漫水填充\n");
	printf("\t\t键盘按键[5]-使用新变、固定范围的漫水填充\n");
	printf("\t\t键盘按键[6]-使用新变、浮动范围的漫水填充\n");
	printf("\t\t键盘按键[7]-操作标识符的低8位使用4的连接模式\n");
	printf("\t\t键盘按键[8]-操作标识符的低8位使用8的连接模式\n");
	printf("--------------------------------------------------------------------\n");
}
int main()
{
	//键盘操作说明
	ShowHelpText();

	//载入原图
	g_srcImage = imread("love.jpg");
	if (!g_srcImage.data)
	{
		printf("载入原图失败~!\n");
		return false;
	}

	//复制原图到目标图
	g_srcImage.copyTo(g_dstImage);
	cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);//转换三通道的g_srcImage到灰度图
	g_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);//利用g_srcImage尺寸初始化mask
	//创建效果图窗口
	namedWindow("效果图", WINDOW_AUTOSIZE);
	//创建TrackBar
	createTrackbar("负差最大值", "效果图", &g_nLowDifference, 255, 0);
	createTrackbar("正差最大值", "效果图", &g_nUpDifference, 255, 0);
	//鼠标回调函数
	setMouseCallback("效果图", onMouse, 0);
	//循环轮询按键
	while (1)
	{
		//先显示效果图
		imshow("效果图", g_bIsColor ? g_dstImage : g_grayImage);
		//获取键盘按键
		int c = waitKey(0);
		//判断按键ESC是否按下,若按下便退出
		if ((c & 255) == 27)
		{
			cout << "程序退出.......\n";
			break;
		}
		//根据按键不同,进行各种操作
		switch ((char)c)
		{
			//如果键盘1按下,效果图在灰度图,彩色图之间互换
		case '1':
			//若原来为彩色,转为灰度图,并将掩模mask所有元素设为0
			if (g_bIsColor)
			{
				cout << "按键“1”按下,切换彩色/灰度模式,当前操作为【彩色模式】切换为【灰度模式】\n";
				cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
				g_maskImage = Scalar::all(0);//将mask所有元素设为0
				g_bIsColor = false;//将标识符设置为false,表示当前图像不为彩色,而是灰度
			}
			//若原来为灰度,将原来彩色图g_srcImage再次复制给g_dstImage,并将掩模mask所有元素设为0
			else
			{
				cout << "按键“1”按下,切换彩色/灰度模式,当前操作为【灰度模式】切换为【彩色模式】\n";
				g_srcImage.copyTo(g_dstImage);
				g_maskImage = Scalar::all(0);
				g_bIsColor = true; //表示当前图像模式为彩色
			}
			break;
			//如果按键2被按下,显示/隐藏掩模窗口
		case '2':
			if (g_bUseMask)
			{
				destroyWindow("mask");
				g_bUseMask = false;
			}
			else
			{
				namedWindow("mask", 0);
				g_maskImage = Scalar::all(0);
				imshow("mask", g_maskImage);
				g_bUseMask = true;
			}
			break;
			//如果按键3被按下,恢复原始图像
		case '3':
			cout << "按键“3”被按下,恢复原始图像\n";
			g_srcImage.copyTo(g_dstImage);
			cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
			g_maskImage = Scalar::all(0);
			break;
			//如果按键4被按下,使用空范围的漫水填充
		case '4':
			cout << "按键“4”被按下,使用空范围的漫水填充\n";
			g_nFillMode = 0;
			break;
			//如果按键5被按下,使用新变、固定范围的漫水填充
		case '5':
			cout << "按键“5”被按下,使用新变、固定范围的漫水填充\n";
			g_nFillMode = 1;
			break;
			//如果按键6被按下,使用新变、浮动范围的漫水填充
		case '6':
			cout << "按键“6”被按下,使用新变、浮动范围的漫水填充\n";
			g_nFillMode = 2;
			break;
			//如果按键7被按下,操作标识符的低8位使用4的连接模式
		case '7':
			cout << "按键“7”被按下,操作标识符的低8位使用4的连接模式\n";
			g_nConnectivity = 4;
			break;
			//如果按键8被按下,操作标识符的低8位使用8的连接模式
		case '8':
			cout << "按键“8”被按下,操作标识符的低8位使用8的连接模式\n";
			g_nConnectivity = 8;
			break;
		}
	}
	return 0;
}

运行效果:
opencv 画填充矩形框 opencv图像填充_标识符_03opencv 画填充矩形框 opencv图像填充_Image_04
opencv 画填充矩形框 opencv图像填充_Image_05
opencv 画填充矩形框 opencv图像填充_漫水填充_06