OpenCV (用形态学运算变换图像)
Eva.Q Lv9

数学形态学!?!?!

recommendation: https://blog.csdn.net/keen_zuxwang/article/details/72768092?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162806274916780264097167%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162806274916780264097167&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-72768092.first_rank_v2_pc_rank_v29&utm_term=morphologyex&spm=1018.2226.3001.4187

mooc视频(较为系统的介绍):https://www.icourse163.org/learn/HDU-1461554161?tid=1464982447#/learn/content?type=detail&id=1243646733&cid=1267148615&replay=true

基本概念

  1. 形态学滤波器通常用于二值图像。且在形态学中,我们习惯用高像素值(白)表示前景物体,低像素值(黑)表示背景物体,因此对图像做了反向处理。(反向处理后的图像称为原二值图像的 补码

形态学滤波器 —— 腐蚀和膨胀

滤波器的作用范围是由结构元素定义的像素集。在某个像素上应用结构元素时,结构元素的锚点与该像素对齐,所有与结构元素相交的像素就包含在当前集合中。腐蚀 就是把当前像素替换成所定义像素集合中的最小像素值,膨胀 就是把当前像素替换成所定义像素集合中的最大像素值。

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
int main()
{
// Read input image
cv::Mat image= cv::imread("binary.bmp");
if (!image.data)
return 0;

// Display the image
cv::namedWindow("Image");
cv::imshow("Image",image);

// Erode the image
// with the default 3x3 structuring element (SE)
cv::Mat eroded; // the destination image
cv::erode(image,eroded,cv::Mat());

// Display the eroded image
cv::namedWindow("Eroded Image");
cv::imshow("Eroded Image",eroded);

// Dilate the image
cv::Mat dilated; // the destination image
cv::dilate(image,dilated,cv::Mat());

// Display the dilated image
cv::namedWindow("Dilated Image");
cv::imshow("Dilated Image",dilated);

return 0;
}

OpenCV 默认使用 3*3 正方形结构元素,将第三个元素指定为空矩阵。也可自定义正方形大小。

1
2
3
4
5
6
7
8
9
// Erode the image with a larger SE
// create a 7x7 mat with containing all 1s
cv::Mat element(7,7,CV_8U,cv::Scalar(1));
// erode the image with that SE
cv::erode(image,eroded,element);

// Display the eroded image
cv::namedWindow("Eroded Image (7x7)");
cv::imshow("Eroded Image (7x7)",eroded);

除了改变结构元素的大小,还可以改变应用一个结构元素的次数。

1
2
3
4
5
6
// Erode the image 3 times.
cv::erode(image,eroded,cv::Mat(),cv::Point(-1,-1),3);

// Display the eroded image
cv::namedWindow("Eroded Image (3 times)");
cv::imshow("Eroded Image (3 times)",eroded);

Point(-1, -1) 表示原点是矩阵的中心点(默认),也可以定义在结构元素上的其他位置。

形态学滤波器 —— 开启和闭合图像

闭合 :对图像先膨胀后腐蚀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Close the image
cv::Mat element5(5,5,CV_8U,cv::Scalar(1));
cv::Mat closed;
cv::morphologyEx(image,closed, // input and output images
cv::MORPH_CLOSE, // operator code
element5); // structuring element

// Display the closed image
cv::namedWindow("Closed Image");
cv::imshow("Closed Image",closed);

// explicit closing
// 1. dilate original image
cv::Mat result;
cv::dilate(image, result, element5);
// 2. in-place erosion of the dilated image
cv::erode(result, result, element5);

// Display the closed image
cv::namedWindow("Closed Image (2)");
cv::imshow("Closed Image (2)", result);

5*5 的结构元素

开启 :先腐蚀后膨胀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Open the image
cv::Mat opened;
cv::morphologyEx(image,opened,cv::MORPH_OPEN,element5);

// Display the opened image
cv::namedWindow("Opened Image");
cv::imshow("Opened Image",opened);

// explicit openning
cv::Mat result;
// 1. in-place erosion of the dilated image
cv::erode(result, result, element5);
// 2. dilate original image
cv::dilate(image, result, element5);

// Display the opened image
cv::namedWindow("Opened Image (2)");
cv::imshow("Opened Image (2)", result);

注:开启和闭合运算是 幂等 的,所以重复开启或者闭合是没有作用的。

应用形态学运算 —— 灰度图像

形态学梯度运算 可以提取出图像边缘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Read input image (gray-level)
image = cv::imread("boldt.jpg",0);
if (!image.data)
return 0;

// Get the gradient image using a 3x3 structuring element
cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());

// Display the morphological edge image
cv::namedWindow("Edge Image");
cv::imshow("Edge Image", 255 - result); // 反色处理,便于观察

