OpenCV实现漫水填充(待完善)
定理:
用一种特定的颜色填充连通区域,通过设置可联通像素的上下限以及连通方式来达到不同的填充效果的方法。
自动选中和种子相连的区域,接着将该区域替换成指定的颜色。也可以用来从输入图像获取掩码区域,掩码区域会加速区域,或只处理掩码指定的像素点。
函数调用:
int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint,Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
- 第一个参数,InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。
- 第二个参数, InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
- 第三个参数,Point类型的seedPoint,漫水填充算法的起始点。
- 第四个参数,Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
- 第五个参数,Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
- 第六个参数,Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
- 第七个参数,Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。
- 第八个参数,int类型的flags,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
- 低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
- 高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
- FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
- FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
算法实现:
- 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
实现代码:扫描线种子填充算法的基本过程如下:当给定种子点(x, y)时,首先分别向左和向右两个方向填充种子点所在扫描线上的位于给定区域的一个区段,同时记下这个区段的范围[xLeft, xRight],然后确定与这一区段相连通的上、下两条扫描线上位于给定区域内的区段,并依次保存下来。反复这个过程,直到填充结束。
扫描线种子填充算法可由下列四个步骤实现:
(1) 初始化一个空的栈用于存放种子点,将种子点(x, y)入栈;
(2) 判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线;
(3) 从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;
(4) 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,从xLeft开始向xRight方向搜索,若存在非边界且未填充的像素点,则找出这些相邻的像素点中最右边的一个,并将其作为种子点压入栈中,然后返回第(2)步;
//实现漫水填充 在灰度图的基础上 彩色图的色差不会计算 囧
//尝试实现一下扫描填充法
void SearchNewLine(InputArray src,stack& stack, int xleft, int xright, int y, Scalar seedPoint,Scalar newVal, Scalar loDiff, Scalar upDiff);
void method_one(InputOutputArray src, Point seedPoint, Scalar newVal, Scalar loDiff, Scalar upDiff)
{ //src为原图像 seedPoint为种子,newVal为设置的目标颜色,loDiff为当前像素值与领域像素之差负值的max,
//upDiff是之差正值的max
std::stack stack;//利用堆栈来存储
stack.push(seedPoint);
int xleft = 0;
int xright = 0;//记录边界信息
int rows = src.rows();
int cols = src.cols();
int channels = src.channels();
Mat _src = src.getMat();//得到目标信息头
Scalar pointcurrent = _src.data[seedPoint.y *cols + seedPoint.x];
int _pointcurrent = pointcurrent[0];//单通道
while (!stack.empty())
{
Point current = stack.top();//取出栈顶元素,作为当前循环的种子
int x = current.x;
int y = current.y;//(x,y)表示坐标 x是横坐标 y是纵坐标
stack.pop();//删除栈顶元素
//向左填充
while(x>=0)
{
Scalar pointleft = _src.data[y*cols + x];
int _pointleft = pointleft[0];
int difference = _pointcurrent - _pointleft;
if ((difference <= upDiff[0] && difference >= 0) || (difference >= -loDiff[0] && difference <= 0))
{
_src.data[y*cols + x] = newVal[0];
}
else
{
xleft = x;
break;
}
x--;
}
x = current.x;//重新设置
//向右填充
while (x<=cols-1 )
{
if (x == cols - 1)//已经到达有边界
{
xright = x;
break;
}
else {
Scalar pointright = _src.data[y*cols + (++x)];
int _pointright = pointright[0];
int difference = (_pointcurrent - _pointright);
if ((difference <= upDiff[0] && difference >= 0) || (difference >= -loDiff[0] && difference <=0))
{
_src.data[y*cols + x] = newVal[0];
}
else
{
xright = x;//这里重新设置了xleft xright的边界值
break;
}
}
}
//扫描上一行
SearchNewLine(src,stack,xleft,xright,y-1,pointcurrent,newVal,loDiff,upDiff);
//扫描下一行
SearchNewLine(src,stack, xleft, xright, y +1,pointcurrent, newVal, loDiff, upDiff);
}
}
void SearchNewLine(InputArray src,stack& stack,int xleft,int xright,int y,Scalar seedPoint,Scalar newVal,Scalar loDiff,Scalar upDiff)
{
Mat _src = src.getMat();
if ((y<0) || (y>_src.rows - 1))//处理越界的情况 纵坐标
return;
if ((xleft <0 )|| (xright > _src.cols - 1))//横坐标越界
return;
int x = xleft;
int targetx = x;
bool flag = false;
while (x <= xright)
{
Scalar current = _src.data[y*_src.cols + x];
if ((((current[0] - seedPoint[0]) >= -loDiff[0]) && ((current[0] - seedPoint[0]) <= 0))
|| (((current[0] - seedPoint[0]) <= upDiff[0]) && ((current[0] - seedPoint[0]) >= 0)))
{
if (current[0] != seedPoint[0])
{
targetx = x;
flag = true;
}
}
else
{
if (flag)
{
Point another = Point(targetx, y);
stack.push(another);
flag = false;
}
}
x++;
}
//考虑边界因素
if (flag)
{
Point another = Point(targetx, y);
stack.push(another);
flag = false;
}
return;
}
int main()
{
Mat srcimage = imread("C:\\Users\\l\\Desktop\\2.jpg",0);
Mat srcimage1 = srcimage.clone();
imshow("原始图", srcimage);
Rect ccomp;
floodFill(srcimage1, Point(50, 50), 255, &ccomp, 10,10);
imshow("漫水填充结果", srcimage1);//实现OpenCV自带函数的漫水填充效果
//调用扫描填充法
Mat srcimage2 = srcimage.clone();
method_one(srcimage2,Point(50,50),255,10,10);
imshow("方法一的结果", srcimage2);
waitKey(0);
return 0;
}
结果相差较大,还需努力!!!如果有同道中人知道如何改进,还望给予指导!感激不尽!