OpenCV4.10使用形态运算提取水平线和垂直线

 返回:OpenCV系列文章目录(持续更新中......)

上一篇:OpenCV的查找命中或未命中

下一篇:OpenCV4.9图像金字塔-CSDN博客

目标

在本教程中,您将学习如何:

  • 应用两个非常常见的形态运算符(即膨胀和侵蚀),并创建自定义内核,以便在水平轴和垂直轴上提取直线。为此,您将使用以下 OpenCV 函数:

    • erode()
    • dilate()
    • getStructuringElement()

    在一个示例中,您的目标是从乐谱中提取音符。

理论

形态操作

形态学是一组图像处理操作,这些操作基于预定义的结构元素(也称为内核)处理图像。输出图像中每个像素的值基于输入图像中相应像素与其相邻像素的比较。通过选择内核的大小和形状,可以构造对输入图像的特定形状敏感的形态操作。

两种最基本的形态操作是扩张和侵蚀。扩张会将像素添加到图像中物体的边界上,而侵蚀则恰恰相反。添加或删除的像素量分别取决于用于处理图像的结构元素的大小和形状。通常,这两个操作遵循的规则如下:

  • 膨胀:输出像素的值是结构元素大小和形状范围内的所有像素的最大值。例如,在二进制图像中,如果输入图像的任何像素在内核范围内设置为值 1,则输出图像的相应像素也将设置为 1。后者适用于任何类型的图像(例如灰度、bgr 等)。

二进制图像上的扩张

灰度图像上的扩张

  • 侵蚀:反之亦然。输出像素的值是结构化元素大小和形状范围内的所有像素的最小值。请看下面的示例图:

二进制映像上的侵蚀

灰度图像上的侵蚀

结构元素

如上所述,通常在任何形态操作中,用于探测输入图像的结构元素是最重要的部分。

结构元素是仅由 0 和 1 组成的矩阵,可以具有任意形状和大小。通常比正在处理的图像小得多,而值为 1 的像素定义邻域。结构元素的中心像素(称为原点)标识感兴趣的像素 - 正在处理的像素。

例如,下面演示了 7x7 大小的菱形结构单元。

一种菱形结构元件及其起源

结构元素可以具有许多常见形状,例如线条、菱形、圆盘、周期线以及圆形和大小。通常,选择的结构化元素的大小和形状与要在输入图像中处理/提取的对象相同。例如,要在图像中查找线条,请创建一个线性结构元素,稍后将看到。

示例代码:

C++

本教程代码如下所示。

您也可以从这里下载。

#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);
}

要点/结果

C++

从这里获取演示图像。

加载图像

 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);

参考文献:

1、《Extract horizontal and vertical lines by using morphological operations》---Theodore Tsesmelis


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/551473.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【贪心 堆 】3081. 替换字符串中的问号使分数最小

算法可以发掘本质&#xff0c;如&#xff1a; 一&#xff0c;若干师傅和徒弟互有好感&#xff0c;有好感的师徒可以结对学习。师傅和徒弟都只能参加一个对子。如何让对子最多。 二&#xff0c;有无限多1X2和2X1的骨牌&#xff0c;某个棋盘若干格子坏了&#xff0c;如何在没有坏…

OpenHarmony社交分享类APP开发实战

介绍 本示例是一个社交分享类APP&#xff0c;搭建了不同的页面向用户提供获取社交信息等能力。为了减少频繁权限弹窗对用户的干扰&#xff0c;同时提供更小的授权范围&#xff0c;使用了安全控件做临时授权场景。当用户实际点击了某种类型的安全控件时&#xff0c;会由系统弹出…

Golang 开发实战day11 - Pass By Value

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 开发实战day11 - 按值…

SpringCloud中的nacos配置中心分析

一、概述 nacos可以作为配置管理使用&#xff0c;为各个微服务之间提供统一的配置中心&#xff0c;方便管理所有服务的配置。 二、什么是配置中心&#xff1f; 配置中心&#xff1a;一般SpringBoot项目都使用在resources下创建类似application.yml之类的配置文件来管理整个项目…

微信生态洗牌,私域拥抱公域的逐步试探

一直被人们奉为“私域神器”的微信&#xff0c;如今&#xff0c;变化越来越大了&#xff0c;微信的几次更新&#xff0c;透露出很多不一样的信息&#xff0c;在微信的很多使用场景中&#xff0c;都逐渐在向平台化公域流量分发的方向发展&#xff0c;不断的尝试从私域走向公域&a…

2024年【起重机械指挥】考试题及起重机械指挥复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 起重机械指挥考试题是安全生产模拟考试一点通总题库中生成的一套起重机械指挥复审模拟考试&#xff0c;安全生产模拟考试一点通上起重机械指挥作业手机同步练习。2024年【起重机械指挥】考试题及起重机械指挥复审模拟…

社科院与新加坡社科大学工商管理博士——在职读博行而不辍,未来可期

在职读博&#xff0c;对于许多人来说&#xff0c;既是一种挑战&#xff0c;也是一种机遇。它要求我们在繁忙的工作之余&#xff0c;还要抽出时间来深入研究学术&#xff0c;不断提升自己的专业素养。然而&#xff0c;正是这种行而不辍的精神&#xff0c;让我们能够在职业生涯中…

C++类和对象:构造函数,析构函数,拷贝构造