// Apply threshold to obtain a binary image
int threshold(80);
cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY);

// Display the close/opened image
cv::namedWindow("Thresholded Edge Image");
cv::imshow("Thresholded Edge Image", result);

// Get the gradient image using a 3x3 structuring element
cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());

顶帽变换 可以从图像中提取出局部的小型前景物体

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
image = cv::imread("book.jpg", 0);
if (!image.data)
return 0;
// rotate the image for easier display
cv::transpose(image, image);
cv::flip(image, image, 0);

// Apply the black top-hat transform using a 7x7 structuring element
cv::Mat element7(7, 7, CV_8U, cv::Scalar(1));
cv::morphologyEx(image, result, cv::MORPH_BLACKHAT, element7);

// Display the top-hat image
cv::namedWindow("7x7 Black Top-hat Image");
cv::imshow("7x7 Black Top-hat Image", 255-result);

// Apply threshold to obtain a binary image
threshold= 25;
cv::threshold(result, result,
threshold, 255, cv::THRESH_BINARY);

// Display the morphological edge image
cv::namedWindow("Thresholded Black Top-hat");
cv::imshow("Thresholded Black Top-hat", 255 - result);

// Apply the black top-hat transform using a 7x7 structuring element
cv::morphologyEx(image, result, cv::MORPH_CLOSE, element7);

// Display the top-hat image
cv::namedWindow("7x7 Closed Image");
cv::imshow("7x7 Closed Image", 255 - result);

分水岭算法 —— 图像分割

封装 WatershedSegmenter

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
#if !defined WATERSHS
#define WATERSHS

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>

class WatershedSegmenter {

private:

cv::Mat markers;

public:

void setMarkers(const cv::Mat& markerImage) {

// Convert to image of ints
markerImage.convertTo(markers,CV_32S);
}

cv::Mat process(const cv::Mat &image) {

// Apply watershed
cv::watershed(image,markers);

return 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;
}

// Return watershed in the form of an image
cv::Mat getWatersheds() {

cv::Mat tmp;
markers.convertTo(tmp,CV_8U,255,255);

return tmp;
}
};


#endif
步骤

读取

1
2
3
4
5
6
7
8
// Read input image
cv::Mat image= cv::imread("group.jpg");
if (!image.data)
return 0;

// Display the image
cv::namedWindow("Original Image");
cv::imshow("Original Image",image);

获得二值图像

1
2
3
4
5
6
7
// Get the binary map
cv::Mat binary;
binary= cv::imread("binary.bmp",0);

// Display the binary image
cv::namedWindow("Binary Image");
cv::imshow("Binary Image",binary);

做深度腐蚀运算,只保留明显属于前景物体的像素

1
2
3
4
5
6
7
// Eliminate noise and smaller objects
cv::Mat fg;
cv::erode(binary,fg,cv::Mat(),cv::Point(-1,-1),4);

// Display the foreground image
cv::namedWindow("Foreground Image");
cv::imshow("Foreground Image",fg);

做大幅度膨胀,来选中一些背景像素

1
2
3
4
5
6
7
8
// Identify image pixels without objects
cv::Mat bg;
cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),4);
cv::threshold(bg,bg,1,128,cv::THRESH_BINARY_INV);

// Display the background image
cv::namedWindow("Background Image");
cv::imshow("Background Image",bg);

合并这两幅图,得到标记图像

1
2
3
4
5
// Show markers image
cv::Mat markers(binary.size(),CV_8U,cv::Scalar(0));
markers= fg+bg;
cv::namedWindow("Markers");
cv::imshow("Markers",markers);

创建分水岭分割类对象 并 进行分割

1
2
3
4
5
6
7
8
9
10
// Create watershed segmentation object
WatershedSegmenter segmenter;

// Set markers and process
segmenter.setMarkers(markers);
segmenter.process(image);

// Display segmentation result
cv::namedWindow("Segmentation");
cv::imshow("Segmentation",segmenter.getSegmentation());

以图像的方式返回分水岭

1
2
3
// Display watersheds
cv::namedWindow("Watersheds");
cv::imshow("Watersheds",segmenter.getWatersheds());
另一种方法

用户可以交互式地在场景中地物体和背景上绘制区域,以标注物体。

如这幅图,在边缘位置(背景)与 中心位置(前景)分别标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Open another image
image= cv::imread("tower.jpg");

// Identify background pixels
cv::Mat imageMask(image.size(),CV_8U,cv::Scalar(0));
cv::rectangle(imageMask,cv::Point(5,5),cv::Point(image.cols-5,image.rows-5),cv::Scalar(255),3);
// Identify foreground pixels (in the middle of the image)
cv::rectangle(imageMask,cv::Point(image.cols/2-10,image.rows/2-10),
cv::Point(image.cols/2+10,image.rows/2+10),cv::Scalar(1),10);

