用GrabCut算法分割图像
【实现】
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
//定义一个带边框的矩形
//矩形外部的像素会被标记为背景
cv::Rect rectangle(5, 70, 260, 120);
cv::Mat result;//分割结果(四种可能的值)
cv::Mat image = imread("test.jpg");
cv::Mat bgModel, fgModel;//模型(内部使用)
//GrabCut分割算法
cv::grabCut(image, //输入图像
result, //分割结果
rectangle, //包含前景的矩形
bgModel, fgModel, //模型
5, //迭代次数
cv::GC_INIT_WITH_RECT); //使用矩形(带边框)
//取得标记为“可能属于前景”的像素
cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);
//生成输出图像
cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
image.copyTo(foreground, result);//不复制背景像素
//用“按位与”运算检查第一位
result = result & 1;//如果是前景像素,结果为1
cv::namedWindow("result");
cv::imshow("result",result);
cv::waitKey(0);
}
【实现原理】
在上例中,只需要指定一个包含前景物体(城堡)的矩形,GrabCut 算法就能提取出它。此外,还可以把输入图像中的几个特定像素赋值为 cv::GC_BGD(表示明确属于背景的像素,例如本例中矩形之外的像素)和 cv::GC_FGD(表示明确属于前景的像素,本例中没有这种像素),以掩码图像的形式提供这些值,作为 cv::grabCut 函数的第二个参数。同时要把输入模式标志指定为 GC_INIT_WITH_MASK。获得这些输入标签的方法有很多种,例如可以提示用户在图像中交互式 地标记一些元素。当然,将这两种输入模式结合使用也未尝不可。
利用输入信息,GrabCut 算法通过以下步骤进行背景/前景分割。首先,把所有未标记的像素 临时标为前景(cv::GC_PR_FGD)。基于当前的分类情况,算法把像素划分为多个颜色相似的组 (即 K 个背景组和 K 个前景组)。下一步是通过引入前景和背景像素之间的边缘,确定背景/前景 的分割,这将通过一个优化过程来实现。在此过程中,将试图连接具有相似标记的像素,并且避 免边缘出现在强度相对均匀的区域。使用 Graph Cuts 算法可以高效地解决这个优化问题,它寻找 最优解决方案的方法是:把问题表示成一幅连通的图形,然后在图形上进行切割,以形成最优的 形态。分割完成后,像素会有新的标记。然后重复这个分组过程,找到新的最优分割方案,如此 反复。因此,GrabCut 算法是一个逐步改进分割结果的迭代过程。根据场景的复杂程度,找到最 佳方案所需的迭代次数各不相同(如果情况简单,迭代一次就足够了)。
这解释了函数中用来表示迭代次数的参数。结合代码看,原意应该是:先把参数传递给函数, 函数返回时会修改参数的值。因此,如果希望通过执行额外的迭代过程来改进分割结果,可以在调用函数时重复使用上次运行的模型。