文章目录 1.类的6个默认成员函数2. 构造函数2.1 概念2.2 特性 3.析构函数3.1 概念3.2 特性 4.拷贝构造 1.类的6个默认成员函数 一个类中什么都不写&#xff0c;就是空类。而空类实际上有成员&#xff0c;当一个类中什么都不写时&#xff0c;编译器会生成六个对应默认成员函数。…

解读我国最新网络安全运维与数据处理安全规范:强化数字化时代安全基石

近日&#xff0c;全国网络安全标准化技术委员会秘书处公布了一系列重要的网络安全与数据安全相关技术规范草案&#xff0c;包括《网络安全技术 网络安全运维实施指南》、《网络安全技术 信息系统灾难恢复规范》以及《数据安全技术 政务数据处理安全要求》。这些规范旨在应对当前…

JavaScript权威指南(第7版) 笔记 - 第 7 章 数组

能用代码说清楚的&#xff0c;绝不多废话&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; Linux创始人Linus的名言&#xff1a;Talk is cheap&#xff0c;show me the code ! &#xff0c;博主技术博文会精心给出能说明问题的范例代码&#xff01;…

安装 k8s集群的问题:默认容器运行时从 Docker 改为 Containerd

安装 k8s集群的问题&#xff1a;默认容器运行时从 Docker 改为 Containerd 1、背景2、容器运行时从 Docker 改为 Containerd2.1、安装 Containerd&#xff1a;2.2、生成 Containerd 的配置文件2.3 、创建 /etc/crictl.yaml 文件2.4 、配置 Containerd 服务开机自启 &#x1f49…

算法与数据结构要点速学——排序算法

排序算法 所有主要的编程语言都有一个内置的排序方法。假设并说排序成本为 O(n*log n)&#xff0c;通常是正确的&#xff0c;其中 n 是要排序的元素数。为了完整起见&#xff0c;这里有一个图表&#xff0c;列出了许多常见的排序算法及其完整性。编程语言实现的算法各不相同&a…

【GDB调试技巧】提高gdb的调试效率

目录 &#x1f31e;gdb的启动 &#x1f31e;gdb技巧 &#x1f33c;1. gdb小技巧汇总 &#x1f33c;2. 打印输出指定地址的值 &#x1f33c;3. 查看当前执行到哪行代码代码内容 3.1 方式一&#xff1a;info line 结合 list 。 3.2 方式二&#xff1a;f 3.3 方式三&#…

WebGIS面试题(第五期)

WebGIS面试题&#xff08;第五期&#xff09; 以下题目仅为部分题目&#xff0c;全部题目在公众号{GISer世界}&#xff0c;答案仅供参考 1、Cesium的核心组件有哪些&#xff1f; Cesium的核心组件包括Viewer、Scene、Model、Geometry、Material和Camera等。其中&#xff0c;…

Latex(从入门到入土)1

第一章&#xff1a;初识Latex 1、安装Latex&#xff0c;当然可以安装官方的开放版本&#xff0c;也可以去找找别人发的资源。我这里只介绍我的学习经过。如果想下载最新的软件资源&#xff0c;我这里推荐微信公众号&#xff1a;软件智库&#xff0c;通过号主提供的网址是可以下…

基于大数据的全国热门景点数据可视化分析系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 本文将介绍如何使用Python中的Pandas库进行数据挖掘&#xff0c;并结合Flask Web框架实现一个旅游景点数据分析系统。该系统将包括以下功能模块&#xff1a;热门景点概况、景点星级与评分分析、景…

Docker 学习笔记(十):Centos7 中 Docker 部署 Redis 集群,打包 SpringBoot 微服务

一、前言 记录时间 [2024-4-17] 系列文章简摘&#xff1a; Docker 学习笔记&#xff08;六&#xff09;&#xff1a;挑战容器数据卷技术一文通&#xff0c;实战多个 MySQL 数据同步&#xff0c;能懂会用&#xff0c;初学必备 Docker 学习笔记&#xff08;七&#xff09;&#x…

基于Copula函数的风光功率联合场景生成_任意修改生成的场景数目(附带Matlab代码)

基于Copula函数的风光功率联合场景生成 削减为6个场景 部分展示削减为5个场景 部分展示 风光等可再生能源出力的不确定性和相关性给系统的设计带来了极大的复杂性&#xff0c;若忽略这些因素&#xff0c;势必会在系统规划阶段引入次优决策风险。因此&#xff0c;在确定系统最佳…

Linux sort/uniq/wc

文章目录 1. sort 排序将线程ID从大到小排序 2.uniq 临近去重3.wc word cnt 统计 1. sort 排序 将线程ID从大到小排序 grep -v是反向筛选&#xff0c;利用USER&#xff0c;排除掉首行 awk是打印第1 2列 sort -n是代码以数值大小做排序&#xff0c;不加的话会以字符排序。 -k是…

Go 单元测试之HTTP请求与API测试

文章目录 一、httptest1.1 前置代码准备1.2 介绍1.3 基本用法 二、gock2.1介绍2.2 安装2.3 基本使用2.4 举个例子2.4.1 前置代码2.4.2 测试用例 一、httptest 1.1 前置代码准备 假设我们的业务逻辑是搭建一个http server端&#xff0c;对外提供HTTP服务。用来处理用户登录请求…