1. 目标
在本文中,将学习如何:
-
应用两个常见的形态学算子(即 Dilation 和 Erosion),创建自定义内核,以便在水平轴和垂直轴上提取直线。为此,将使用以下 OpenCV 函数:
在这个示例中,目标是从乐谱中提取音符。
2. 理论
2.1.形态学操作
形态学是一组图像处理操作,基于预定义的结构元(也称为内核)处理图像。输出图像中每个像素的值基于输入图像中相应像素与其相邻像素的比较。通过选择结构元的大小和形状,可以构建一个对输入图像的特定形状敏感的形态学操作。
两个最基本的形态学运算是膨胀和腐蚀。膨胀将像素添加到图像中对象的边界,而腐蚀则恰恰相反。添加或删除的像素数量分别取决于用于处理图像的结构元的大小和形状。一般来说,这两个操作遵循的规则如下:
-
膨胀:输出像素的值是落在结构元覆盖范围内的像素的最大值。例如在二值图像中,如果输入图像的任何像素落在结构元范围内的值设置为 1,则输出图像的相应像素也将设置为 1。后者适用于任何类型的图像(例如灰度、bgr 等)。
二值图像的膨胀
灰度图像上的膨胀
-
腐蚀:与膨胀相反的操作时腐蚀操作。输出像素的值是落在结构元覆盖范围内的像素的最小值。*请看下面的示例图:
二值图像上的侵蚀
灰度图像上的侵蚀
2.2 结构元
从上面可以看出,通常在任何形态学操作中,用于探测输入图像的结构元是最重要的部分。
结构元是一个仅由 0 和 1 组成的矩阵,可以具有任意形状和大小。通常比正在处理的图像小得多,而值为 1 的像素定义邻域。原点是结构元的中心像素,就是正在处理的像素。
例如,下图说明了一个 7x7 大小的菱形结构元素。
一种菱形结构元件及其起源
结构元可以有许多常见的形状,例如线型、菱形、圆盘、周期线以及圆形和大小。通常会选择与要在输入图像中处理/提取的对象具有相同大小和形状的结构元素。例如,要在图像中查找线条,请创建一个线型结构元素,将在后面看到。
3. 代码
本教程代码如下所示。
从这下载。
#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);
}
4. 代码解释/结果
从这下载图像。
原图
4.1 加载图像
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);
4.2 转换为灰度图像
// 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);
4.3 灰度图像转换为二值图像
// 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);
4.4 输出图像
现在准备应用形态学操作来提取水平和垂直线,从而将音符从乐谱中分离出来,但首先初始化输出图像:
// Create the images that will use to extract the horizontal and vertical lines
Mat horizontal = bw.clone();
Mat vertical = bw.clone();
4.5 结构元
正如在理论中指定的那样,为了提取想要的对象,需要创建相应的结构元。由于要提取水平线,因此相应的结构元素将具有以下形状:
在源代码中,这由以下代码片段表示:
// 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);
4.6 优化边缘/结果
如上所见,我们做到了。但是,此时注意到音符的边缘有些粗糙。出于这个原因,需要细化边缘以获得更平滑的结果:
// 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);