当前位置: 首页 > news >正文

【OpenCV C++20 学习笔记】直方图计算-split, calcHist, normalize

直方图计算-split, calcHist, normalize

  • 广义直方图
  • 示例
    • 目标
    • 分离通道
    • 计算直方图
    • 绘制计算结果
      • 归一化
      • 绘制
    • 最终结果

广义直方图

直方图的横坐标除了可以是图片中的强度值,也可以是任何其他我们想要观察的特征。例如,下面的图片矩阵中包含了0-255的强度值:
图片矩阵
如果想观察每个宽度为16的强度值区间上的频数分布,我们就可以将横坐标分成下面的区间:
[ 0 , 255 ] = [ 0 , 15 ] ∪ [ 16 , 31 ] ∪ . . . . . ∪ [ 240 , 255 ] r a n g e = b i n 1 ∪ b i n 2 ∪ . . . . . ∪ b i n n = 15 [0, 255] = [0, 15] \cup [16, 31] \cup ..... \cup [240, 255] \\ range = bin_1 \cup bin_2 \cup ..... \cup bin_{n=15} [0,255]=[0,15][16,31].....[240,255]range=bin1bin2.....binn=15
这样就可以得到类似于下图的直方图:
区间直方图
直方图中的元素的定义如下:

  1. 维数(dims):即想要观察的参数的数量,比如上例中只观察灰度图中每个像素的强度值,因此dims = 1
  2. 组数(bins):每个维度中的数据被分组的数量,比如上例中分了16组区间,所以bins = 16
  3. 全距(range):被观察的数据的总区间,比如上例中range = [0, 255]

如果你相观察的参数不止一个,比如说2个,即dims = 2,那就需要画一个3维的图了。

示例

目标

  1. 导入图片
  2. 分离通道:用split函数将图片分离为R, G, B3个矩阵数据
  3. 计算直方图:用calcHist函数对分离出来的3个矩阵分别计算直方图
  4. 绘制计算结果

分离通道

split函数,其原型如下:

void cv::split(	const Mat& src,Mat*		mvbegin)

该函数将多通道的矩阵数组分成多个单通道的矩阵数组,其中:

  • src为要进行通道分离的原矩阵
  • mvbegin为接收分离结果的数组的指针,该数组的长度要和原矩阵的通道数相同

该函数还有以下更便利的重载版本(第2个参数不再是指针,而是多维数组):

void cv::split(	InputArray			m,OutputArrayOfArrays	mv)

在本例中的应用如下:

Mat src{ imread("lena.jpg") };	//导入图片vector<Mat> bgr_planes;	//接收通道分离结果的向量
split(src, bgr_planes);	//通道分离之后,bgr_planes中的3个元素分别是b,g, r,3个通道的数据矩阵

原图是 512 × 512 512 \times 512 512×512的3通道矩阵,在VS调试中,可以看到分离出来的结果bgr_planes中分别有3个元素,而每个元素也是 512 × 512 512 \times 512 512×512的矩阵。
通道分离结果
至此,通道分离完成;接下来对每个通道进行直方图计算。

计算直方图

计算直方图用的函数是calcHist,该函数有3个版本,这里选择比较常用的一个版本,其原型如下:

void cv::calcHist(	const Mat * 	images,int 			nimages,const int * 	channels,InputArray 		mask,OutputArray 	hist,int 			dims,const int * 	histSize,const float ** 	ranges,bool 			uniform = true,bool 			accumulate = false )	
  • imagesconst Mat*类型,可以是一个图片矩阵的指针,也可以是图片矩阵的数组;代表需要计算直方图的图片
  • nimages:第1个参数中包含的图片数量
  • channelsconst int*类型,可以是一个整数常量,也可以是整数数组,代表对应图片中需要进行计算的通道索引,从0开始。如果数组的话,也就是说有多张图片,且每张图片中需要有多个通道被计算,那么这个参数可能遵循以下写法:
    [ i m a g e s [ 0 ] . c h a n n e l s ( ) − 1 , i m a g e s [ 0 ] . c h a n n e l s ( ) , i m a g e s [ 0 ] . c h a n n e l s ( ) + i m a g e s [ 1 ] . c h a n n e l s ( ) − 1 , . . . , ∑ i m a g e s [ n ] . c h a n n e l s ( ) ] [images[0].channels()-1, images[0].channels(), \\ images[0].channels()+images[1].channels()-1, ... , \sum images[n].channels() ] [images[0].channels()1,images[0].channels(),images[0].channels()+images[1].channels()1,...,images[n].channels()]
  • mask:可以不指定,如果指定则其中的矩阵必须是与对应图片具有相同尺寸且是8位的数据类型,从而给图片提供了一个掩码
  • hist:输出结果
  • dims:直方图的维数,必须是正数,且不能超过32
  • histSize:直方图每个维度的组数
  • rangesconst float **类型,所以必须是一个数组的住宿。代表直方图每个维度的全距;如果是均匀分布的直方图,那每个维度只需要提供最大值和最小值就行了,即 m i n , m a x {min, max} minmax,注意是左闭右开的区间;如果不是均匀分布的直方图,则需要提供每组的最小值及最后一组的最大值,即KaTeX parse error: Expected '}', got 'EOF' at end of input: …{histSize[i]-1}
  • uniform:是否为均匀分布
  • accumulate:是否允许覆盖,即不清除之前的直方图

