In this chapter, we will cover:
Eroding and dilating images using morphological filters
Erosion and dilation are the most fundamental morphological operators.
The fundamental instrument in mathematical morphology is the structuring element(结构元素).A structuring element is simply defined as a configuration of pixels (a shape) on which an origin is defined (also called anchor point).数学形态学最基本的工具是结构元素。结构元素定义为像素的结构(形状)以及一个锚点。
形态学中,我们一般规定,高像素(白色)表示前景物体,用低像素(黑色)表示背景。一张图像A,对其取反(255-x)得到图像B,那么说A是B的补(B是A的补).补(Complement).
1 | cv::Mat image= cv::imread("binary.bmp"); |
图书上都有,代码也是摘自书上,通俗说:腐蚀使图像变瘦,膨胀使其变胖。
前面说过,要进行腐蚀和膨胀操作,我们用一个结构元素,结构元素有很多种,比如3*3矩阵,半径为3的圆,当anchor point对准图像中像素(given pixel)(相当于把结构元素对准后贴上去),anchor point周围的几个元素相应的也对其到图像中这个given pixel周围的像素上。那么是怎么进行操作的呢?
- 腐蚀:找出结构元素与原图像重合的所有像素中像素值最小的那个值,用这个值替代这个given pixel,也就是和anchor point重合的这个点。
- 膨胀:··········································最大的·····································································。
see more in here.
所以腐蚀可以去除独立的噪声点,而膨胀可以填充物体的一些“孔洞”。
OpenCV默认使用3*3的方形结构元素。当函数的第三个参数被设置为空矩阵(即cv::Mat())时,这个默认的结构元素被使用。也可以自己定义结构元素。1
2cv::Mat element(7, 7, CV_8U, cv::Scalar(1));
cv::erode(image, eroded, element);
还可以对一幅图像反复应用同一个结构元素来达到上述同样的效果。1
cv::erode(image, eroded, cv::Mat(), cv::Point(-1, -1), 3); //腐蚀图像三次
cv::Point(-1, -1)代表矩阵的anchor point(默认参数),可以通过它修改anchor point位置。
对于二值图像来说,使用一个结构元素来腐蚀前景也可以视为对图像背景部分的膨胀运算。或者用更正式的语言来表述:
- The erosion of an image is equivalent to the complement of the dilation of the
complement image.(对图像的腐蚀运算等于对图像complement的膨胀运算的complement)- The dilation of an image is equivalent to the complement of the erosion of the
complement image.(对图像的膨胀运算等于对图像complement的腐蚀运算的complement)
Opening and closing images using morphological filters
如果有图像处理基础,应该知道:
开运算 == 先 腐蚀 + 后 膨胀
闭运算 == 先 膨胀 + 后 腐蚀
(我是这么记的,腐蚀变瘦,膨胀变胖,那么我们一般说一个人张开了,也就是长胖了,所以开运算最后要“开”,自然后面的是膨胀,那前面自然就是腐蚀,记住一个另外一个自然也就ok了)
所以OpenCV也提供这样的函数:1
2
3
4
5
6
7cv::Mat element5(5,5,CV_8U,cv::Scalar(1));
//闭运算
cv::Mat closed;
cv::morphologyEx(image,closed,cv::MORPH_CLOSE,element5);
//开运算
cv::Mat opened;
cv::morphologyEx(image,opened,cv::MORPH_OPEN,element5);
See more in cv::morphologyEx. morphological filters不仅仅值提供开运算闭运算,OpenCV还有如下(具体可以参见上述链接):
- MORPH_OPEN - an opening operation
- MORPH_CLOSE - a closing operation
- MORPH_GRADIENT - a morphological gradient
- MORPH_TOPHAT - “top hat”
- MORPH_BLACKHAT - “black hat
开运算闭运算除了用上面那个完成,还可以直接分解成腐蚀和膨胀来完成:1
2
3
4
5//下面这个是闭运算,开运算把顺序倒过来就行
// dilate original image
cv::dilate(image,result,cv::Mat());
// in-place erosion of the dilated image
cv::erode(result,result,cv::Mat());
对一幅图像多次使用相同的开运算(或闭运算)是没有效果的,在第一次开运算如果填充了图像的洞之后,再次使用相同的滤波不会对图像产生任何变化。用数学的术语讲,这些运算是等幂的(Idempotent).
Detecting edges and corners using morphological filters
code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140// [20151115]OpenCV_Chapter5.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <opencv2/opencv.hpp>
class MorphoFeatures
{
public:
MorphoFeatures();
~MorphoFeatures(){}
void setThreshold(int _threshold);
void applyThreshold(cv::Mat &result);
cv::Mat getEdges(const cv::Mat &image);
cv::Mat getCorners(const cv::Mat &image);
void drawOnImage(cv::Mat &binary, cv::Mat &image);
private:
int threshold;
cv::Mat cross;
cv::Mat square;
cv::Mat diamond;
cv::Mat x;
};
MorphoFeatures::MorphoFeatures() : threshold(-1), cross(5, 5, CV_8U, cv::Scalar(0)),
square(5, 5, CV_8U, cv::Scalar(1)), diamond(5, 5, CV_8U, cv::Scalar(1)), x(5, 5, CV_8U, cv::Scalar(0))
{
//Creating the cross-shaped structuring element
for (int i = 0; i < 5; ++i)
{
cross.at<uchar>(2, i) = 1;
cross.at<uchar>(i, 2) = 1;
}
//Creating the diamond-shaped structuring element
diamond.at<uchar>(0, 0) = 0;
diamond.at<uchar>(0, 1) = 0;
diamond.at<uchar>(1, 0) = 0;
diamond.at<uchar>(4, 4) = 0;
diamond.at<uchar>(3, 4) = 0;
diamond.at<uchar>(4, 3) = 0;
diamond.at<uchar>(4, 0) = 0;
diamond.at<uchar>(4, 1) = 0;
diamond.at<uchar>(3, 0) = 0;
diamond.at<uchar>(0, 4) = 0;
diamond.at<uchar>(0, 3) = 0;
diamond.at<uchar>(1, 4) = 0;
//Creating the x-shaped structuring element
for (int i = 0; i < 5; ++i)
{
x.at<uchar>(i, i) = 1;
x.at<uchar>(4 - i, i) = 1;
}
}
void MorphoFeatures::setThreshold(int _threshold)
{
threshold = _threshold;
}
void MorphoFeatures::applyThreshold(cv::Mat &result)
{
if (threshold > 0)
{
cv::threshold(result, result, threshold, 255, CV_THRESH_BINARY);
}
}
cv::Mat MorphoFeatures::getEdges(const cv::Mat &image)
{
cv::Mat result;
cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());
applyThreshold(result);
return result;
}
cv::Mat MorphoFeatures::getCorners(const cv::Mat &image)
{
cv::Mat result;
cv::dilate(image, result, cross);
cv::erode(result, result, diamond);
cv::Mat result2;
cv::dilate(image, result2, x);
cv::erode(result2, result2, square);
cv::absdiff(result, result2, result);
applyThreshold(result);
return result;
}
void MorphoFeatures::drawOnImage(cv::Mat &binary, cv::Mat &image)
{
// cv::Mat_<uchar>::iterator it = binary.begin<uchar>();
// cv::Mat_<uchar>::iterator itend = binary.end<uchar>();
// for (int i = 0; it != itend; ++it, ++i)
// {
// if (*it) //There is a mistake in book, change `!*it` to `*it`
// {
// cv::circle(image, cv::Point(i % image.step, i / image.step), 5, cv::Scalar(255, 0, 0));
// }
// }
for (int j = 0; j < binary.rows; ++j)
{
uchar *data = binary.ptr<uchar>(j);
for (int i = 0; i < binary.cols; ++i)
{
if (*data++)
{
cv::circle(image, cv::Point(i, j), 5, cv::Scalar(255, 0, 0));
}
}
}
}
int main()
{
MorphoFeatures morpho;
morpho.setThreshold(40);
cv::Mat edges;
cv::Mat image = cv::imread("../../resource/building1.jpg", 0);
edges = morpho.getEdges(image);
cv::Mat corners;
corners = morpho.getCorners(image);
morpho.drawOnImage(corners, image);
cv::imshow("corners", image);
cv::imshow("edge", edges);
cv::waitKey();
system("pause");
return 0;
}
MorphoFeatures::getEdges`的原理
腐蚀膨胀的原理前面已经提及,检测图像边缘可以通过膨胀与腐蚀的结果做差值得到(膨胀相当于扩大物体的地盘,腐蚀相当于缩小,插值刚好就是边缘地带)。This is exactly what the cv::morphologyEx function is doing when the cv::MORPH_GRADIENT argument is inputted.膨胀减原图和原图减腐蚀得到的边缘比较薄。
MorphoFeatures::getCorners的原理:
假设原图如下,1表示物体,0表示背景:1
2
3
4
5
6
7
8
9
10图1
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
代码中的cross结构元素如下:1
2
3
4
50 0 1 0 0
0 0 1 0 0
1 1 1 1 1
0 0 1 0 0
0 0 1 0 0
为了便于叙述,称锚点为X(这里锚点元素默认在结构元素中心,即(2,2)位置),当cross的X与原图位置(2, 2)位置重合时,按照膨胀的原理,则原图变成下面这样:1
2
3
4
5
6
7
8
9
10图2
0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0
1 1 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
依次类推,当X与原图位置(Xi, 2)位置重合时,则原图变成下面这样:1
2
3
4
5
6
7
8
9
10图3
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
1 1 1 1 1 1 1 1 1
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
可以看出,将上侧两个角点的位置空出,仍然是先前那样,当X与(3, 2)位置重合时(进行到这一步),则原图变成下面这样:1
2
3
4
5
6
7
8
9
10图4
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
依次类推,全图被膨胀后,就变成下面这样了:1
2
3
4
5
6
7
8
9
10图5
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
接下来就是腐蚀操作了,代码中腐蚀元素diamond如下所示:1
2
3
4
50 0 1 0 0
0 1 0 1 0
1 0 0 0 1
0 1 0 1 0
0 0 1 0 0
同样,锚点元素位置默认是中心,记描点元素为Y,同理,当Y和原图像(0, 2)位置重合时,该位置元素变成0,依次类推,第一行元素在腐蚀后全为0,第二行元素也是0,当与(2, 2)重合时,就变成下面这样了:1
2
3
4
5
6
7
8
9
10图6
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
可以看到(2, 2)位置元素变为0,注意也许有人会疑惑,那接下来继续腐蚀,比如(2, 3)位置上面都是0,那岂不是被腐蚀后也要变成0?注意腐蚀是相对原图,在这里要看图5,图6是我们得到的图像(相当于我们再给图6填充元素,只是进行的(2, 3)这个位置,下面本来还没有元素(或者说有默认元素值),我只是为了演示才给他补充上去的),我们不是对图6做腐蚀操作,是对图5做腐蚀,所以要参照图5,从图5看,(2, 3)位置元素值不变。
最后我们得到腐蚀后的图像如下:1
2
3
4
5
6
7
8
9
10图7
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 0 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 1 1 1 1 1 0 0
0 0 0 1 1 1 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
四个角出来了,然后将cross和diamond旋转45度就得到X和square,用来检测45度旋转后角点。然后对两次结果做差值,取出角点特征。
Segmenting images using watersheds
书上的代码(有微微改动):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66class WatershedSegmenter
{
public:
void setMarkers(const cv::Mat &markerImage)
{
markerImage.convertTo(markers, CV_32S); //convert to image of ints
}
void process(const cv::Mat &image)
{
cv::watershed(image, markers);
}
// Return result in the form of an image
cv::Mat getSegmentation() {
cv::Mat tmp;
// all segment with label higher than 255
// will be assigned value 255
markers.convertTo(tmp, CV_8U);
return tmp;
}
cv::Mat getWatersheds() {
cv::Mat tmp;
// Each pixel p is transformed into
// 255p+255 before conversion
markers.convertTo(tmp, CV_8U, 255, 255);
return tmp;
}
private:
cv::Mat markers;
};
int main()
{
cv::Mat image = cv::imread("../../resource2/group.jpg");
cv::Mat binary = cv::imread("../../resource2/binary.jpg", 0);
cv::Mat foreground;
cv::erode(binary, foreground, cv::Mat(), cv::Point(-1, -1), 6);
cv::threshold(foreground, foreground, 200, 255, CV_THRESH_BINARY);
cv::Mat background;
cv::dilate(binary, background, cv::Mat(), cv::Point(-1, -1), 6);
cv::threshold(background, background, 1, 128, CV_THRESH_BINARY_INV);
cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0));
markers = foreground + background;
WatershedSegmenter segmenter;
segmenter.setMarkers(markers);
segmenter.process(image);
cv::Mat segmentation = segmenter.getSegmentation();
cv::Mat watersheds = segmenter.getWatersheds();
cv::imshow("image", image);
cv::imshow("foreground", foreground);
cv::imshow("background", background);
cv::imshow("markers", markers);
cv::imshow("segmentation", segmentation);
cv::imshow("watersheds", watersheds);
cv::waitKey();
return 0;
}

Figure 1: 分水岭算法的示意图
分水岭算法的最初版本:把图像视为拓扑结构的地图,那么均值区域对应的是被陡峭边缘包围的平坦盆地。由于这个算法过于简单,最初版本极有可能将图像过度分割为多个微小区域。(图像灰度分布不均,高低不平就会有这样的情况,导致插的分水岭太多了),所以OpenCV搞了算法的升级版,使用预定义的一组标记来引导对图像的分割。
见Fig 1所示,分水岭就相当于边界,当盆地里的水长满到两个盆地的水相遇时,加上分水岭(大坝)将他们拦截出,不让他们在一块,这样他们就各自是一块区域,这样也就是分割出两块区域。但是可能图像会有许多小盆地,导致过度分割。所以对像素进行标记,相同标记的的盆地汇聚时,不生成分水岭,避免过度分割。这便是cv::watershed函数所做工作,输入的标记图像得到更新,生成最终的分水岭分割。
标记图像(即代码中的私有成员变量markers)可以拥有任意数量的标签,未知像素的标签赋予0,-1被允许赋予分水岭上的像素。标记图像的类型是32位有符号整数,这是为了能够定义超过255种标签,这也是cv::watershed函数返回的格式。
这里我在敲代码遇到两个问题,按照书上的源代码,在我从作者给的网址上下载的原图像操作结果来看,无法做到与书上一样。后来我在前景操作后面加了这样一句代码:1
cv::threshold(foreground, foreground, 200, 255, CV_THRESH_BINARY);
问题被解决,根据我查看图像,发现不进行阈值操作,可能图像某些像素灰度值很小,看不出来,但不代表不存在,所以被当作标记传递进函数中,从而导致分割结果很差,与书上效果不一致,进行阈值操作后,foreground就只剩下两种灰度值,一种0,一种255,255为标记的前景物体,然后接下来的128标记的是背景物体,这样最后得到的结果就和书上一致了。
第二个问题就是刚开始我并没有使用这句代码1
markers.converTo(tmp, CV_8U);
直接使用cv::imshow来显示markers,然后显示出来的就是纯黑色,后来找了一下OpenCV官网文档上关于cv::imshow的说明,才算知道原因,文档里说:
- If the image is 8-bit unsigned, it is displayed as is
- If the image is 16-bit unsigned or 32-bit integer, the pixels are divided by 256. That is, the value range [0,255*256] is mapped to [0,255]
- If the image is 32-bit floating-point, the pixel values are multiplied by 255. That is, the value range [0,1] is mapped to [0,255]
可以看到将[0, 255*256]映射到[0, 255],而我们在代码中的标记值最大的是255,然后一个128,还有个0,按照映射规则,他们都映射到了0上面,所以自然而然的显示为黑色,使用convertTo将其转换到CV_8U即可解决问题。
同样的原理解释为啥WatershedSegmenter::getWatersheds,只能显示0~255,通过convertTo使其灰度值不为1全部超量程,然后被截断为255,这样只有原来是灰度值为0的仍然是0,所以显示出轮廓了。
不过有一点比较疑惑,我们可以在书上看到标记图像上白色标记点除了四头牛的位置有,其他位置也有,甚至比右下角那头趴着的牛的标记面积还大,可是为啥在后面没有给单独分割出来呢?按道理来说标记值不为0,也和周围的不一样,应该要建立分水岭的啊?
Extracting foreground objects with the GrabCut algorithm
代码实现
mode == GC_INIT_WITH_RECT的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37int main()
{
cv::Mat image = cv::imread("../../resource2/group.jpg");
// cv::Mat imageCopy = image.clone();
cv::Rect rectangle(10, 100, 380, 180);
cv::Mat result;
cv::Mat bgModel, fgModel;
cv::grabCut(image,
result,
rectangle,
bgModel, fgModel,
5,
cv::GC_INIT_WITH_RECT);
cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);
// for (int j = 0; j < result.rows; ++j)
// {
// uchar *data = result.ptr<uchar>(j);
// for (int i = 0; i < result.cols; ++i)
// {
// printf("%d ", *data++);
// }
// printf("\n");
// }
result = result & 1;
cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
image.copyTo(foreground, result);
cv::imshow("result", foreground);
cv::waitKey();
return 0;
}
mode == GC_INIT_WITH_MASK的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29int main()
{
cv::Mat image = cv::imread("../../resource2/group.jpg");
cv::Rect rectangle(10, 100, 380, 180);
cv::Mat result;
cv::Mat mask(image.size(), CV_8UC1, cv::Scalar(cv::GC_BGD));
cv::Mat ROI(mask, rectangle);
ROI = cv::Scalar(cv::GC_PR_FGD); //将矩形框位置的像素值变成3,告诉函数这些可能是前景像素
cv::Mat bgModel, fgModel;
cv::grabCut(image,
mask,
rectangle,
bgModel, fgModel,
5,
cv::GC_INIT_WITH_MASK);
cv::compare(mask, cv::GC_PR_FGD, result, cv::CMP_EQ);
cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
image.copyTo(foreground, result);
cv::imshow("result", foreground);
cv::waitKey();
return 0;
}
参考这篇文章. OpenCV官方文档关于cv::grabCut的说明。
官网的说明比较简单,看完书上只知道GC_INIT_WITH_RECT怎么用,不知道GC_INIT_WITH_MASK怎么用,这里解释一下,防止自己以后遗忘。
首先,知道这四个的含义:
- GC_BGD defines an obvious background pixels. GC_BGD = 0
- GC_FGD defines an obvious foreground (object) pixel. GC_FGD = 1
- GC_PR_BGD defines a possible background pixel. GC_PR_BGD = 2
- GC_PR_FGD defines a possible foreground pixel GC_PR_FGD = 3
其次,知道cv::grabCut第三个参数的含义:
- ROI containing a segmented object. The pixels outside of the ROI are marked as “obvious background”. The parameter is only used when mode==GC_INIT_WITH_RECT
所以在GC_INIT_WITH_MASK模式下,这参数没有用。
GC_INIT_WITH_RECT 模式
这时候我们看mode == GC_INIT_WITH_RECT的代码cv::grabCut(image, result, rectangle, bgModel, fgModel, 5, cv::GC_INIT_WITH_RECT);:
rectangle相当于告诉函数,在我框范围外的全部是GC_BGD,即背景像素。而在这个矩形框范围内的是GC_PR_FGD,即可能的前景像素。然后5是迭代次数,最后得到的结果保存在result,得到的result是啥呢?根据我的调试来看,仍然是GC_PR_FGD,那跟前面说矩形框里的像素都是GC_PR_FGD有什么区别呢?
区别在于你跑grabCut算法之前告诉他这个矩形框里有的像素是前景像素,有的不是,你给我找找,看看哪些是前景,给我保存在result里面吧,然后grabCut算法就迭代得到结果,什么样的结果,他认为可能是前景像素的结果,即GC_PR_FGD,保存在result里面,因为算法在(面对矩形框里的前景和背景很相似)也有可能出bug(得到她认为可能是的前景其实不是前景),所以他说可能是,其实大多数情况下,得到的结果与真实的前景相差无几,不然这个算法成果也不能发表在ToG 2004的会议上面。
那么result里面是什么呢?我看了一下,背景元素的值是0,可能的前景为3,可能的背景元素值为2,这样调用cv::compare函数,将result元素值与cv::GC_PR_FGD比较,即与3比较,相同的在赋给result,不过这时候result的值就变了,原来等于3的位置的像素值全部变成255,而不是之前的3,感觉很奇怪,不知道为什么,求高人解答,好在不影响结果。因为result用来做掩模,只要可能的前景像素对应的值不为0,其他的通通为0即可。
最后调用copyTo函数,在掩模result的作用下,只拷贝可能的前景像素,从而达到提取前景的效果。
GC_INIT_WITH_MASK 模式
然后接着看一下mode == GC_INIT_WITH_MASK的代码:
1 | cv::Mat mask(image.size(), CV_8UC1, cv::Scalar(cv::GC_BGD)); |
这里掩模mask必须是8-bit single-channel的,然后构造掩模时,直接将全部元素赋值为0,即GC_BGD,然后之前说过,cv::Mat这样的操作只是浅拷贝,他们仍指向同一块内存,所以在这里提取ROI,将ROI这一块的像素值换成3,也就是相当于将mask这一块的像素值换位3,即GC_PR_FGD,然后将其传递进函数,进行运算。
然后第三个参数在GC_INIT_WITH_MASK是不起作用的,后面的代码基本跟mode == GC_INIT_WITH_RECT的代码道理相同,不在赘述。
grabCut算法的原理,可以看看这几篇文章,文章1,文章2, GrabCut算法是由Graph Cut算法衍生出来的。
- (1)Graph Cut算法是发表在ICCV2001会议上的成果,见Interactive Graph Cuts for Optimal Boundary & Region Segmentation of Objects in N-D Images;
- (2)Grab Cut算法是发表在ToG上的成果“GrabCut” — Interactive Foreground Extraction using Iterated Graph Cuts。
这一章结束了,等后面有时间,好好看一下这两篇论文,看看能不能实现出来,对了,还有分水岭算法,看看paper上的原理,看看能不能自己把轮子造出来,而不是做一个只会调API的码农。。。