OpenCV通过使用形态学操作来提取水平和垂直线
目标
在本教程中,您将学习如何:
- 应用两个非常常见的形态运算符(即扩张和侵蚀),创建自定义内核,以便在水平轴和垂直轴上提取直线。为此,您将使用以下OpenCV功能:CV ::侵蚀CV ::扩张CV :: getStructuringElement在一个例子中,您的目标是从音乐表中提取音乐笔记。
理论
形态操作
形态学是一组图像处理操作,其基于预定义的也称为内核的结构元素来处理图像。输出图像中的每个像素的值基于输入图像中的对应像素与其邻居的比较。通过选择内核的大小和形状,您可以构建对输入图像的特定形状敏感的形态操作。
两个最基本的形态操作是扩张和侵蚀。扩散将像素添加到图像中对象的边界,而侵蚀恰恰相反。添加或删除的像素数量分别取决于用于处理图像的结构元素的大小和形状。一般来说,这两个操作遵循的规则如下:
- 扩展:输出像素的值是属于结构元素大小和形状的所有像素的最大值。例如,在二进制图像中,如果落入内核范围内的输入图像的任何像素被设置为值1,则输出图像的相应像素也将被设置为1。后者适用于任何类型的图像(例如灰度,bgr等)。
二进制图像的扩张
灰度图像的扩张
侵蚀:反之亦然适用于侵蚀作业。输出像素的值是落在结构元素的大小和形状内的所有像素的最小值。看下面的例子:
侵蚀二进制图像
灰度图像侵蚀
结构元素
如上所述,通常在任何形态学操作中,用于探测输入图像的结构元素是最重要的部分。
一个结构化元素是一个由只有0和1组成的矩阵,可以有任意的任意形状和大小。通常比正在处理的图像小得多,而值为1的像素定义邻域。称为原点的结构元素的中心像素标识感兴趣的像素 - 正在处理的像素。
例如,下面示出了7×7尺寸的菱形结构元件。
钻石形结构元件及其起源
结构元素可以具有许多常见的形状,例如线,菱形,圆盘,周期线以及圆和尺寸。您通常选择与要在输入图像中处理/提取的对象相同的大小和形状的结构元素。例如,要在图像中查找行,请创建一个线性结构元素,您将在后面看到。
Code
本教程代码如下所示。您也可以从这里下载。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int, char** argv)
{
// Load the image
Mat src = imread(argv[1]);
// Check if image is loaded fine
if(!src.data)
cerr << "Problem loading image!!!" << endl;
// Show source image
imshow("src", src);
// Transform source image to gray if it is not
Mat gray;
if (src.channels() == 3)
{
cvtColor(src, gray, CV_BGR2GRAY);
}
else
{
gray = src;
}
// Show gray image
imshow("gray", gray);
// Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
Mat bw;
adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
// Show binary image
imshow("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 horizontalsize = horizontal.cols / 30;
// Create structure element for extracting horizontal lines through morphology operations
Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));
// Apply morphology operations
erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
// Show extracted horizontal lines
imshow("horizontal", horizontal);
// Specify size on vertical axis
int verticalsize = vertical.rows / 30;
// Create structure element for extracting vertical lines through morphology operations
Mat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));
// Apply morphology operations
erode(vertical, vertical, verticalStructure, Point(-1, -1));
dilate(vertical, vertical, verticalStructure, Point(-1, -1));
// Show extracted vertical lines
imshow("vertical", vertical);
// Inverse vertical image
bitwise_not(vertical, vertical);
imshow("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, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
imshow("edges", edges);
// Step 2
Mat kernel = Mat::ones(2, 2, CV_8UC1);
dilate(edges, edges, kernel);
imshow("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
imshow("smooth", vertical);
waitKey(0);
return 0;
}
说明/结果
- 加载源图像并检查它是否加载没有任何问题,然后显示:
// Load the image
Mat src = imread(argv[1]);
// Check if image is loaded fine
if(!src.data)
cerr << "Problem loading image!!!" << endl;
// Show source image
imshow("src", src);
- 然后将图像转换为灰度,如果还没有:
// Transform source image to gray if it is not
Mat gray;
if (src.channels() == 3)
{
cvtColor(src, gray, CV_BGR2GRAY);
}
else
{
gray = src;
}
// Show gray image
imshow("gray", gray);
- 然后将灰度图像转换为二进制。注意〜符号,表示我们使用它的逆(即bitwise_not)版本:
// Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
Mat bw;
adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
// Show binary image
imshow("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 horizontalsize = horizontal.cols / 30;
// Create structure element for extracting horizontal lines through morphology operations
Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));
// Apply morphology operations
erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
// Show extracted horizontal lines
imshow("horizontal", horizontal);
- 同样适用于垂直线,具有相应的结构元素:
这又表示如下:
// Specify size on vertical axis
int verticalsize = vertical.rows / 30;
// Create structure element for extracting vertical lines through morphology operations
Mat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));
// Apply morphology operations
erode(vertical, vertical, verticalStructure, Point(-1, -1));
dilate(vertical, vertical, verticalStructure, Point(-1, -1));
// Show extracted vertical lines
imshow("vertical", vertical);
- 你可以看到我们几乎在那里 但是,在这一点上你会注意到这些笔记的边缘有点粗糙。为此,我们需要细化边缘以获得更平滑的结果:
// Inverse vertical image
bitwise_not(vertical, vertical);
imshow("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, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
imshow("edges", edges);
// Step 2
Mat kernel = Mat::ones(2, 2, CV_8UC1);
dilate(edges, edges, kernel);
imshow("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
imshow("smooth", vertical);