该函数也有其他重载版本,参数即原理基本与上述版本相同,这里就不赘述了。
乍一看这个函数非常复杂,事实也确实如此。但是在本例中,因为我们将一个3通道的矩阵分离成3个单通道的矩阵,然后分别对它们进行计算,所以事情就变得相对简单了。我们直接看代码和注释吧:

int histSize{ 256 };	//定义直方图中的组数为256,即每个强度值一组//定义直方图中的全距
float range[]{ 0, 256 };			//表示全距的区间,左闭右开
const float* histRange[]{ range };	//由于直方图只有一个维度,所以数组只有一个元素bool uniform{ true };		//均匀分布
bool accumulate{ false };	//不允许覆盖Mat b_hist, g_hist, r_hist;	//接收计算结果的矩阵
//b通道的直方图计算
calcHist(&bgr_planes[0],	//b通道矩阵,因为形参是指针类型,所以要加取址符&1,						//只有b通道一个矩阵,相当于只有一张图片,所以nimages = 10,						//矩阵中只有一个通道,所以只有一个通道索引,且从0开始,channels = 0Mat(),					//空矩阵代表不使用掩码b_hist,					//接收计算结果的矩阵1,						//直方图只有一个维度,即b的强度值的频数分布,dims = 1&histSize,				//直方图的组数,因为是指针类型,所以要加取址符&histRange,				//直方图的全距,因为这里只有1个维度、1个矩阵,所以该数组包含一个区间uniform,				//均匀分布accumulate);			//不允许覆盖
//g通道的直方图计算
calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, histRange, uniform, accumulate);
//r通道的直方图计算
calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, histRange, uniform, accumulate);

对于单通道的矩阵来说,很多需要传入数组的形参,只要传入字面量就行了,所以简化了很多。
在Image watch中查看计算结果:
直方图计算结果
可以看到每个通道的计算结果都是 1 × 256 1 \times 256 1×256的矩阵,代表原图中每个通道上从0到255这256个强度值的频数。

绘制计算结果

归一化

在绘制直方图的之前需要对数据进行归一化,从而使数据的值域能够适应直方图尺寸。这就要用到normalize函数,其原型如下:

void cv::normalize(	InputArray			src,InputOutputArray	dst,double				alpha = 1,double				beta = 0,int					norm_type = NORM_L2,int					dtype = -1,InputArray			maxk = noArray())
  • alpha:值域归一化中的值域最小值
  • beta:值域归一化中的值域最大值
  • norm_type:归一化类型
  • dtype:输出矩阵的数据类型,默认为-1,即与原矩阵保持一致
  • mask:掩码矩阵(可选)

这里我们定义的直方图的尺寸是 512 × 400 512 \times 400 512×400,而直方图的计算结果肯定会有超出512的数值,所以必须进行归一化处理:

