【OpenCV C++20 学习笔记】提取图片中的水平和垂直对象

news2024/12/24 3:42:10

提取图片中的水平和垂直对象

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

原理

在腐蚀和膨胀操作中,通过卷积核(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/1983754.html

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

相关文章

在优化微信、支付宝小程序用户体验时有哪些关键指标

在优化小程序用户体验时&#xff0c;有几个关键指标需要特别关注&#xff0c;这些指标不仅能够帮助评估当前的用户体验状况&#xff0c;还能为后续的优化工作提供明确的方向。以下是一些关键指标及其解释&#xff1a; 1. 日活跃用户&#xff08;DAU&#xff09; 是指每天使用…

搭建Mybatis,实现数据库增删改

在pom.xml文件中粘贴下列&#xff0c;加载maven依赖 <?xmlversion"1.0"encoding"UTF-8"?> <projectxmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation&…

2024年武汉东湖高新区职称第二批次开始了

众所周知&#xff0c;武汉市东湖高新区职称一年两批次&#xff0c;今年下半年第二批水平能力测试报名也已经开始了&#xff0c;请注意报名时间&#xff0c;别错过&#xff01;&#xff01; 2024年武汉东湖高新区第二批次水测报名时间&#xff1a;&#xff08;一&#xff09;网上…

letcode 分类练习 哈希表 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

letcode 分类练习 哈希表 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和 242.有效的字母异位词349.两个数组的交集202.快乐数1.两数之和 242.有效的字母异位词 分别定义两个字母哈希表就可以了 class Solution { public:bool isAnagram(string s, strin…

sql注入-常见注入方法复现

环境演示均已sql-labs为例 1、报错注入 1.1常用的报错注入的函数 掌握好extractvalue、updatexml、floor报错&#xff0c;floor报错较难需要多理解&#xff0c;updatexml较为常用 定义 报错注入是通过特殊函数错误使用并使其输出错误结果来获取信息的。是一种页面响应形式…

MySQL练习题(五)

环境搭建&#xff08;MySQL:8.0.25&#xff09;: CREATE TABLE DEPT (DEPTNO int PRIMARY KEY,##部门编号DNAME VARCHAR(14), ##部门名称LOC VARCHAR(13) ##部门地址 ); INSERT INTO DEPT VALUES (10, ACCOUNTING, NEW YORK); INSERT INTO DEPT VALUES (20, RESEAR…

sqllab靶场练习第1~15关

1、第一关 代码解析 if(isset($_GET[id]))//判断获取的id字段是否为空 { $id$_GET[id]; //logging the connection parameters to a file for analysis. $fpfopen(result.txt,a);//打开这个文件&#xff0c;记录操作的日志 fwrite($fp,ID:.$id."\n"); fclose($fp);…

Java、PHP、Node 操作 MySQL 数据库常用方法

一、Java 操作 MySQL 数据库 1、Java 连接 MySQL 数据库 1. 使用 JDBC 驱动程序连接 使用这种方式&#xff0c;首先需要导入 MySQL 的 JDBC 驱动程序依赖&#xff0c;然后通过 Class.forName() 方法加载驱动程序类。其创建连接的过程相对直接&#xff0c;只需提供准确的数据库…

我不喜欢蔚来的原因

商业逻辑 蔚来主打的是换电和服务。 先说服务&#xff0c;这种极其依赖人工&#xff0c;打造极致尊贵感的服务&#xff0c;是完全无法随着销售规模扩大而降低成本的&#xff0c;相反&#xff0c;如果要保持相同水准的服务&#xff0c;可能会导致成本随着规模扩大而增高&#x…

桥田动态|盛夏官宣 · 桥田全新品牌形象正式发布

VISUAL IDENTITY 2024品牌革新 桥田智能品牌VI系统升级发布 Hi 各位伙伴们&#xff0c;很高兴的告知大家&#xff0c;我们的品牌形象VI和官方网站全面升级。全新的LOGO设计和官方网站将为您带来更为直观和互动的体验。 1 品牌标识 Brand identity 全新LOGO&#xff0c;彰显…

学习STM32(3)--STM32单片机中断的应用

1 引 言 本次实验旨在深入探究STM32F103单片机中断的应用。通过实验&#xff0c;我们将全面掌握STM32F103中断的定义、NVIC&#xff08;Nested Vectored Interrupt Controller&#xff0c;嵌套向量中断控制器&#xff09;的使用和开发&#xff0c;以及外部中断&#xff08;E…

SpringBoot AOP 简单的权限校验

本篇文章的主要内容是通过AOP切面编程实现简单的权限校验。 书接上回登录与注册功能 我们的用户表里面不是有role(权限)这个字段吗 在JWT令牌的生成中&#xff0c;我们加入了role字段。 那么接下来&#xff0c;我们就可以通过这个字段来实现权限校验。 我这里就很简单&#x…

pywinauto:Windows桌面应用自动化测试(七)

前言 上一篇文章地址&#xff1a; pywinauto&#xff1a;Windows桌面应用自动化测试&#xff08;六&#xff09;-CSDN博客 下一篇文章地址&#xff1a; 暂无 一、实战常用方法 1、通过Desktop快速获取窗口 通过之前章节我们了解到控制应用的方法为Application&#xff0…

实战OpenCV之环境安装与配置

OpenCV是什么 OpenCV&#xff0c;英文全称为Open Source Computer Vision Library&#xff0c;是一个开源的计算机视觉和机器学习软件库。它设计用于提供一系列功能强大的算法&#xff0c;以帮助开发者处理图像和视频数据&#xff0c;实现各种视觉任务&#xff0c;包括&#xf…

Nginx进阶-常见配置(三)

nginx 变量 Nginx的配置文件使用的语法的就是一门微型的编程语言。既然是编程语言&#xff0c;一般也就少不了“变量”这种东西。 Nginx配置文件使用的语法主要包括以下几个方面&#xff1a; &#xff08;1&#xff09;配置块 (Block Directives): Nginx配置文件由多个嵌套的…

渗透8-05作业

第十五关: 利用时间盲注 拦截并放到intruder里 请求的参数替换或改写一下 uname1 OR IF(SUBSTRING((SELECT database()), 2, 1)e, sleep(1), 1) -- &passwd1&submitSubmit payload1设置 payload2设置&#xff08;点击从列表中添加可快速找到a到z的列表&#xff09; 开始…

Python | Leetcode Python题解之第326题3的幂

题目&#xff1a; 题解&#xff1a; class Solution:def isPowerOfThree(self, n: int) -> bool:return n > 0 and 1162261467 % n 0

spdlog日志库--基础介绍

文章目录 1. 简介1.1. spdlog代码特点1.2. 说明1.3. spdlog架构 2. spdlog的安装2.1. 使用包管理器安装2.2. 使用源码安装2.3. 仅使用头文件 3. 相关概念3.0 常用的头文件3.1. level_enum3.2. sink3.3. logger3.4 格式输出3.5 对齐方式3.6 截断3.7 字符串格式化fmt 4. 特性4.1.…

职业教育云计算实验实训室建设应用案例

云计算作为信息技术领域的一次革命&#xff0c;正在深刻改变着我们的工作和生活方式。随着企业对云计算技术的依赖日益加深&#xff0c;对具备云计算技能的专业人才的需求也日益迫切。职业院校面临着培养符合行业标准的云计算人才的挑战。唯众凭借其在教育技术领域的专业经验&a…

Web3时代的智能合约:区块链技术的革命性应用

随着区块链技术的发展&#xff0c;Web3时代已经悄然来临。智能合约作为这一时代的重要组成部分&#xff0c;正引领着技术应用的革命性变革。本文将深入解析智能合约的概念、工作原理、应用场景及其带来的挑战与机遇&#xff0c;全面展现其在Web3时代的重要作用。 一、智能合约…