【OpenCV C++20 学习笔记】提取水平和垂直线条

news2024/11/15 21:23:50

提取水平和垂直线条

  • 原理
  • 实操——去除五线谱的五线
    • 二进制化
    • 提取垂直对象
    • 完善边缘和最终输出图片
      • 黑白反转
      • 平滑
    • 完整代码
  • 其他图片元素提取实践
    • 提取水平线条
    • 提取音符轮廓

原理

在腐蚀和膨胀操作中,通过卷积核(kernel),或者结构元素(structuring element),将每次被结构元素扫描到的区域中的最小值或最大值赋予其锚点(一般为结构元素的中心),从而实现扩大或缩小图像中的对象的轮廓。

关于膨胀和腐蚀运算,可以参考本合集中的另一篇文章《腐蚀和膨胀》

从腐蚀和膨胀的具体操作中可以看出,结构元素是非常重要的,它决定了最终的计算效果。
结构元素由0和1组成,可以自由排列。它的矩阵尺寸要比被计算的图片的矩阵尺寸小很多。结构元素的中心,被称为原点(origin)或者锚点(anchor point)。这个点是用来定位每次被操作的像素点的。
结构元素可以有很多形状:线型、钻石型、圆盘型等等。要根据具体用途来选择结构元素的形态。通常是,想要在图片中提取或者操作什么形态的对象就用什么形态的结构元素。比如说,如果想要在图片中定位线条,就可以创建一个线型的结构元素。这也是本文的示例中要进行的操作。

实操——去除五线谱的五线

这里使用的图片可以在安装OpenCV的路径中找到:...\opencv\sources\samples\data\note.png。目标是去掉图中的五线谱,只保留谱号、升降号和音符。
五线谱图片
在导入图片之后,要将图片进行灰度化,或者直接以灰度格式读取图片:

Mat src{ imread("notes.png", IMREAD_GRAYSCALE) };

二进制化

