OpenCV学习笔记(九): 漫水填充:floodFill()
定义:
漫水填充法是一种用特定的颜色填充联通区域(自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色)通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。
使用:
1)经常被用来标记或分离图像的一部分,以便对其进行进一步处理或分析。
2)从输入图像获取掩码区域(掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域)
算子:
int floodFill(
InputOutputArray image, // 1.输入/输出图像
InputOutputArray mask, // 2.这是第二个版本的floodFill独享的参数,表示操作掩模(它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像)
Point seedPoint, // 3.漫水填充算法的起始点
Scalar newVal, // 4.在重绘区域像素点被染色的新值
Rect* rect=0, // 5.一个可选的参数,将要重绘区域的最小边界矩形区域
Scalar loDiff=Scalar(), // 6.loDiff 负差最大值(当前像素与领域像素或种子像素颜色或亮度的)
Scalar upDiff=Scalar(), // 7.upDiff 正差最大值(当前像素与领域像素或种子像素颜色或亮度的)
int flags=4 // 8.操作标志符
)
// PS:
1、漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,
掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
2、
1)低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。
如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;
如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
2)高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,
否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal),
而是去填充掩模图像(mask)。
3)中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
//如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
示例代码:
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage; //定义原始图、目标图、灰度图、掩模图
int g_nFillMode = 1; //漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20; //负差最大值、正差最大值
int g_nConnectivity = 4; //表示floodFill函数标识符低八位的连通值
bool g_bIsColor = true; //是否为彩色图的标识
bool g_bUseMask = false; //是否显示掩膜窗口的标识
int g_nNewMaskVal = 255; //新的重新绘制的像素值int main()
{//显示帮助文字ShowHelpText();// 1、载入原图g_srcImage = imread("F:/C++/2. OPENCV 3.1.0/TEST/2.jpg", 1);if( !g_srcImage.data ){printf("读取图片image0错误~! \n");return false;}// 2、拷贝源图到目标图g_srcImage.copyTo(g_dstImage);// 3、转换三通道的image0到灰度图cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);// 4、利用image0的尺寸来初始化掩膜maskg_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);// 5、创建TrackbarnamedWindow( "效果图",WINDOW_AUTOSIZE );createTrackbar( "负差最大值", "效果图", &g_nLowDifference, 255, 0 ); // 无滑动条事件,只改变滑动条值(0~255)createTrackbar( "正差最大值" ,"效果图", &g_nUpDifference, 255, 0 ); // 无滑动条事件,只改变滑动条值(0~255)// 6、鼠标回调函数(鼠标点击便触发)// 将mask所有元素设置为0(背景设为黑色)g_maskImage = Scalar::all(0); setMouseCallback( "效果图", onMouse, 0 );// 7、循环轮询按键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':if( g_bIsColor )//若原来为彩色,转为灰度图,并且将掩膜mask所有元素设置为0{cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);g_maskImage = Scalar::all(0); //将mask所有元素设置为0g_bIsColor = false; //将标识符置为false,表示当前图像不为彩色,而是灰度}else//若原来为灰度图,便将原来的彩图image0再次拷贝给image,并且将掩膜mask所有元素设置为0{cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";g_srcImage.copyTo(g_dstImage);g_maskImage = Scalar::all(0);g_bIsColor = true; //将标识符置为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;// 改变”漫水填充“操作标识:// 1、如果键盘按键“4”被按下,使用空范围的漫水填充case '4':cout << "按键“4”被按下,使用空范围的漫水填充\n";g_nFillMode = 0;break;// 2、如果键盘按键“5”被按下,使用渐变、固定范围的漫水填充case '5':cout << "按键“5”被按下,使用渐变、固定范围的漫水填充\n";g_nFillMode = 1;break;// 3、如果键盘按键“6”被按下,使用渐变、浮动范围的漫水填充case '6':cout << "按键“6”被按下,使用渐变、浮动范围的漫水填充\n";g_nFillMode = 2;break;// 4、如果键盘按键“7”被按下,操作标志符的低八位使用4位的连接模式case '7':cout << "按键“7”被按下,操作标志符的低八位使用4位的连接模式\n";g_nConnectivity = 4;break;// 5、如果键盘按键“8”被按下,操作标志符的低八位使用8位的连接模式case '8':cout << "按键“8”被按下,操作标志符的低八位使用8位的连接模式\n";g_nConnectivity = 8;break;}}
}
输出一些帮助信息
static void ShowHelpText()
{printf("\n\n\t漫水填充示例程序~");printf("\n\n\t根据鼠标选取的点搜索图像中与之颜色相近的点,并用不同颜色标注。");printf("\n\n\t按键操作说明: \n\n""\t\t鼠标点击图中区域- 进行漫水填充操作\n""\t\t键盘按键【ESC】- 退出程序\n""\t\t键盘按键【1】- 切换彩色图/灰度图模式\n""\t\t键盘按键【2】- 显示/隐藏掩膜窗口\n""\t\t键盘按键【3】- 恢复原始图像\n""\t\t键盘按键【4】- 使用空范围的漫水填充\n""\t\t键盘按键【5】- 使用渐变、固定范围的漫水填充\n""\t\t键盘按键【6】- 使用渐变、浮动范围的漫水填充\n""\t\t键盘按键【7】- 操作标志符的低八位使用4位的连接模式\n""\t\t键盘按键【8】- 操作标志符的低八位使用8位的连接模式\n\n");
}
鼠标消息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_nLowDifferenceint UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference; //空范围的漫水填充,此值设为0,否则设为全局的g_nUpDifference// 1.1、floodFill()函数第八个参数值:flags=低八位+中八位+高八位int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0); // FLOODFILL_FIXED_RANGE// 1.2、随机生成bgr值int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值Rect ccomp;//定义重绘区域的最小边界矩形区域// 1.3、在重绘区域像素的新值,若是彩色图模式,取Scalar(b, g, r);若是灰度图模式,取Scalar(r*0.299 + g*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;//目标图的赋值// 2、正式调用floodFill函数threshold(g_maskImage, g_maskImage, 1, 128, THRESH_BINARY);// 带掩码输出的 floodFill()int area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),Scalar(UpDifference, UpDifference, UpDifference), flags);// 3、关闭或显示掩码窗口if (g_bUseMask){imshow("mask", g_maskImage);}
// else
// {
// // 不带掩码输出的 floodFill()
// area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
// Scalar(UpDifference, UpDifference, UpDifference), flags);
// }imshow("效果图", dst);cout << area << " 个像素被重绘\n";
}
结果: