OpenCV 形态学操作与乐谱线条提取
目标
在本教程中,您将学习如何应用两个非常常见的形态算子(即膨胀和侵蚀),并创建自定义核,以便在水平轴和垂直轴上提取直线。为此,您将使用以下 OpenCV 函数:
cv::dilate(膨胀)cv::erode(侵蚀)cv::getStructuringElement(获取结构元素)cv::morphologyEx(形态学变换)
在一个示例中,您的目标是从乐谱图像中提取音符之间的线条,从而分离出音符本身。
理论
形态学操作
形态学是一组图像处理操作,它们基于预定义的结构元素(也称为内核)处理图像。输出图像中每个像素的值基于输入图像中相应像素与其相邻像素的比较。通过选择内核的大小和形状,您可以构建对输入图像的特定形状敏感的形态操作。
两个最基本的形态学操作是扩张和侵蚀。膨胀将像素添加到图像中物体的边界上,而侵蚀则恰恰相反。添加或删除的像素量分别取决于用于处理图像的结构元素的大小和形状。通常,这两个操作遵循的规则如下:
膨胀 (Dilation):输出像素的值是结构元素大小和形状内所有像素的最大值。例如,在二进制图像中,如果输入图像中属于内核范围的任何像素设置为值 1,则输出图像的相应像素也将设置为 1。后者适用于任何类型的图像(例如灰度、BGR 等)。膨胀操作常用于填充物体内部的小孔洞或连接邻近的物体。
![图:二值图像上的膨胀效果演示]
侵蚀 (Erosion):反之亦然。输出像素的值是结构元素大小和形状范围内所有像素的最小值。请看下面的示例图:侵蚀操作常用于去除小噪点或断开两个相连的物体。
![图:二值图像上的侵蚀效果演示]
结构元素
如上所述,一般来说,在任何形态学操作中,用于探测输入图像的结构元素是最重要的部分。
结构元素是一个仅由 0 和 1 组成的矩阵,可以具有任意形状和大小。通常比正在处理的图像小得多,而值为 1 的像素定义邻域。结构元素的中心像素(称为原点)标识感兴趣的像素 - 正在处理的像素。
例如,下面演示了 7x7 大小的菱形结构元素。
![图:菱形结构元素及其起源]
结构元素可以具有许多常见的形状,例如线条、菱形、圆盘、周期线以及圆形和大小。通常,您选择与要在输入图像中处理/提取的对象具有相同大小和形状的结构元素。例如,要查找图像中的线条,请创建一个线性结构元素,稍后将看到。
对于乐谱提取任务,我们需要区分水平线和垂直线。因此,我们将创建长宽比极端的矩形结构元素:一个宽度大高度小的元素用于检测水平线,一个宽度小高度大的元素用于检测垂直线。
代码示例 C++
本教程代码如下所示。代码基于 OpenCV 4.x 版本编写,确保兼容性和稳定性。
#include <opencv2/opencv.hpp>
#include <iostream>
// 辅助函数:显示图像并等待按键关闭窗口
void show_wait_destroy(const char* winname, const cv::Mat& img) {
cv::imshow(winname, img);
int key = cv::waitKey(0);
(key == || key == ) {
;
}
cv::(winname);
}
{
std::string input_path = ;
(argc > ) {
input_path = argv[];
}
cv::Mat src = cv::(input_path);
(src.()) {
std::cout << << std::endl;
std::cout << << argv[] << << std::endl;
;
}
(, src);
cv::Mat gray;
(src.() == ) {
cv::(src, gray, cv::COLOR_BGR2GRAY);
} {
gray = src.();
}
(, gray);
cv::Mat bw;
cv::(~gray, bw, , cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY_INV, , );
(, bw);
cv::Mat horizontal = bw.();
cv::Mat vertical = bw.();
horizontal_size = horizontal.cols / ;
cv::Mat horizontalStructure = cv::(
cv::MORPH_RECT, cv::(horizontal_size, ));
cv::(horizontal, horizontal, horizontalStructure);
cv::(horizontal, horizontal, horizontalStructure);
(, horizontal);
vertical_size = vertical.rows / ;
cv::Mat verticalStructure = cv::(
cv::MORPH_RECT, cv::(, vertical_size));
cv::(vertical, vertical, verticalStructure);
cv::(vertical, vertical, verticalStructure);
(, vertical);
cv::Mat edges;
cv::(vertical, edges, , cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY, , );
(, edges);
cv::Mat kernel = cv::Mat::(, , CV_8U);
cv::(edges, edges, kernel);
(, edges);
cv::Mat smooth;
vertical.(smooth);
cv::(smooth, smooth, cv::(, ), );
smooth &= edges;
(, smooth);
;
}