int hist_w{ 512 }, hist_h{ 400 };	//直方图的长和宽Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));	//用来绘制直方图的图片//对直方图计算结果进行归一化处理
normalize(b_hist,	b_hist,0,				//归一化之后值域的最小值 alpha = 0histImage.rows,	//归一化之后值域的最大值 alpha = 400NORM_MINMAX,	//归一化类型-1,				//输出结果类型与原矩阵一致Mat());			//空矩阵代表不是用掩码
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

归一化之后,3个通道的计算结果的值域都在0到400之间。

绘制

绘制直方图的基本思路是将每个通道中的计算结果(频数结果)转换成点的坐标(横坐标为强度值,纵坐标为结果值,即频数),然后将每个点和前一个点进行连线,最后组成一条完整的折线。具体实现方法如下:

int bin_w{ cvRound(static_cast<double>(hist_w / histSize)) };	//每组的宽度,即组距
for (int i{ 1 }; i < histSize; i++) {line(histImage,//前一个点Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),//当前点(注意,原点在图的左上角)Point(bin_w * (i),	//组距X当前索引=当前点的横坐标hist_h - cvRound(b_hist.at<float>(i))),	//图的高度-当前的频数值=当前点的纵坐标Scalar(255, 0, 0), 2, 8, 0);line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))),Scalar(0, 255, 0), 2, 8, 0);line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))),Scalar(0, 0, 255), 2, 8, 0);
}

最终结果

计算结果绘制
右边就是左图的直方图计算结果。横坐标是0-255的每个强度值,纵坐标分别为R, G, B3个通道的强度值频数。

相关文章:

【OpenCV C++20 学习笔记】直方图计算-split, calcHist, normalize

直方图计算-split, calcHist, normalize 广义直方图示例目标分离通道计算直方图绘制计算结果归一化绘制 最终结果 广义直方图 直方图的横坐标除了可以是图片中的强度值&#xff0c;也可以是任何其他我们想要观察的特征。例如&#xff0c;下面的图片矩阵中包含了0-255的强度值&…...

js入门经典学习小结

简介 js是解释型语言&#xff0c;虽然名字有java&#xff0c;但和java&#xff0c;c等编译型语言不同&#xff0c;它是解释型的&#xff0c;类似perl&#xff0c;py 历史 90年代最早js 1.0版本是网景navigator2引入的 然后欧洲计算机制造商协会&#xff08;ECMA&#xff09…...

nps内网穿透之——腾讯云服务器和linux虚拟机

准备 1、客户端&#xff1a;准备一个内网的linux内网主机&#xff0c;或是一个虚拟机。 2、服务端&#xff1a;准备一个云服务器&#xff08;阿里、腾讯、华为都行&#xff09;。 安装方式&#xff1a; 1、自己到Github官网下载安装包上传。 下载地址&#xff1a;https://…...

大数据知识点

VMWare 设置网段 虚拟机设置 JDK部署 云平台 创建VPC 找到阿里云控制台里的VPC&#xff0c;点击专有网络 安全组 搁置…有需要再使用&#xff0c;因为每月要花200左右 大数据 数据导论...

【计算机毕设项目】2025级计算机专业项目推荐 (前后端Web项目)

以下项目选题适合计算机专业大部分专业&#xff0c;技术栈主要为&#xff1a;Java语言&#xff0c;SSMVue框架&#xff0c;MySQL数据库 后台免费获取源码&#xff0c;可提供远程调试、环境安装配置服务&#xff08;文末有联系方式&#xff09; 以下是本次部分项目推荐1-end&a…...

【MySQL】2.MySQL实际操作

目录 一、数据分析基本流程 注&#xff1a;Navicat快捷键 二、获取数据后的代码操作 &#xff08;1&#xff09;探索数据&#xff0c;查看定义 &#xff08;2&#xff09;筛选有用的字段 &#xff08;3&#xff09;建新表&#xff08;查询建表插值 三合一&#xff09; 注意…...

Winform画圆以及无边框窗体的移动

普通圆 在WinForms中绘制一个圆形&#xff0c;可以通过几种方式实现&#xff1a; 1. 使用ControlPaint类 在窗体的Paint事件中使用ControlPaint.DrawCircle方法来绘制圆形。 private void Form1_Paint(object sender, PaintEventArgs e) {int x 100; // 圆心的X坐标int y …...

如何高效记录并整理编程学习笔记?

高效记录并整理编程学习笔记是提升编程学习效率和效果的重要方法。以下是一些具体的步骤、工具及其使用方法的介绍&#xff1a; 一、高效记录笔记的方法 专注理解&#xff1a;在记录笔记时&#xff0c;首先要保持高度的专注&#xff0c;努力理解老师或教程中讲解的知识点。避免…...

docker的安装和常用命令

docker的安装和常用命令 安装老版本新版本 镜像源配置常用命令基本命令清理文件复制构建镜像上传镜像 补充权限不足无目录权限无用户权限 容器访问jenkins推送镜像失败修改主机名编写Dockerfile 注&#xff1a;这里的安装是针对于cetnos7。 安装 老版本 安装老版本可能遇到报…...

haproxy 7000字配图超详细教程 从小白到入门

简介&#xff1a;HAProxy是一个免费的负载均衡软件&#xff0c;可以运行于大部分主流的Linux操作系统上。HAProxy提供了L4(TCP)和L7(HTTP)两种负载均衡能力&#xff0c;具备丰富的功能。HAProxy的社区非常活跃&#xff0c;版本更新快速,HAProxy具备媲美商用负载均衡器的性能和稳…...

使用 LangChain 掌握检索增强生成 (RAG) 的终极指南:5、将自然语言问题转换为结构化查询

5. 查询构建 — Ragatouille 用户用自然语言提出问题并被路由到特定数据源&#xff08;例如&#xff0c;向量存储、图形数据库等&#xff09;后&#xff0c;该问题需要被转换为结构化查询&#xff0c;以便从选定的数据源检索信息&#xff08;例如&#xff0c;文本到SQL、文本到…...

浅析JavaScript 堆内存及其通过 Chrome DevTools 捕获堆快照的方法

JavaScript 的堆内存&#xff08;Heap Memory&#xff09;是内存中专门用于存放程序执行过程中动态生成的对象、函数实例以及其他动态数据结构的区域。与调用栈&#xff08;Call Stack&#xff09;专注于管理函数调用的顺序和执行环境不同&#xff0c;堆内存则专注于动态地分配…...

C++学习笔记----2、使用C++进行优雅编程(五)----命名

C编译器对于命名有如下规则&#xff1a; 命名中可以有大小写字母、数字、下划线。字母不限于英文字符&#xff0c;可以是任意国家语言的字母&#xff0c;例如日文&#xff0c;阿拉伯文等。不能以数字开头&#xff0c;例如9to5。包含双下划线的被标准库保留不可使用&#xff0c…...

Element UI顶部导航栏与左侧导航栏联动实现~

需求&#xff1a;点击顶部导航栏的不同栏位实现左侧导航栏菜单的不同展示实现联动效果。 点击顶部导航栏按钮将对应的左侧导航栏数据传递给vuex,并在左侧导航栏父组件中接收并传递给左侧导航栏子组件&#xff0c;使用递归组件实现渲染等&#xff0c;具体的优化可以看下面的注释…...

ECMAScript6模板字面量:反引号、${}占位符的使用

ECMAScript 6 中引入了模板字面量&#xff0c;主要通过多行字符串和字符串占位符对字符串进行增强操作。如下&#xff1a; //使用ECMAScript6模板字面量拼接字符串&#xff0c;例如&#xff1a;2024年8月12日 15:38:28 星期一 let dateRet ${Year}年${Month}月${Dates}日 ${H…...

网关与AWS云心跳周期,网关断电或者网络不稳定的离线机制

当mqtt连线建立时, 需要指定keep alive参数,当 iot core在1.5倍 keep alive timeout时长内都没收到任何来自设备端的操作, 例如 ping, subscribe, publish &#xff0c;则会主动将连线中断。如果iot core检测到tcp 连接中断, 会立即中断&#xff1b;如果未检测到则会等到1.5倍 …...

【代码随想录训练营第42期 Day26打卡 贪心Part1 - LeetCode 455.分发饼干 376. 摆动序列 53. 最大子序和

目录 一、贪心 二、题目与题解 题目一&#xff1a;455.分发饼干 题目链接 题解&#xff1a;排序双指针贪心 题目二&#xff1a;376. 摆动序列 题目链接 题解&#xff1a;贪心 题目三&#xff1a;53. 最大子序和 题目链接 题解1&#xff1a;暴力&#xff08;失败&…...

利用有限元法(FEM)模拟电磁场与样品的相互作用

一、引言 电磁场与物质的相互作用是理解光学现象的基础。在实际应用中&#xff0c;激光光束与样品的相互作用通常涉及复杂的电磁场分布&#xff0c;尤其在微纳尺度结构中。因此&#xff0c;使用数值模拟方法如有限元法&#xff08;FEM&#xff09;来模拟电磁场的分布和传播&…...

如何保持git主分支树的整洁

经典应用展示Git版本控制用法 本章将列举Git的一些闪亮特性,期待能够让您爱上Git 文章目录 经典应用展示Git版本控制用法前言一、分支是什么?二、主-分支合并merge三、cherry-pick(精挑细选)四、Rebase(变基)4.1 合并本地分支到主分支4.2 合并本地分支从指定commit开始的…...

Datawhale X 魔搭 AI夏令营 Task1 从零入门AI生图原理实践笔记

赛题内容 参赛者需在可图Kolors模型的基础上训练LoRA模型&#xff0c;生成无限风格&#xff0c;如水墨画风格、水彩风格、赛博朋克风格、日漫风格… 基于LoRA模型生成8张图片组成连贯故事&#xff0c;故事内容可自定义&#xff1b;基于8图故事&#xff0c;评估LoRA风格的美感度…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...