GrabCut图像分割算法
基本概念
OpenCV中的GrabCut算法是Graphcut算法的改进, Graphcut是一种直接基于图割算法的图像分割技术, 仅仅需要确认前景和背景输入, 该算法就可以完成前景和背景的最优分割, 算法依据《“GrabCut” - Interactive Foreground Extraction using Iterated Graph Cuts》这篇文章来实现的。该算法利用了图像中的纹理(颜色)信息和边界(反差)信息, 只要少量的用户交互操作即可得到比较好的分割结果, 和分水岭算法比较相似, 但是计算速度比较慢, 得到的结果比较精确。如果要从静态图像中提取前景物体(如从一个图像剪切物体到另一个图像), 采用GrabCut算法是最好的选择。
用法很简单: 只需输入一幅图像, 并对一些像素做属于背景或属于前景的标记, 算法会根据这个局部标记, 计算出整个图像中前景和背景的分割线。
参考
参考源码
D:\opencv\sources\modules\imgproc\src\grabcut.cpp
参考例程
D:\opencv\sources\samples\cpp\grabcut.cpp
函数原型之GrabCut算法—grabCut()
void grabCut( InputArray img, InputOutputArray mask, Rect rect,
InputOutputArray bgdModel, InputOutputArray fgdModel,
int iterCount, int mode = GC_EVAL );
- 1
- 2
- 3
- 4
- img: 待分割原图像, 需为8位三通道彩色图像
-
mask: 8位单通道掩码图像, 如果使用掩码进行初始化, 那么mask保存掩码信息, 在执行分割的时候, 也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数, 在处理结束之后,mask中会保存结果。
Mask只能取4种可能的值:
GC_BGD: 表示明确属于背景的像素
GC_FGD: 表示明确属于前景的像素
GC_PR_BGD: 表示可能属于背景的像素
GC_PR_FGD: 表示可能属于前景的像素
如果没有手动标记GC_BGD或者GC_FGD, 那么结果只会有GC_PR_BGD或 GC_PR_FGD -
rect: 包含分割对象的矩形ROI, 矩形外部的像素为背景, 矩形内部的像素为前景,当参数mode=GC_INIT_WITH_RECT使用
- bgdModel: 背景模型(内部使用)
- fgdModel: 前景模型(内部使用)
- iterCount: 迭代次数, 必须大于0
- mode: 用于指示grabCut函数进行什么操作, 可选的值有:
GC_INIT_WITH_RECT: 用矩形框初始化GrabCut
GC_INIT_WITH_MASK: 用掩码图像初始化GrabCut
GC_EVAL: 执行分割
相关所用其他函数
比较两幅图像对应位置像素值
将矩阵特定区域设置为特定值
应用实例
代码
///利用Rect做GrabCut分割
Mat src = imread("D:\\1\\bird.jpg");
Rect rect(84, 84, 406, 318);
Mat result;
Mat bgModel, fgModel;
grabCut(src, result, rect, bgModel, fgModel, 1, GC_INIT_WITH_RECT);
imshow("grab", result);
compare(result, GC_PR_FGD, result, CMP_EQ);
imshow("result", result);
Mat foreground(src.size(), CV_8UC3, Scalar(255, 255, 255));
src.copyTo(foreground, result);
imshow("foreground", foreground);
waitKey(0);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
///利用mask做GrabCut分割
Mat src = imread("bird.jpg");
Rect rect;
Mat bgModel, fgModel;
Mat result(src.size(), CV_8U, Scalar(0));
Mat ROI(result(Rect(84, 84, 406, 318)));
ROI.setTo(GC_PR_FGD);
grabCut(src, result, rect, bgModel, fgModel, 1, GC_INIT_WITH_MASK);
imshow("grab", result);
compare(result, GC_PR_FGD, result, CMP_EQ);
imshow("result", result);
Mat foreground(src.size(), CV_8UC3, Scalar(255, 255, 255));
src.copyTo(foreground, result);
imshow("foreground", foreground);
waitKey(0);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
运行结果
代码详解
compare(result, GC_PR_FGD, result, CMP_EQ);
- 1
threshold(result, result, 2, 255, CV_THRESH_BINARY);
- 1
//result = result&1;
- 1
这三段代码实现的功能相同
漫水填充算法—floodFill
基本概念
FloodFill漫水填充算法是在很多图形绘制软件中常用的填充算法, 通常来说是自动选中与种子像素相连的区域, 利用指定颜色进行区域颜色填充, 常用于标记或分离图像的一部分, 以便做进一步分析和处理。Windows画图工具中的油漆桶功能和Photoshop的魔术棒选择工具, 都是通过FloodFill漫水填充的改进和延伸。
原理很简单: 就是从一个点开始遍历附近像素点, 填充成新的颜色, 直到封闭区域内所有像素点都被填充成新颜色为止。FloodFill填充的实现方法常见的有4邻域像素填充法、8邻域像素填充法、基于扫描线的像素填充方法等。
函数原型之漫水填充算法—floodFill()
int floodFill( InputOutputArray image, InputOutputArray mask,
Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,
Scalar loDiff = Scalar(), Scalar upDiff = Scalar(),
int flags = 4 );
- 1
- 2
- 3
- 4
- 5
- image: 输入/输出单通道或3通道8位或浮点图像
-
mask: 操作掩码, 应为8位单通道且长和宽比输入图像大两个像素点的图像, 漫水填充不会填充mask的非零像素区域(可以用边缘检测算子输出来防止填充到边界),
mask中与输入图像(x,y)像素点相对应的点坐标为(x+1,y+1) -
seedPoint: 种子点, 漫水填充算法起始点
- newVal: 像素点被染颜色的值, 即在重绘区域像素的新值
- rect: Rect*类型,默认值0, 可选参数, 用于设置floodFill函数将要重绘区域的最小边界矩形区域
- loDiff: 默认值Scalar(), 表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)最大值
- upDiff: 默认值Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)最大值
- flags: 操作标志符, 包含三部分:
低八位(0~7位): 用于控制算法的连通性, 可取4(默认)或8
高八位(16~32位): 可以为0或者如下两种标志符的组合:
FLOODFILL_FIXED_RANGE: 设置此标志符会考虑当前像素与种子像素之间的 差, 否则就考虑当前像素与其相邻像素的差
FLOODFILL_MASK_ONLY: 设置此标志符, 函数不会去填充改变原始图像(也就是忽略第三个参数newVal), 而是去填充掩码图像mask
中间八位(8~15位): 用于指定填充掩码图像的值, 但是如果中间八位为0则掩码用1来填充
应用实例
代码
Mat srcImg = imread("1.png");
imshow("src", srcImg);
Rect rect;
floodFill(srcImg, Point(346, 282), Scalar(255, 0, 0), &rect, Scalar(10, 10, 10), Scalar(10, 10, 10));
imshow("result", srcImg);
waitKey(0);
- 1
- 2
- 3
- 4
- 5
- 6
- 7