【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=bin1∪bin2∪.....∪binn=15
这样就可以得到类似于下图的直方图:

直方图中的元素的定义如下:
- 维数(dims):即想要观察的参数的数量,比如上例中只观察灰度图中每个像素的强度值,因此
dims = 1; - 组数(bins):每个维度中的数据被分组的数量,比如上例中分了16组区间,所以
bins = 16; - 全距(range):被观察的数据的总区间,比如上例中
range = [0, 255];
如果你相观察的参数不止一个,比如说2个,即dims = 2,那就需要画一个3维的图了。
示例
目标
- 导入图片
- 分离通道:用
split函数将图片分离为R, G, B3个矩阵数据 - 计算直方图:用
calcHist函数对分离出来的3个矩阵分别计算直方图 - 绘制计算结果
分离通道
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 )
images:const Mat*类型,可以是一个图片矩阵的指针,也可以是图片矩阵的数组;代表需要计算直方图的图片nimages:第1个参数中包含的图片数量channels:const 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:直方图的维数,必须是正数,且不能超过32histSize:直方图每个维度的组数ranges:const float **类型,所以必须是一个数组的住宿。代表直方图每个维度的全距;如果是均匀分布的直方图,那每个维度只需要提供最大值和最小值就行了,即 m i n , m a x {min, max} min,max,注意是左闭右开的区间;如果不是均匀分布的直方图,则需要提供每组的最小值及最后一组的最大值,即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 广义直方图示例目标分离通道计算直方图绘制计算结果归一化绘制 最终结果 广义直方图 直方图的横坐标除了可以是图片中的强度值,也可以是任何其他我们想要观察的特征。例如,下面的图片矩阵中包含了0-255的强度值&…...
js入门经典学习小结
简介 js是解释型语言,虽然名字有java,但和java,c等编译型语言不同,它是解释型的,类似perl,py 历史 90年代最早js 1.0版本是网景navigator2引入的 然后欧洲计算机制造商协会(ECMA)…...
nps内网穿透之——腾讯云服务器和linux虚拟机
准备 1、客户端:准备一个内网的linux内网主机,或是一个虚拟机。 2、服务端:准备一个云服务器(阿里、腾讯、华为都行)。 安装方式: 1、自己到Github官网下载安装包上传。 下载地址:https://…...
大数据知识点
VMWare 设置网段 虚拟机设置 JDK部署 云平台 创建VPC 找到阿里云控制台里的VPC,点击专有网络 安全组 搁置…有需要再使用,因为每月要花200左右 大数据 数据导论...
【计算机毕设项目】2025级计算机专业项目推荐 (前后端Web项目)
以下项目选题适合计算机专业大部分专业,技术栈主要为:Java语言,SSMVue框架,MySQL数据库 后台免费获取源码,可提供远程调试、环境安装配置服务(文末有联系方式) 以下是本次部分项目推荐1-end&a…...
【MySQL】2.MySQL实际操作
目录 一、数据分析基本流程 注:Navicat快捷键 二、获取数据后的代码操作 (1)探索数据,查看定义 (2)筛选有用的字段 (3)建新表(查询建表插值 三合一) 注意…...
Winform画圆以及无边框窗体的移动
普通圆 在WinForms中绘制一个圆形,可以通过几种方式实现: 1. 使用ControlPaint类 在窗体的Paint事件中使用ControlPaint.DrawCircle方法来绘制圆形。 private void Form1_Paint(object sender, PaintEventArgs e) {int x 100; // 圆心的X坐标int y …...
如何高效记录并整理编程学习笔记?
高效记录并整理编程学习笔记是提升编程学习效率和效果的重要方法。以下是一些具体的步骤、工具及其使用方法的介绍: 一、高效记录笔记的方法 专注理解:在记录笔记时,首先要保持高度的专注,努力理解老师或教程中讲解的知识点。避免…...
docker的安装和常用命令
docker的安装和常用命令 安装老版本新版本 镜像源配置常用命令基本命令清理文件复制构建镜像上传镜像 补充权限不足无目录权限无用户权限 容器访问jenkins推送镜像失败修改主机名编写Dockerfile 注:这里的安装是针对于cetnos7。 安装 老版本 安装老版本可能遇到报…...
haproxy 7000字配图超详细教程 从小白到入门
简介:HAProxy是一个免费的负载均衡软件,可以运行于大部分主流的Linux操作系统上。HAProxy提供了L4(TCP)和L7(HTTP)两种负载均衡能力,具备丰富的功能。HAProxy的社区非常活跃,版本更新快速,HAProxy具备媲美商用负载均衡器的性能和稳…...
使用 LangChain 掌握检索增强生成 (RAG) 的终极指南:5、将自然语言问题转换为结构化查询
5. 查询构建 — Ragatouille 用户用自然语言提出问题并被路由到特定数据源(例如,向量存储、图形数据库等)后,该问题需要被转换为结构化查询,以便从选定的数据源检索信息(例如,文本到SQL、文本到…...
浅析JavaScript 堆内存及其通过 Chrome DevTools 捕获堆快照的方法
JavaScript 的堆内存(Heap Memory)是内存中专门用于存放程序执行过程中动态生成的对象、函数实例以及其他动态数据结构的区域。与调用栈(Call Stack)专注于管理函数调用的顺序和执行环境不同,堆内存则专注于动态地分配…...
C++学习笔记----2、使用C++进行优雅编程(五)----命名
C编译器对于命名有如下规则: 命名中可以有大小写字母、数字、下划线。字母不限于英文字符,可以是任意国家语言的字母,例如日文,阿拉伯文等。不能以数字开头,例如9to5。包含双下划线的被标准库保留不可使用,…...
Element UI顶部导航栏与左侧导航栏联动实现~
需求:点击顶部导航栏的不同栏位实现左侧导航栏菜单的不同展示实现联动效果。 点击顶部导航栏按钮将对应的左侧导航栏数据传递给vuex,并在左侧导航栏父组件中接收并传递给左侧导航栏子组件,使用递归组件实现渲染等,具体的优化可以看下面的注释…...
ECMAScript6模板字面量:反引号、${}占位符的使用
ECMAScript 6 中引入了模板字面量,主要通过多行字符串和字符串占位符对字符串进行增强操作。如下: //使用ECMAScript6模板字面量拼接字符串,例如: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 ,则会主动将连线中断。如果iot core检测到tcp 连接中断, 会立即中断;如果未检测到则会等到1.5倍 …...
【代码随想录训练营第42期 Day26打卡 贪心Part1 - LeetCode 455.分发饼干 376. 摆动序列 53. 最大子序和
目录 一、贪心 二、题目与题解 题目一:455.分发饼干 题目链接 题解:排序双指针贪心 题目二:376. 摆动序列 题目链接 题解:贪心 题目三:53. 最大子序和 题目链接 题解1:暴力(失败&…...
利用有限元法(FEM)模拟电磁场与样品的相互作用
一、引言 电磁场与物质的相互作用是理解光学现象的基础。在实际应用中,激光光束与样品的相互作用通常涉及复杂的电磁场分布,尤其在微纳尺度结构中。因此,使用数值模拟方法如有限元法(FEM)来模拟电磁场的分布和传播&…...
如何保持git主分支树的整洁
经典应用展示Git版本控制用法 本章将列举Git的一些闪亮特性,期待能够让您爱上Git 文章目录 经典应用展示Git版本控制用法前言一、分支是什么?二、主-分支合并merge三、cherry-pick(精挑细选)四、Rebase(变基)4.1 合并本地分支到主分支4.2 合并本地分支从指定commit开始的…...
Datawhale X 魔搭 AI夏令营 Task1 从零入门AI生图原理实践笔记
赛题内容 参赛者需在可图Kolors模型的基础上训练LoRA模型,生成无限风格,如水墨画风格、水彩风格、赛博朋克风格、日漫风格… 基于LoRA模型生成8张图片组成连贯故事,故事内容可自定义;基于8图故事,评估LoRA风格的美感度…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
