漫水填充算法是一种用特定颜色填充连通区域,通过设置像素上下限及连通方式来达到不同的连通效果。漫水填充经常用来标记或分离图像的一部分,以便于对其进行进一步的处理和分析。也可以从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域,连通性由像素值的接近程度来衡量。下面是OpenCV3.X的两个floodFill函数原型:(...\opencv\sources\modules\imgproc\src\floodfill,cpp)
//第一个函数原型
int cv::floodFill( InputOutputArray _image, Point seedPoint,//不带mask
Scalar newVal, Rect* rect,
Scalar loDiff, Scalar upDiff, int flags )
{
CV_INSTRUMENT_REGION()
return floodFill(_image, Mat(), seedPoint, newVal, rect, loDiff, upDiff, flags);
//可以看到,第一个函数原型内部只是调用了第二个函数
}
//第二个函数原型
int cv::floodFill( InputOutputArray _image, InputOutputArray _mask,//带mask
Point seedPoint, Scalar newVal, Rect* rect,
Scalar loDiff, Scalar upDiff, int flags )
{
CV_INSTRUMENT_REGION()
ConnectedComp comp;
std::vector buffer;
if( rect )
*rect = Rect();
int i, connectivity = flags & 255;
union {
uchar b[4];
int i[4];
float f[4];
double _[4];
} nv_buf;
nv_buf._[0] = nv_buf._[1] = nv_buf._[2] = nv_buf._[3] = 0;
struct { Vec3b b; Vec3i i; Vec3f f; } ld_buf, ud_buf;
Mat img = _image.getMat(), mask;
if( !_mask.empty() )
mask = _mask.getMat();
Size size = img.size();
int type = img.type();
int depth = img.depth();
int cn = img.channels();
if ( (cn != 1) && (cn != 3) )
{
CV_Error( CV_StsBadArg, "Number of channels in input image must be 1 or 3" );
}
if( connectivity == 0 )
connectivity = 4;
else if( connectivity != 4 && connectivity != 8 )
CV_Error( CV_StsBadFlag, "Connectivity must be 4, 0(=4) or 8" );
bool is_simple = mask.empty() && (flags & FLOODFILL_MASK_ONLY) == 0;
for( i = 0; i < cn; i++ )
{
if( loDiff[i] < 0 || upDiff[i] < 0 )
CV_Error( CV_StsBadArg, "lo_diff and up_diff must be non-negative" );
is_simple = is_simple && fabs(loDiff[i]) < DBL_EPSILON && fabs(upDiff[i]) < DBL_EPSILON;
}
if( (unsigned)seedPoint.x >= (unsigned)size.width ||
(unsigned)seedPoint.y >= (unsigned)size.height )
CV_Error( CV_StsOutOfRange, "Seed point is outside of image" );
scalarToRawData( newVal, &nv_buf, type, 0);
size_t buffer_size = MAX( size.width, size.height ) * 2;
buffer.resize( buffer_size );
if( is_simple )
{
size_t elem_size = img.elemSize();
const uchar* seed_ptr = img.ptr(seedPoint.y) + elem_size*seedPoint.x;
size_t k = 0;
for(; k < elem_size; k++)
if (seed_ptr[k] != nv_buf.b[k])
break;
if( k != elem_size )
{
if( type == CV_8UC1 )
floodFill_CnIR(img, seedPoint, nv_buf.b[0], &comp, flags, &buffer);
else if( type == CV_8UC3 )
floodFill_CnIR(img, seedPoint, Vec3b(nv_buf.b), &comp, flags, &buffer);
else if( type == CV_32SC1 )
floodFill_CnIR(img, seedPoint, nv_buf.i[0], &comp, flags, &buffer);
else if( type == CV_32FC1 )
floodFill_CnIR(img, seedPoint, nv_buf.f[0], &comp, flags, &buffer);
else if( type == CV_32SC3 )
floodFill_CnIR(img, seedPoint, Vec3i(nv_buf.i), &comp, flags, &buffer);
else if( type == CV_32FC3 )
floodFill_CnIR(img, seedPoint, Vec3f(nv_buf.f), &comp, flags, &buffer);
else
CV_Error( CV_StsUnsupportedFormat, "" );
if( rect )
*rect = comp.rect;
return comp.area;
}
}
if( mask.empty() )
{
Mat tempMask( size.height + 2, size.width + 2, CV_8UC1 );
tempMask.setTo(Scalar::all(0));
mask = tempMask;
}
else
{
CV_Assert( mask.rows == size.height+2 && mask.cols == size.width+2 );
CV_Assert( mask.type() == CV_8U );
}
memset( mask.ptr(), 1, mask.cols );
memset( mask.ptr(mask.rows-1), 1, mask.cols );
for( i = 1; i <= size.height; i++ )
{
mask.at(i, 0) = mask.at(i, mask.cols-1) = (uchar)1;
}
if( depth == CV_8U )
for( i = 0; i < cn; i++ )
{
ld_buf.b[i] = saturate_cast(cvFloor(loDiff[i]));
ud_buf.b[i] = saturate_cast(cvFloor(upDiff[i]));
}
else if( depth == CV_32S )
for( i = 0; i < cn; i++ )
{
ld_buf.i[i] = cvFloor(loDiff[i]);
ud_buf.i[i] = cvFloor(upDiff[i]);
}
else if( depth == CV_32F )
for( i = 0; i < cn; i++ )
{
ld_buf.f[i] = (float)loDiff[i];
ud_buf.f[i] = (float)upDiff[i];
}
else
CV_Error( CV_StsUnsupportedFormat, "" );
uchar newMaskVal = (uchar)((flags & 0xff00) == 0 ? 1 : ((flags >> 8) & 255));
if( type == CV_8UC1 )
floodFillGrad_CnIR(
img, mask, seedPoint, nv_buf.b[0], newMaskVal,
Diff8uC1(ld_buf.b[0], ud_buf.b[0]),
&comp, flags, &buffer);
else if( type == CV_8UC3 )
floodFillGrad_CnIR(
img, mask, seedPoint, Vec3b(nv_buf.b), newMaskVal,
Diff8uC3(ld_buf.b, ud_buf.b),
&comp, flags, &buffer);
else if( type == CV_32SC1 )
floodFillGrad_CnIR(
img, mask, seedPoint, nv_buf.i[0], newMaskVal,
Diff32sC1(ld_buf.i[0], ud_buf.i[0]),
&comp, flags, &buffer);
else if( type == CV_32SC3 )
floodFillGrad_CnIR(
img, mask, seedPoint, Vec3i(nv_buf.i), newMaskVal,
Diff32sC3(ld_buf.i, ud_buf.i),
&comp, flags, &buffer);
else if( type == CV_32FC1 )
floodFillGrad_CnIR(
img, mask, seedPoint, nv_buf.f[0], newMaskVal,
Diff32fC1(ld_buf.f[0], ud_buf.f[0]),
&comp, flags, &buffer);
else if( type == CV_32FC3 )
floodFillGrad_CnIR(
img, mask, seedPoint, Vec3f(nv_buf.f), newMaskVal,
Diff32fC3(ld_buf.f, ud_buf.f),
&comp, flags, &buffer);
else
CV_Error(CV_StsUnsupportedFormat, "");
if( rect )
*rect = comp.rect;
return comp.area;
}
//简化版原型:
int cv::floodFill( InputOutputArray _image,
InputOutputArray _mask,
Point seedPoint,
Scalar newVal,
Rect* rect = 0,
Scalar loDiff = Scalar(),
Scalar upDiff = Scalar(),
int flags = 4 )
参数详解:两个版本唯一区别就是是否有第二个参数,其他参数都一样
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
#include
using namespace cv;
int main() {
Mat srcImage = imread("1.jpg");
imshow("原始图", srcImage);
Rect ccomp;
floodFill(srcImage, Point(300, 300), Scalar(0, 100, 55), &ccomp, Scalar(10, 10, 10), Scalar(10, 10, 10));
imshow("效果图", srcImage);
waitKey(0);
return(0);
}
运行结果:
#include
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;//新的重新绘制的像素值
//===============【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;
int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;
//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0
int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
//随机生成BGR值
int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
int g = (unsigned)theRNG() & 255;
int r = (unsigned)theRNG() & 255;
Rect ccomp;//定义重绘区域的最小边界矩阵区域
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);
imshow("mask", g_maskImage);
}
else {
area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
}
imshow("效果图", dst);
cout << area << " 个像素被重新绘制\n";
}
//main()函数
int main(int argc, char** argv) {
//载入原图
g_srcImage = imread("1.jpg", 1);
if (!g_srcImage.data) {
printf("读取g_srcImage错误!\n");
return false;
}
g_srcImage.copyTo(g_dstImage);//复制原图到目标图
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转为灰度图到g_grayImage
g_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);//用原图尺寸初始化掩膜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 == 27) {
cout << "程序退出........、\n";
break;
}
//根据按键不同进行不同的操作
switch ((char)c) {
//如果键盘1被按下,效果图在灰度图和彩色图之间转换
case '1':
if (g_bIsColor) {//若原来为彩色图,转换为灰度图,并将掩膜mask所有元素设置为0
cout << "键盘‘1’按下,切换彩色/灰度模式,当前操作将【彩色模式】切换为【灰度模式】" << endl;
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);//将mask所有元素设置为0
g_bIsColor = false;
}
else {
cout << "键盘‘1’按下,切换彩色/灰度模式,当前操作将【灰度模式】切换为【彩色模式】" << endl;
g_srcImage.copyTo(g_dstImage);
g_maskImage = Scalar::all(0);
g_bIsColor = true;
}
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;
case '3'://如果键盘3被按下,恢复原始图像
cout << "按下键盘‘3’,恢复原始图像\n";
g_srcImage.copyTo(g_dstImage);
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);
break;
case '4':
cout << "键盘‘4’被按下,使用空范围的漫水填充\n";
g_nFillMode = 0;
break;
case '5':
cout << "键盘‘5’被按下,使用渐变、固定范围的漫水填充\n";
g_nFillMode = 1;
break;
case '6':
cout << "键盘‘6’被按下,使用渐变、浮动范围的漫水填充\n";
g_nFillMode = 2;
break;
case '7':
cout << "键盘‘7’被按下,操作标识符的低八位使用4位的连接模式\n";
g_nConnectivity = 4;
break;
case '8':
cout << "键盘‘8’被按下,操作标识符的低八位使用8为的连接模式\n";
g_nConnectivity = 8;
break;
}
}
return 0;
}
运行结果:(部分截图)
<1>原始图:
<2>运行截图1
<2>运行截图2
<3>运行截图3