读入灰度图片之后,要先进行二进制化。使用的是adptiveThreshold()函数,该函数能够应用自适应的阈值将灰度图片转换成二进制的图片。它包含两种阈值类型:THRESH_BINARYTHRESH_BINARY_INV。它们各自的算法如下:

  • THRESH_BINARY
    d s t ( x , y ) = { m a x V a l u e if  s r c ( x , y ) > T ( x , y ) 0 otherwise dst(x,y)= \begin{cases} maxValue & \quad \text{if } src(x,y)>T(x,y)\\ 0 & \quad \text{otherwise} \end{cases} dst(x,y)={maxValue0if src(x,y)>T(x,y)otherwise
  • THRESH_BINARY_INV
    d s t ( x , y ) = { 0 if  s r c ( x , y ) > T ( x , y ) m a x V a l u e otherwise dst(x,y)= \begin{cases} 0 & \quad \text{if } src(x,y)>T(x,y)\\ maxValue & \quad \text{otherwise} \end{cases} dst(x,y)={0maxValueif src(x,y)>T(x,y)otherwise
  • T ( x , y ) T(x,y) T(x,y)是对每个像素进行计算的自适应阈值,由自适应阈值计算方法方法确定
  • s r c src src d s t dst dst,分别为原图和输出图

所以上述两种阈值类型下的算法都很简单。当图中的像素值大于该像素点的阈值的时候,该像素就会被赋予一个用户自定义的值( m a x V a l u e maxValue maxValue),或者0。
那么每个像素点的阈值是如何确定的呢?

  • 首先阈值是由像素点的邻近像素共同决定,这个邻近区域是个方形区域,其边长记为 b l o c k S i z e blockSize blockSize,则该区域一共有 b l o c k S i z e × b l o c k S i z e blockSize\times blockSize blockSize×blockSize个像素点;
  • 然后指定一个常数 C C C
  • 接着根据不同的自适应方法计算相应的阈值:
    • ADAPTIVE_THRESH_MEAN_C方法:阈值 T ( x , y ) T(x,y) T(x,y)为以像素点 ( x , y ) (x,y) (x,y)为中心的 b l o c k S i z e × b l o c k S i z e blockSize\times blockSize blockSize×blockSize个像素点的平均值减去常数 C C C
    • ADAPTIVE_THRESH_GAUSSIAN_C方法:以像素点 ( x , y ) (x,y) (x,y)为中心的 b l o c k S i z e × b l o c k S i z e blockSize\times blockSize blockSize×blockSize个像素点都通过一个符合高斯分布的权重系数就行加权,然后求和,最后再减去常数 C C C,得到阈值阈值 T ( x , y ) T(x,y) T(x,y)

下面的代码展示了在本例中如何将图片二进制化:

Mat dst;	//用来储存结果的矩阵
//二进制化
adaptiveThreshold(~src,					//原图
				dst,					//目标图
				255,					//maxValue
				ADAPTIVE_THRESH_MEAN_C,	//自适应阈值计算方法
				THRESH_BINARY,			//阈值类型
				15,						//blockSize
				-2);					//常量C

经过上面的二进制化之后,图片中的像素值就只有两种0或255。所以图片中就只有黑白两种颜色,如下图:
二进制化后的图片

提取垂直对象

像原理中介绍的那样,要提取垂直的对象就要用垂直线型的结构元素,结构元素的高度指定为原图高度的1/30,示意图如下:
垂直线型结构元素
然后使用这个结构元素,进行腐蚀和膨胀运算:

int v_size{ dst.rows / 30 };	//结构矩阵的高度=图片高度/30
//创建结构元素
Mat verticalStructure{ getStructuringElement(MORPH_RECT, Size(1, v_size)) };
//腐蚀和膨胀运算
erode(dst, dst, verticalStructure, Point(-1, -1));
dilate(dst, dst, verticalStructure, Point(-1, -1));

我们先看腐蚀后的效果:
垂直提取中的腐蚀效果
可以看到垂直方向的线条和色块都被保存下来了,水平方向的五条线都被腐蚀掉了。但是目前图片中的音符看起来有点“干瘪”,不太清晰,所以要进行膨胀操作。
膨胀后的效果:
垂直提取中的膨胀效果
膨胀让线条更加清晰。
这样我们就提取了清晰的垂直方向的对象。

完善边缘和最终输出图片

虽然经过上述对于垂直对象的提取我们已经得到了音符,但是还需要进一步的完善来得到最终的结果。

黑白反转

我们希望最终的图片中背景是白色的,而音符是黑色的。所以要进行黑白反转的操作。
这里使用了bitwise_not()函数。该函数对图片中的每个像素值进行按位取反的操作:
d s t ( I ) = ¬ s r c ( I ) dst(I)=\neg src(I) dst(I)=¬src(I)

  • s r c ( I ) src(I) src(I)为原图片中的像素值
  • d s t ( I ) dst(I) dst(I)为按位取反后的像素值

对上一操作的结果vertical进行按位取反:

bitwise_not(dst, dst);	//按位取反

由于vertical已经是值为0或255的8位无符号的数据,所以按位取反之后,255变成0, 0变成255。这就实现了黑白的反转。效果如下:
黑白反转

平滑

上面的音符轮廓看上去还不够平滑,所以要进行平滑操作:

blur(dst, dst, Size(2, 2));	//使用归一化滤波进行平滑

这里使用的是归一化滤波,即将每个像素点替换为以它为中心的2*2的区域的所有像素点的平均值。

关于平滑操作的介绍,可以参照本合集的另一篇文章《图像平滑基础》

平滑之后,效果如下:
平滑后的音符
这样我们就完成了从五线谱中提取音符的所有操作了,完整代码如下:

完整代码

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

import <iostream>;

using namespace cv;

void show_wait_destroy(const char* winname, Mat img);

int main() {
	Mat src{ imread("notes.png", IMREAD_GRAYSCALE) };

	Mat dst;	//用来储存结果的矩阵
	//**二进制化**
	adaptiveThreshold(~src,					//原图
					dst,					//目标图
					255,					//maxValue
					ADAPTIVE_THRESH_MEAN_C,	//自适应阈值方法类型
					THRESH_BINARY,			//阈值类型
					15,						//blockSize
					-2);					//常量C

	//**提取垂直对象**
	int v_size{ dst.rows / 30 };	//结构矩阵的高度=图片高度/30
	//创建结构元素
	Mat verticalStructure{ getStructuringElement(MORPH_RECT, Size(1, v_size)) };
	//腐蚀和膨胀运算
	erode(dst, dst, verticalStructure, Point(-1, -1));
	dilate(dst, dst, verticalStructure, Point(-1, -1));

	//**黑白反转**
	bitwise_not(dst, dst);	//按位取反

	//**平滑**
	blur(dst, dst, Size(2, 2));	//使用归一化滤波进行平滑

	imshow("原图", src);
	imshow("结果图", dst);
	waitKey(0);
}

其他图片元素提取实践

除了可以从原图中提取音符,我们还可以进行以下其他操作。

提取水平线条

像之前讲的,如果我们像提取水平线条类型的图片对象就要使用水平线条型的结构元素,就像下图所示(中间为原点):
水平结构元素
结构元素的宽度指定为图片宽度的1/30。创建结构元素的代码如下:

int horizontal_size{ dst.cols / 30 };	//结构矩阵的宽度=图片宽度/30
//创建结构元素
Mat horizontalStructure{ getStructuringElement(MORPH_RECT,	//指定结构元素形态为矩形
	Size(horizontal_size, 1)) };	//指定结构元素的宽和高

接着,使用这个结构元素进行腐蚀和膨胀运算:

erode(dst, dst, horizontalStructure, Point(-1, -1));	//腐蚀运算
dilate(dst, dst, horizontalStructure, Point(-1, -1));	//膨胀运算

我们先看腐蚀运算后的效果:
腐蚀后的效果
可以看到,用水平线型的结构元素进行腐蚀运算会消除所有垂直的图片对象,只剩下水平线型的对象。
再看膨胀后的效果:
膨胀后的效果
可以看到没什么大的改变,只是将线条的某些部分变得更粗了,线条也更清晰了。
这样我们就提取除了清晰的水平线条了。

提取音符轮廓

还可以描绘出每个音符的轮廓。这个操作需要再音符已经被提取,但还没有进行黑白反转前进行。这个时候我们已经得到了已经被二进制化了的、黑底白色的音符图片。
要描绘音符的轮廓只要对黑白反转后的图片再进行一次二进制化就可以了。不过这次 b l o c k S i z e blockSize blockSize要调得比较小。代码如下:

Mat edges;
adaptiveThreshold(dst,				
				edges,
				255,					//maxValue
				ADAPTIVE_THRESH_MEAN_C,	//自适应阈值计算方法
				THRESH_BINARY,			//阈值类型
				3,						//blockSize
				-2);					//常量C	

与第一次二进制化相比,这次二进制化只是 b l o c k S i z e blockSize blockSize变小了。这里可能看起来有点难以理解,其实仔细想想就能明白。

现在的图片已经变成了白底黑音符的了。对于每个 b l o c k S i z e × b l o c k S i z e blockSize \times blockSize blockSize×blockSize的9个像素点组成的方形区域:

  • 如果其中心小于该区域内所有像素的平均值+常数 C C C(2),就变成0,即黑色。这样所有的白色区域都会变成黑色。因为白色区域的9个像素都是255,其平均值也是255,再加上2,就是257。那所有白色的像素点(255)肯定会小于这个值(257),所以就被赋予0值,变成黑色。同样的,全黑的区域也是如此,所以音符还是黑色。
  • 如果其中心大于该区域内所有像素的平均值+2,就变成 m a x V a l u e maxValue maxValue,即255。这样所有黑色区域的边缘都会变成255。因为黑色区域的边缘部分是白色(255),但这些白色的像素点又与黑色的像素点相邻。所以在9个像素点组成的方形区域内,至少有一个黑色像素点,那它们的平均数最少也有大概226,即使再加上2,也是228。这样的话,白色的像素点(255)肯定会大于这个值(228),那么就会被赋予 m a x V a l u e = 255 maxValue=255 maxValue=255。所以黑色区域边缘的像素边缘的一圈白色像素点肯定会被保留,于是就变成了轮廓。
  • 综上所述,原来白色的区域都变成了黑色,原来黑色的区域还是黑色,只有黑色边缘的一圈白色像素点还是白色。所以图片就会变成下面这个样子:
    描绘轮廓
    上面的轮廓看起来不够清晰,所以用一个较小的( 2 × 2 2 \times 2 2×2)结构元素对它进行一次膨胀操作:
Mat kernel{ Mat::ones(2,2,CV_8UC1) };	//2*2的结构元素
dilate(edges, edges, kernel);	//膨胀运算

效果如下:
对轮廓进行膨胀
可以看到线条变粗、变清晰了。
这样就完成了音符轮廓的提取。

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

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

相关文章

vue-router核心TS类型

NavigationFailureType 枚举&#xff1a; export declare enum NavigationFailureType {/*** An aborted navigation is a navigation that failed because a navigation* guard returned false or called next(false)*/aborted 4,/*** A cancelled navigation is a navigati…

arduino程序-MC猜数字5、6(基础知识)

arduino程序-MC猜数字5、6&#xff08;基础知识&#xff09; 1-23 MC猜数字-5 自定义函数自定义函数自定义清理显示内容函数displayClear&#xff08;&#xff09;带参数函数displayNumber带参数、返回值的函数 1-24 MC猜数字-6 完成制作显示0~9数字函数改造产生随机数字函数改…

嵌入式人工智能(42-基于树莓派4B的红外遥控)

1、简介 红外遥控想必对大家来说都不陌生&#xff0c;红外也属于无线通信的一种&#xff0c;只要是无线通信&#xff0c;必然要用电磁波&#xff0c;要理解无线通信的本质和原理&#xff0c;不管用哪个频段都要学习电磁场与电磁波&#xff0c;这是一个难度很大的课&#xff0c…

IT事件经理在数字企业中的角色和责任

什么是IT事件经理&#xff1f; IT事件经理有时也被称为事件指挥官&#xff0c;他们承担着管理组织事件响应的总体责任&#xff0c;从委派各种事件响应任务到与每个利益相关者进行沟通和协调。 示例&#xff1a;当一个全球性的电子商务平台在一次销售活动中流量激增&#xff0c…

George Danezis谈Mysticeti的共识性能

Sui的新共识引擎Mysticeti已经在主网上开始分阶段推出。Mysten Labs联合创始人兼首席科学家George Danezis在采访中&#xff0c;讨论了其低延迟如何通过高性能应用程序提升用户体验。 采访视频&#xff1a; https://youtu.be/WHvtPQUa2Q0 中文译文&#xff1a;延迟和吞吐量对…

LSTM与GNN强强结合!全新架构带来10倍推理速度提升

今天来推荐一个深度学习领域很有创新性的研究方向&#xff1a;LSTM结合GNN。 GNN擅长处理图数据关系和特征&#xff0c;而LSTM擅长处理时间序列数据及长期依赖关系。通过将两者结合&#xff0c;我们可以有效提升时间序列预测的准确性和效率&#xff0c;尤其是在处理空间和时间…

手搓交换排序、归并排序、计数排序

文章目录 交换排序冒泡排序快速排序hoare版本挖坑法lomuto前后指针 非递归快速排序 归并排序实现计数实现排序代码测试排序算法性能 交换排序 冒泡排序 void BubbleSort(int* arr, int n) {for (int i 0; i < n; i){int flag 0;for (int j 0; j < n - i - 1; j){if …

day13 Java基础——逻辑运算符,位运算符及面试题

day13 Java基础——逻辑运算符&#xff0c;位运算符及面试题 1. 逻辑运算符&#xff1a;与&#xff0c;或&#xff0c;非 package operator;public class Demo07 {public static void main(String[] args) {boolean a true;boolean b false;System.out.println("a &…

【网络问题】网络诊断:远程计算机或设备将不接受连接的解决办法/DNS服务器可能不可用

当网络出现问题时&#xff0c;一定要点击“请尝试运行Windows网络诊断”来获取具体的网络问题&#xff0c; 今天碰到且得以解决的两个问题&#xff1a; 一、远程计算机或设备将不接受连接的解决办法 打开控制面板——点击“网络和Internet”——点击“Internet选项”&#xf…

电脑自动重启是什么原因?重启原因排查和解决办法!

当你的电脑突然毫无预警地自动重启&#xff0c;不仅打断了工作流程&#xff0c;还可能导致未保存的数据丢失&#xff0c;这无疑令人很懊恼&#xff0c;那么&#xff0c;电脑自动重启是什么原因呢&#xff1f;有什么方法可以解决呢&#xff1f;别担心&#xff0c;在大多数情况下…

《从零开始:使用Python构建简单Web爬虫》

前言 随着互联网信息的爆炸性增长&#xff0c;如何高效地获取和处理这些数据变得越来越重要。Web爬虫作为一种自动化工具&#xff0c;可以帮助我们快速抓取所需的网页内容。本文将介绍如何使用Python编写一个简单的Web爬虫&#xff0c;并通过实例演示其基本用法。 准备工作 …

创建互动照片墙:HTML、CSS 和 JavaScript 实战

在这个数字化时代&#xff0c;照片已经成为我们生活中不可或缺的一部分。无论是记录重要时刻&#xff0c;还是分享日常生活&#xff0c;我们都离不开照片。今天&#xff0c;我们将一起探索如何使用 HTML、CSS 和 JavaScript 创建一个互动的照片墙程序&#xff0c;让您可以轻松展…

四步构建App跨渠道归因分析方法

通常来讲&#xff0c;在互联网场景中&#xff0c;最简单也最常用的App归因模型就是基于最后一次点击来源进行归因转化&#xff0c;因为越靠近决策环节的时刻通常影响就越大。 不过有机构对营销测量的研究发现&#xff0c;只有11%的营销人员对他们的归因模型的准确性感到“非常…

大语言模型(LLM)快速理解

自2022年&#xff0c;ChatGPT发布之后&#xff0c;大语言模型&#xff08;Large Language Model&#xff09;&#xff0c;简称LLM掀起了一波狂潮。作为学习理解LLM的开始&#xff0c;先来整体理解一下大语言模型。 一、发展历史 大语言模型的发展历史可以追溯到早期的语言模型…

视频孪生:如何有效利用智慧机房里的视频监控系统?

机房是存储设备和数据的重要场所。常见的机房安全隐患有电源不稳定、设备温度异常、空调及新风系统故障、机房漏水等&#xff0c;因此需要管理人员全天轮班值守巡检。传统机房运维工作繁琐且效率低下&#xff0c;对监控设备的利用率不高&#xff0c;而视频孪生技术能很好地解决…

02 pip指令的使用

pip 是一个现代的&#xff0c;通用的 Python 包管理工具 。提供了对Python 包的查找、下载、安装、卸载的功能。 1. 在安装好的python环境下&#xff0c;进入以下目录可以查看到pip命令。 同样在windows命令窗口进行测试&#xff0c;pip命令是否可用。WindowsR键&#xff0c;使…

“职场中,不要和上司作对”,真的很重要吗?你认同这句话吗?

在职场上&#xff0c;领导对下属的期望永远都只有两个字&#xff0c;不是忠诚&#xff0c;也不是能力&#xff0c;而是省心。 领导对下属的要求就是别让我操心。 在职场中&#xff0c;通常面临的首要问题就是如何与领导相处。 把职场中的前辈当作老师来尊重&#xff0c;你尊…

基础复习(多线程)

线程创建方式 1.继承Thread类 2.实现Runable接口 3.Callable接口实现有返回值的线程 &#xff08;1&#xff09;第一种 提供了一个类叫做Thread&#xff0c;此类的对象用来表示线程。创建线程并执行线程的步骤如下 1.定义一个子类继承Thread类&#xff0c;并重写run方法 2.创建…

无密码sudo

文件路径&#xff1a;/etc/sudoers 修改sudoers文件 进去root 权限&#xff1a;sudo su 加入sudoers 写权限&#xff1a;chmod w sudoers 修改sudoers文件&#xff1a;vim sudoers 根据下面图片修改 wq退出编辑

华为LTC流程体系详解

LTC&#xff0c;全称Lead to Cash&#xff0c;中文翻译为从线索到现金&#xff0c;是一种企业运营管理思想&#xff0c;也是一个集成的业务流程。它涵盖了企业从接触客户到收到客户回款的整个流程&#xff0c;通过科学化管理&#xff0c;实现更高效地将线索客户转化为付费客户。…