【从零学习OpenCV 4】分割图像——Grabcut图像分割

经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。 |
Grabcut算法是重要的图像分割算法,其使用高斯混合模型估计目标区域的背景和前景。该算法通过迭代的方法解决了能量函数最小化的问题,使得结果具有更高的可靠性。OpenCV 4提供了利用Grabcut算法分割图像的grabCut()函数,该函数的函数原型在代码清单8-21中给出。
代码清单8-21 grabCut()函数原型
void cv::grabCut(InputArray img,
InputOutputArray mask,
Rect rect,
InputOutputArray bgdModel,
InputOutputArray fgdModel,
int iterCount,
int mode = GC_EVAL
)
img:输入的待分割图像,数据类型为CV_8U的三通道图像。 mask:用于输入、输出的CV_8U单通道掩码图像,图像中像素值的取值范围以及含义在表8-4给出。 rect:包含对象的ROI区域,该参数仅在mode == GC_INIT_WITH_RECT时使用。 bgdModel:背景模型的临时数组。 fgdModel:前景模型的临时数组。 iterCount:算法需要进行的迭代次数。 mode:分割模式标志,该参数值可选择范围以及含义在表8-5给出。
表8-4 grabCut()函数中掩码图像像素值范围和含义
标志参数 | 简记 | 含义 |
---|---|---|
GC_BGD | 0 | 明显为背景的像素 |
GC_FGD | 1 | 明显为前景(对象)的像素 |
GC_PR_BGD | 2 | 可能为背景的像素 |
GC_PR_FGD | 3 | 可能为前景(对象)的像素 |
表8-5 grabCut()函数中分割模式标志取值范围和含义
标志参数 | 简记 | 含义 |
---|---|---|
GC_INIT_WITH_RECT | 0 | 使用提供的矩形初始化状态和掩码,之后根据算法进行迭代更新 |
GC_INIT_WITH_MASK | 1 | 使用提供的掩码初始化状态,可以组合GC_INIT_WITH_RECT和GC_INIT_WITH_MASK。然后,使用GC_BGD自动初始化ROI外部的所有像素。 |
GC_EVAL | 2 | 算法应该恢复 |
GC_EVAL_FREEZE_MODEL | 3 | 只使用固定模型运行grabCut算法(单次迭代) |
该函数实现了GrabCut图像分割算法,函数的第一个参数是待分割的输入图像,要求是CV_8U的三通道彩色图像。第二个参数是掩码矩阵,该参数既用于输入又用于输出,当最后一个参数设置为GC_INIT_WITH_RECT时,该矩阵会被设置为初始掩码,掩码矩阵中具有4个可选择的参数,分别是0(GC_BGD)表示明显为背景的像素、1(GC_FGD)表示明显为前景或者对象的像素、2(GC_PR_BGD)表示疑似背景的像素、3(GC_PR_FGD)表示疑似前景或者对象的像素。最后图像的分割结果也是通过分析掩码矩阵中每个像素的数值进行提取。函数第三个参数是需要进行分割去ROI区域,在ROI区域的外部会被标记为“明显的背景”区域,该参数尽在mode == GC_INIT_WITH_RECT时使用。函数第四个和第五个参数分别是背景模型、前景模型的临时数组,需要注意的是在处理同一图像时,请勿对它进行修改。函数第六个参数是算法进行迭代的次数。函数最后一个参数是分割模式标志,可以选择的参数及其含义在表8-5给出。
为了了解该函数的使用方法以及对图像的分割效果,在代码清单8-22中给出了通过grabCut()函数对图像进行分割的示例程序。程序中首先在原图像中选择ROI矩形区域,之后利用grabCut()函数对该区域分割,计算前景和背景,最后将掩码矩阵中明显是前景和疑似前景的像素点全部输出,程序运行结果如图8-14所示。需要说明的是程序中为了保证绘制矩形框不对图像分割产生影响,在绘制矩形框时对原图像进行了深拷贝。
代码清单8-22 myGrabCut.cpp利用grabCuts方法图像分割
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("lena.png");
if (!img.data) //防止错误读取图像
{
cout<<"读取图像错误,请确认图像文件是否正确" << endl;
return 0;
}
//绘制矩形
Mat imgRect;
img.copyTo(imgRect); //备份图像,方式绘制矩形框对结果产生影响
Rect rect(80, 30, 340, 390);
rectangle(imgRect, rect, Scalar(255, 255, 255),2);
imshow("选择的矩形区域", imgRect);
//进行分割
Mat bgdmod = Mat::zeros(1, 65, CV_64FC1);
Mat fgdmod = Mat::zeros(1, 65, CV_64FC1);
Mat mask = Mat::zeros(img.size(), CV_8UC1);
grabCut(img, mask, rect, bgdmod, fgdmod, 5, GC_INIT_WITH_RECT);
//将分割出的前景绘制回来
Mat result;
for (int row = 0; row < mask.rows; row++)
{
for (int col = 0; col < mask.cols; col++)
{
int n = mask.at<uchar>(row, col);
//将明显是前景和可能是前景的区域都保留
if (n == 1 || n == 3)
{
mask.at<uchar>(row, col) = 255;
}
//将明显是背景和可能是背景的区域都删除
else
{
mask.at<uchar>(row, col) = 0;
}
}
}
bitwise_and(img, img, result, mask);
imshow("分割结果", result);
waitKey(0);
return 0;
}

图8-14 myGrabCut.cpp程序中选择的区域和分割结果