理论
形态操作
形态学是一组图像处理操作,这些操作基于预定义的结构元素(也称为内核)处理图像。输出图像中每个像素的值基于输入图像中相应像素与其相邻像素的比较。通过选择内核的大小和形状,可以构造对输入图像的特定形状敏感的形态操作。
两种最基本的形态操作是扩张和侵蚀。扩张会将像素添加到图像中物体的边界上,而侵蚀则恰恰相反。添加或删除的像素量分别取决于用于处理图像的结构元素的大小和形状。通常,这两个操作遵循的规则如下:
膨胀:输出像素的值是结构元素大小和形状范围内的所有像素的最大值。例如,在二进制图像中,如果输入图像的任何像素在内核范围内设置为值 1,则输出图像的相应像素也将设置为 1。后者适用于任何类型的图像(例如灰度、bgr 等)。
二进制上的膨胀
灰度图像上的膨胀
可以发现他是核函数覆盖区域内取最大值
腐蚀:反之亦然。输出像素的值是结构化元素大小和形状范围内的所有像素的最小值。请看下面的示例图:
二进制映像上的腐蚀
灰度图像上的腐蚀
可以发现核函数覆盖内取最小值
结构元素
如上所述,通常在任何形态操作中,用于探测输入图像的结构元素是最重要的部分。
结构元素是仅由 0 和 1 组成的矩阵,可以具有任意形状和大小。通常比正在处理的图像小得多,而值为 1 的像素定义邻域。结构元素的中心像素(称为原点)标识感兴趣的像素 - 正在处理的像素。
例如,下面演示了 7x7 大小的菱形结构单元。
一种菱形结构元件及其起源
结构元素可以具有许多常见形状,例如线条、菱形、圆盘、周期线以及圆形和大小。通常,选择的结构化元素的大小和形状与要在输入图像中处理/提取的对象相同。例如,要在图像中查找线条,请创建一个线性结构元素,稍后将看到。
例子提取乐谱
输入图片如下:
二值化后:
需求是如何提取对应的水平直线和乐谱形状呢?
提取水平线
构建结构元素
正如我们在理论中指出的那样,为了提取我们想要的对象,我们需要创建相应的结构元素。由于我们要提取水平线,因此用于该目的的相应结构元素将具有以下形状:
在源代码中,这由以下代码片段表示:
// Specify size on horizontal axis
int horizontal_size = horizontal.cols / 30;
// Create structure element for extracting horizontal lines through morphology operations
Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontal_size, 1));
// Apply morphology operations
erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
// Show extracted horizontal lines
show_wait_destroy("horizontal", horizontal);
结果如下
提取乐谱形状
这同样适用于具有相应结构元素的垂直线:
同样,这表示如下:
// Specify size on vertical axis
int vertical_size = vertical.rows / 30;
// Create structure element for extracting vertical lines through morphology operations
Mat verticalStructure = getStructuringElement(MORPH_RECT, Size(1, vertical_size));
// Apply morphology operations
erode(vertical, vertical, verticalStructure, Point(-1, -1));
dilate(vertical, vertical, verticalStructure, Point(-1, -1));
// Show extracted vertical lines
show_wait_destroy("vertical", vertical);
结果如下:
总结:
可以发现,只要我们合理的设置核函数就可以提取我们想要的形状,不仅仅是水平直线和竖直直线,其他形状也是可以的,例如倾斜45度的直线,圆弧等等,只要设置好核函数即可
源码如下:
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
void show_wait_destroy(const char* winname, cv::Mat img);
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
CommandLineParser parser(argc, argv, "{@input | notes.png | input image}");
Mat src = imread( samples::findFile( parser.get<String>("@input") ), IMREAD_COLOR);
if (src.empty())
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
// Show source image
imshow("src", src);
// Transform source image to gray if it is not already
Mat gray;
if (src.channels() == 3)
{
cvtColor(src, gray, COLOR_BGR2GRAY);
}
else
{
gray = src;
}
// Show gray image
show_wait_destroy("gray", gray);
// Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
Mat bw;
adaptiveThreshold(~gray, bw, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
// Show binary image
show_wait_destroy("binary", bw);
// Create the images that will use to extract the horizontal and vertical lines
Mat horizontal = bw.clone();
Mat vertical = bw.clone();
// Specify size on horizontal axis
int horizontal_size = horizontal.cols / 30;
// Create structure element for extracting horizontal lines through morphology operations
Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontal_size, 1));
// Apply morphology operations
erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
// Show extracted horizontal lines
show_wait_destroy("horizontal", horizontal);
// Specify size on vertical axis
int vertical_size = vertical.rows / 30;
// Create structure element for extracting vertical lines through morphology operations
Mat verticalStructure = getStructuringElement(MORPH_RECT, Size(1, vertical_size));
// Apply morphology operations
erode(vertical, vertical, verticalStructure, Point(-1, -1));
dilate(vertical, vertical, verticalStructure, Point(-1, -1));
// Show extracted vertical lines
show_wait_destroy("vertical", vertical);
// Inverse vertical image
bitwise_not(vertical, vertical);
show_wait_destroy("vertical_bit", vertical);
// Extract edges and smooth image according to the logic
// 1. extract edges
// 2. dilate(edges)
// 3. src.copyTo(smooth)
// 4. blur smooth img
// 5. smooth.copyTo(src, edges)
// Step 1
Mat edges;
adaptiveThreshold(vertical, edges, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
show_wait_destroy("edges", edges);
// Step 2
Mat kernel = Mat::ones(2, 2, CV_8UC1);
dilate(edges, edges, kernel);
show_wait_destroy("dilate", edges);
// Step 3
Mat smooth;
vertical.copyTo(smooth);
// Step 4
blur(smooth, smooth, Size(2, 2));
// Step 5
smooth.copyTo(vertical, edges);
// Show final result
show_wait_destroy("smooth - final", vertical);
return 0;
}
void show_wait_destroy(const char* winname, cv::Mat img) {
imshow(winname, img);
moveWindow(winname, 500, 0);
waitKey(0);
destroyWindow(winname);
}