// Set markers and process
segmenter.setMarkers(imageMask);
segmenter.process(image);

// Display the image with markers
cv::rectangle(image,cv::Point(5,5),cv::Point(image.cols-5,image.rows-5),cv::Scalar(255,255,255),3);
cv::rectangle(image,cv::Point(image.cols/2-10,image.rows/2-10),
cv::Point(image.cols/2+10,image.rows/2+10),cv::Scalar(1,1,1),10);
cv::namedWindow("Image with marker");
cv::imshow("Image with marker",image);

// Display watersheds
cv::namedWindow("Watershed");
cv::imshow("Watershed",segmenter.getWatersheds());

MSER算法 —— 提取特征区域

MSER 最大稳定外部区域

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
int main()
{
// Read input image
cv::Mat image= cv::imread("building.jpg",0);
if (!image.data)
return 0;

// Display the image
cv::namedWindow("Image");
cv::imshow("Image",image);


// basic MSER detector
cv::Ptr<cv::MSER> ptrMSER= cv::MSER::create(5, // delta value for local minima detection
200, // min acceptable area
2000); // max acceptable area

// vector of point sets
std::vector<std::vector<cv::Point> > points;
// vector of rectangles
std::vector<cv::Rect> rects;
// detect MSER features
ptrMSER->detectRegions(image, points, rects);

std::cout << points.size() << " MSERs detected" << std::endl;

检测结果放在两个容器里。第一个是区域地容器,每个区域用组成它的像素点表示;第二个是矩形的容器,每个矩形包围一个区域。为了呈现效果,创建一个空白图像,在图像上用不同的颜色显示检测到的区域。

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
// create white image
cv::Mat output(image.size(),CV_8UC3);
output= cv::Scalar(255,255,255);

// OpenCV random number generator
cv::RNG rng;

// Display the MSERs in color areas
// for each detected feature
// reverse order to display the larger MSER first
for (std::vector<std::vector<cv::Point> >::reverse_iterator it= points.rbegin();
it!= points.rend(); ++it) {

// generate a random color
cv::Vec3b c(rng.uniform(0,254),
rng.uniform(0,254),
rng.uniform(0,254));

std::cout << "MSER size= " << it->size() << std::endl;

// for each point in MSER set
for (std::vector<cv::Point>::iterator itPts= it->begin();
itPts!= it->end(); ++itPts) {

//do not overwrite MSER pixels
if (output.at<cv::Vec3b>(*itPts)[0]==255) {

output.at<cv::Vec3b>(*itPts)= c;
}
}
}

cv::namedWindow("MSER point sets");
cv::imshow("MSER point sets",output);
cv::imwrite("mser.bmp", output);
1
2
3
4
5
6
7
8
9
10
11
12
13
// Extract and display the rectangular MSERs
std::vector<cv::Rect>::iterator itr = rects.begin();
std::vector<std::vector<cv::Point> >::iterator itp = points.begin();
for (; itr != rects.end(); ++itr, ++itp) {

// ratio test
if (static_cast<double>(itp->size())/itr->area() > 0.6)
cv::rectangle(image, *itr, cv::Scalar(255), 2);
}

// Display the resulting image
cv::namedWindow("Rectangular MSERs");
cv::imshow("Rectangular MSERs", image);

依据检测到的区域不能太细长(将封闭矩形旋转,计算高宽比),用封闭椭圆表示

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
	// Reload the input image
image = cv::imread("building.jpg", 0);
if (!image.data)
return 0;

// Extract and display the elliptic MSERs
for (std::vector<std::vector<cv::Point> >::iterator it = points.begin();
it != points.end(); ++it) {

// for each point in MSER set
for (std::vector<cv::Point>::iterator itPts = it->begin();
itPts != it->end(); ++itPts) {

// Extract bouding rectangles
cv::RotatedRect rr = cv::minAreaRect(*it);
// check ellipse elongation
if (rr.size.height / rr.size.height > 0.6 || rr.size.height / rr.size.height < 1.6)
cv::ellipse(image, rr, cv::Scalar(255), 2);
}
}

// Display the image
cv::namedWindow("MSER ellipses");
cv::imshow("MSER ellipses", image);

cv::waitKey();
}
  • Post title:OpenCV (用形态学运算变换图像)
  • Post author:Eva.Q
  • Create time:2021-08-05 09:05:07
  • Post link:https://qyy/2021/08/05/OPENCV/OPENCV1-9/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.