当前位置: 首页 > 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风格的美感度…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...