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

news2024/11/18 9:02:55

 返回: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


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java/C#语言开发的医疗信息系统10套源码

java/C#语言开发的医疗信息系统10套源码 云HIS系统源码&#xff0c;云LIS系统源码&#xff0c;PEIS体检系统&#xff0c;手麻系统 源 码&#xff0c;PACS系统源码&#xff0c;微源预约挂号源码&#xff0c;医院绩效考核源码&#xff0c;3D智能导诊系统源码&#xff0c;ADR药物…

数据分析场景,连号相关业务

连号相关业务 业务场景&#xff1a;现在需要从a列一堆编号中&#xff0c;将连号范围在10以内的数据分别分成一组。 先看实先效果 演示的为db2数据库&#xff0c;需要含有窗口函数&#xff0c;或者可以获取到当前数据偏移的上一位数据 第一步&#xff1a;将A列数据正序第二步…

【笔试强训_Day06】

文章目录 1.字符串相乘 1.字符串相乘 题目链接 解题思路&#xff1a; 高精度乘法&#xff0c;注意要学会下面这种列式相乘的形式&#x1f34e; 注意细节❗&#xff1a; ① &#x1f34e; 首先把列式相乘的数据都存放到数组中去&#xff0c; 然后再对数组中的数据进行取余进…

Web开发:ASP.NET CORE的前端demo(纯前端)

目录 一、建立项目 二、删除无用文件 三、样式添加 四、写一个登录页面 五、登录主界面 一、建立项目 二、删除无用文件 三、样式添加 将你的图片资源添加在wwwroot下方&#xff0c;例如pics/logo.png 四、写一个登录页面 将Privacy.cshtml改为 Forget.cshtml &#xff0…

喜报 | 英码科技顺利通过2023年度广东省工程技术研究中心认定

近日&#xff0c;广东省科学技术厅公示了2023年度广东省工程技术研究中心的名单&#xff0c;英码科技设立的“广东省人工智能与边缘计算工程技术研究中心”顺利通过2023年度广东省工程技术研究中心的认定&#xff1b;英码科技在边缘计算领域的技术创新能力、科技成果转化再次获…

452. 用最少数量的箭引爆气球[排序+贪心]

https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xst…

ZooKeeper写数据流程

ZooKeeper写数据流程 初始化连接&#xff1a; 客户端初始化与 ZooKeeper 集群的连接&#xff0c;连接可以是 TCP 连接或者基于 UDP 的通信。客户端可以连接到集群中的任何一个节点。 查找 Leader&#xff1a; 当客户端发送写请求时&#xff0c;如果连接的节点不是 Leader&…

最新版frp将家里的nas机器内网穿透(含域名配置)

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。 前言 最近&#xff0c;家里整了个nas,自此开始入坑nas,由于是黑群晖&#xff0c;所以没有带公网访问的功能&#xff0c;只能自己研究了。 好在之前用过frp&#xff0c;整过内网穿透&…

springboot+vue社区报修便民维修网站设计与实现

便民维修网站&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、管理员管理、用户管理、维修人员管理、在线报修管理、联系客服管理、公司回访管理、基础数据管理、论坛管理、公告管理、轮播图信息&#xff0c;维修人员&#xff1b;首页、个人中心、维修人员评价管…

ViM-UNet:用于生物医学细分的 Vision Mamba

ViM-UNet&#xff1a;用于生物医学细分的 Vision Mamba 摘要IntroductionMethod and Experiments结果与讨论 ViM-UNet: Vision Mamba for Biomedical Segmentation 摘要 卷积神经网络&#xff08;CNNs&#xff09;&#xff0c;尤其是UNet&#xff0c;是生物医学分割的默认架构…

springboot+java照相馆预约管理系统ssm

框架&#xff1a;ssm/springboot都有 jdk版本&#xff1a;1.8 及以上 ide工具&#xff1a;IDEA 或者eclipse 数据库: mysql 编程语言: java 前端&#xff1a;layuibootstrapjsp 详细技术&#xff1a;HTMLCSSJSjspspringmvcmybatisMYSQLMAVENtomcat 开发工具 IntelliJ IDEA: 一…

利用二维码定位技术实现桌面机器人简易定位方案(下篇)

目录 1、前言2、定位标签定义3、识别算法流程4、python编程4.1 查找三个回字定位点python4.2 根据三个定位点坐标位置关系&#xff0c;进行识别python4.3 根据实际坐标对当前图像进行矫正python4.4 计算物体的坐标值python 总结本篇对应python源码 1、前言 机械手臂尤其是工业…

vue全屏后下拉框失效

如图&#xff0c;vue页面有个全屏功能 问题&#xff1a;全屏后下拉菜单消失 解决&#xff1a;加个这个 :teleported"false"如果不行试试这个 :popper-append-to-body"false"ok我话说完

【1425】java 外籍人员管理系统Myeclipse开发mysql数据库web结构jsp编程servlet计算机网页项目

一、源码特点 java 外籍人员管理系统是一套完善的java web信息管理系统 采用serlvetdaobean&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式 开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff…

Mac M3 安装Ollama和llama3,本地部署LobeChat和刘皇叔聊三国!

OllamaLobeChat&#xff0c;本地部署聊天助手 Ollama安装下载OllamaOllama常用指令和链接运行OllamaAPI 交互Ollama基于Llama 3角色扮演 LobeChat安装首先安装docker安装LobeChat的docker 镜像和运行 Ollama安装 下载Ollama 网址&#xff1a;https://ollama.com/ 支持macOS、…

每日一题(L2-011):玩转二叉树--建树+层序遍历

与L2-006近乎相同&#xff0c;先建树&#xff0c;然后遍历 #include<bits/stdc.h> using namespace std; int in[35]; int pre[35]; typedef struct Tree{int num;Tree* left;Tree* right; }T;T * build(int in1,int in2,int pre1,int pre2){T * tnew T;t->numpre[pr…

代码优化实践之税率计算问题

开篇 今天的问题来自于《编程珠玑》第三章【数据决定程序结构】&#xff0c;这里提出了几条代码优化相关的原则&#xff0c;受益不浅。下面是提到的几条原则&#xff1a; 使用数组重新编写重复代码。冗长的相似代码往往可以使用最简单的数据结构——数组来更好的表述&#xff1…

JS-29-Promise对象

一、JavaScript的异步操作 在JavaScript的世界中&#xff0c;所有代码都是单线程执行的。 由于这个“缺陷”&#xff0c;导致JavaScript的所有网络操作&#xff0c;浏览器事件&#xff0c;都必须是异步执行。异步执行可以用回调函数实现&#xff1a; function callback() {c…

三种空间数据的聚合算法

原始数据分布 给老外做的Demo&#xff0c;所以是英文界面。 原始数据分布情况如下&#xff1a; geojson文本内容&#xff1a; 三种方法基本原理 三种聚合算法来做一个例子&#xff08;500条记录&#xff09;。 方法1&#xff1a;按Ol默认方法进行聚类&#xff0c;使用Open…

Python+GDAL 栅格坐标系转换(自动计算输出像元大小)

GDAL对栅格进行坐标系转换不难&#xff0c;直接用gdal.Warp()就可以了 gdal.Warp("output", "input", dstSRSEPSG:***, xRes**, yRes**, targetAlignedPixelsTrue)麻烦的是&#xff0c;需要的参数xRes和yRes&#xff0c;gdal.Warp()不能自动计算。坐标系转…