OpenCV4(C++)—— 直方图

文章目录

  • 前言
  • 一、计算直方图
  • 二、归一化
  • 三、直方图均衡化
  • 四、直方图匹配


前言

直方图(Histogram)最开始在统计学中被提出,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。在图像领域,直方图用来更直观地展现各像素值出现的频率,例如常用的灰度直方图反映的是一幅图像中各灰度像素值(0-255)出现的频率之间的关系

在这里插入图片描述
图像的直方图表示图像像素灰度值的统计特性,因此可以通过比较两张图像的直方图特性比较两张图像的相似程度。从一定程度上来讲,虽然两张图像的直方图分布相似不代表两张图像相似,但是两张图像相似则两张图像的直方图分布一定相似。例如通过插值对图像进行放缩后图像的直方图虽然不会与之前完全一致,但是两者一定具有很高的相似性,因而可以通过比较两张图像的直方图分布相似性对图像进行初步的筛选与识别。

一、计算直方图

目前OpenCV中只提供了图像直方图的统计函数calcHist(),该函数能够统计出图像中每个灰度值的个数,但是对于直方图的绘制需要我们自行解决。

void cv::calcHist(const Mat* images,  // 图像或图像集合,集合内所有的图像应具有相同的尺寸和数据类型,并且数据类型只能是CV_8U、CV_16U和CV_32F三种中的一种,但是不同图像的通道数可以不同。int nimages,   		  // 输入图像的数量(当处理多幅图像时使用)const int* channels,  // 需要统计的通道索引数组,第一个图像的通道索引从0到images[0].channels()-1(灰度图设置为[0]),第二个图像通道索引从images[0].channels()到images[0].channels()+ images[1].channels()-1,以此类推InputArray mask,   // 可选的操作掩码,如果是空矩阵则表示图像中所有位置的像素都计入直方图中,如果矩阵不为空,则必须与输入图像尺寸相同且数据类型为CV_8UOutputArray hist,  // 输出的统计直方图结果,是一个dims维度的数组,cv::Mat形式int dims,  // 直方图的维数。对于灰度图像,默认为1;对于彩色图像,默认为3(每个颜色通道一个维度)const int* histSize,  // 存放每个维度直方图的数组的尺寸,即像素最小值和最小值的差距const float** ranges,  // 每个图像通道中灰度值的取值范围bool uniform = true,   // 直方图是否均匀的标志符,默认状态下为均匀bool accumulate = false  // 是否累积统计直方图的标志,如果累积(true),则统计新图像的直方图时之前图像的统计结果不会被清除,该功能主要用于统计多个图像整体的直方图);

上面参数中需要注意有些参数是指针,下面是用数组来定义的方式:

    cv::Mat image = cv::imread("C:/Users/Opencv/temp/lena.png");if (image.empty()) {cout << "打开图片失败" <<endl;return -1;}cv::Mat gray;cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);//定义直方图参数来统计cv::Mat hist;  // 存放直方图结果const int channels[] = { 0 };  // 通道索引const int histsize[] = { 256 }; // 直方图的维度,即像素最小值和最大值的差距float inrange[] = { 0,255 };const float* ranges[] = {inrange};  // 像素灰度值范围cv::calcHist(&gray, 1, channels, cv::Mat(), hist, 1, histsize, ranges);

二、归一化

  一张很大的图像,某些像素点的值可能有成百上千个,某些像素点可能为0,彼此差距较大。所以需要进行归一化处理,在特定范围内对像素数据进行缩放。OpenCV4提供了normalize()函数实现多种形式的归一化功能。

void normalize(InputArray src, OutputArray dst, double alpha = 1.0, double beta = 0.0, 
int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());norm_type(归一化类型):NORM_INF, NORM_L1, NORM_L2
dtype:输出数据类型选择标志,如果为负数,则输出数据与src拥有相同的类型

在一个全黑的图片上绘制直方图

    // 绘制直方图int histH = 500;int histW = 600;int width = cvRound(histW / histsize[0]);cv::Mat histImg(histH, histW, CV_8UC1, cv::Scalar(0, 0, 0));//归一化cv::normalize(hist, hist, 1, 0, cv::NORM_INF, -1, cv::Mat());//cv::normalize(hist, hist, 1, 0, cv::NORM_L1, -1, cv::Mat());//cv::normalize(hist, hist, 1, 0, cv::NORM_L2, -1, cv::Mat());for (int i = 1; i < hist.rows; i++){cv::rectangle(histImg, cv::Point(width * (i - 1), histH - 1),cv::Point(width * i - 1, histH - cvRound(histH * hist.at<float>(i - 1)) - 1),cv::Scalar(255, 255, 255), -1);}

在这里插入图片描述

三、直方图均衡化

  从上面的结果可以看出,当图像亮度比较暗的时候,其直方图就会集中在低像素区域,同理,原图较亮就集中在高像素区域。当都集中在中间值100到150之间,则整个图像想会给人一种模糊的感觉,看不清图中的内容
  这种像素集中在某个区域的图像,其整体对比度较小,不利于纹理的识别(如像素灰度值集中在10,11,12这些相邻区域,肉眼都很难区分这几种像素值的差别)。为此,需要增大对比度,可通过映射关系,将图像中灰度值的范围扩大,增加原来两个灰度值之间的差值,这个过程称为图像直方图均衡化,也就是图像增强中的对比度增强。
  Opencv中使用equalizeHist()函数对单通道图像进行直方图均衡化操作,参数只有两个:输入图和输出图

void drawHist(cv::Mat &hist, const int* histsize, string name)
{// 绘制直方图int histH = 500;int histW = 600;int width = cvRound(histW / histsize[0]);cv::Mat histImg(histH, histW, CV_8UC1, cv::Scalar(0, 0, 0));//归一化cv::normalize(hist, hist, 1, 0, cv::NORM_INF, -1, cv::Mat());//cv::normalize(hist, hist, 1, 0, cv::NORM_L1, -1, cv::Mat());//cv::normalize(hist, hist, 1, 0, cv::NORM_L2, -1, cv::Mat());for (int i = 1; i < hist.rows; i++){cv::rectangle(histImg, cv::Point(width * (i - 1), histH - 1),cv::Point(width * i - 1, histH - cvRound(histH * hist.at<float>(i - 1)) - 1),cv::Scalar(255, 255, 255), -1);}cv::imshow(name, histImg);
}int main()
{cv::Mat image = cv::imread("C:/Users/Opencv/temp/hist.png");if (image.empty()) {cout << "打开图片失败" <<endl;return -1;}cv::Mat gray, equImg;cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);cv::equalizeHist(gray, equImg);//定义直方图参数cv::Mat hist1, hist2;  // 存放直方图结果const int channels[] = { 0 };  // 通道索引const int histsize[] = { 256 }; // 直方图的维度,即像素最小值和最大值的差距float inrange[] = { 0,255 };const float* ranges[] = {inrange};  // 像素灰度值范围cv::calcHist(&gray, 1, channels, cv::Mat(), hist1, 1, histsize, ranges);cv::calcHist(&equImg, 1, channels, cv::Mat(), hist2, 1, histsize, ranges);drawHist(hist1, histsize, "原图直方图");drawHist(hist2, histsize, "均值化后直方图");cv::imshow("gray", gray);cv::imshow("equ", equImg);cv::waitKey(0);cv::destroyAllWindows();return 0;
}

在这里插入图片描述
注:直方图均衡化只能在单通道图片进行

四、直方图匹配

  直方图匹配(Histogram Matching),也称为直方图规定化或直方图规范化。给定一个参考图像,在某些特定的条件下需要将原图直方图映射成指定的分布形式。它通过调整原始图像的像素值,使其直方图与参考图像的直方图相似,从而达到两幅图像之间颜色和对比度的匹配。
  匹配的原理不解释了,OpenCV中没有直接进行匹配的函数,可自行使用LUT来进行相关的映射来改变像素值。大致步骤:1)计算两张图像直方图的累积概率。2)构建累积概率误差矩阵。3)生成LUT映射表

代码如下(示例):

#include <opencv2/opencv.hpp>
#include <vector>
#include<iostream>  using namespace std;void drawHist(cv::Mat &hist, const int* histsize, string name)
{// 绘制直方图int histH = 500;int histW = 600;int width = cvRound(histW / histsize[0]);cv::Mat histImg(histH, histW, CV_8UC1, cv::Scalar(0, 0, 0));//归一化cv::normalize(hist, hist, 1, 0, cv::NORM_INF, -1, cv::Mat());//cv::normalize(hist, hist, 1, 0, cv::NORM_L1, -1, cv::Mat());//cv::normalize(hist, hist, 1, 0, cv::NORM_L2, -1, cv::Mat());for (int i = 1; i < hist.rows; i++){cv::rectangle(histImg, cv::Point(width * (i - 1), histH - 1),cv::Point(width * i - 1, histH - cvRound(histH * hist.at<float>(i - 1)) - 1),cv::Scalar(255, 255, 255), -1);}cv::imshow(name, histImg);
}int main()
{cv::Mat image1 = cv::imread("C:/Users/Opencv/temp/hist.png");cv::Mat image2 = cv::imread("C:/Users/Opencv/temp/yuan.png");if (image1.empty() || image2.empty()) {cout << "打开图片失败" <<endl;return -1;}cv::Mat gray1, gray2, equImg;cv::cvtColor(image1, gray1, cv::COLOR_BGR2GRAY);cv::cvtColor(image2, gray2, cv::COLOR_BGR2GRAY);cv::equalizeHist(gray1, equImg);//定义直方图参数cv::Mat hist1, hist2, hist3;  // 存放直方图结果const int channels[] = { 0 };  // 通道索引const int histsize[] = { 256 }; // 直方图的维度,即像素最小值和最大值的差距float inrange[] = { 0,255 };const float* ranges[] = {inrange};  // 像素灰度值范围// 计算两张图像直方图//cv::calcHist(&image1, 1, channels, cv::Mat(), hist1, 1, histsize, ranges);//cv::calcHist(&image2, 1, channels, cv::Mat(), hist2, 1, histsize, ranges);cv::calcHist(&gray1, 1, channels, cv::Mat(), hist1, 1, histsize, ranges);cv::calcHist(&gray2, 1, channels, cv::Mat(), hist2, 1, histsize, ranges);cv::calcHist(&equImg, 1, channels, cv::Mat(), hist3, 1, histsize, ranges);// 归一化drawHist(hist1, histsize, "原图直方图");drawHist(hist2, histsize, "模板直方图");drawHist(hist3, histsize, "均值化后直方图");//1.计算两张图像直方图的累积概率float hist1_cdf[256] = { hist1.at<float>(0) };float hist2_cdf[256] = { hist2.at<float>(0) };for (int i = 1; i < 256; i++){hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);}//2.构建累积概率误差矩阵float diff_cdf[256][256];for (int i = 0; i < 256; i++){for (int j = 0; j < 256; j++){diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);}}//3.生成LUT映射表cv::Mat lut(1, 256, CV_8U);for (int i = 0; i < 256; i++){// 查找源灰度级为i的映射灰度// 和i的累积概率差值最小的规定化灰度float min = diff_cdf[i][0];int index = 0;//寻找累积概率误差矩阵中每一行中的最小值for (int j = 1; j < 256; j++){if (min > diff_cdf[i][j]){min = diff_cdf[i][j];index = j;}}lut.at<uchar>(i) = (uchar)index;}cv::Mat matchImg;//cv::LUT(image1, lut, matchImg);cv::LUT(gray1, lut, matchImg);//cv::imshow("原图", image1);//cv::imshow("模板图", image2);cv::imshow("原图", gray1);cv::imshow("模板图", gray2);cv::imshow("匹配图", matchImg);cv::imshow("equ", equImg);cv::Mat hist4;cv::calcHist(&matchImg, 1, channels, cv::Mat(), hist4, 1, histsize, ranges);drawHist(hist4, histsize, "匹配直方图");cv::waitKey(0);cv::destroyAllWindows();return 0;
}

直方图均值化的equalizeHist()函数没有任何可调参数,故只有一种结果——图像直方图必然是均匀分布的。直方图匹配更加灵活,根据模板图片的不同,能实现不同区域分布的直方图,能够实现增强某个灰度区间。

个人理解:像素之间的个数差距是不变的,即直方图中的高点和低点的趋势不变。直方图均值化是让其均匀分布,直方图匹配是根据一定的映射关系来改变分布区域(如像素值18的个数最多,为最高点,即使映射成其它数值,它还是最高点)。

在这里插入图片描述

在这里插入图片描述
此外,直方图均衡化的目标是增强图像的全局对比度,使得图像更加鲜明、清晰。通过重新分布图像的像素值,使得直方图在整个灰度范围内尽可能平均分布,其equalizeHist函数只能对灰度图进行操作。而直方图匹配没有这个限制,在对彩色图像进行操作时,通过匹配图像的颜色特性可以实现视觉一致性,可用于颜色转换、风格迁移等应用中。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

旅游网站HTML

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>旅游网</title> </head> <body><!--采用table编辑--> <!--最晚曾table,用于整个页面那布局--><table width&q…

Java I/O 的 OutputStream 输出流相关知识点详解

Java 17 的 I/O 基础 OutputStream 篇 对于 OutputStream 主要是字节流类型的输出流。 OutputStream OutputStream 抽象类是所有字节输出流类的超类。输出流接受输出字节并将它们发送到某个接收器中。 同样该抽象类需要一个子类来继承实现始终提供至少一种写入一个字节输出的…

es6(二)——常用es6说明

ES6的系列文章目录 es6&#xff08;一&#xff09;——var和let和const的区别 文章目录 ES6的系列文章目录一、变量的结构赋值1.数组的结构赋值2.对象的结构赋值 二、模板字符串三、扩展运算符1.字符串的使用2.数组的使用 四、箭头函数1.普通函数的定义2.箭头函数的定义3.箭头…

简单大方的自我介绍 PPT 格式

自我介绍是展示自己的机会&#xff0c;同时也是展现自信和魅力的重要时刻。通过简单大方的PPT格式&#xff0c;可以更好地展示自己的个性和才华。下面是一些建议&#xff0c;帮助你在自我介绍中展现自信和魅力。 1. 打造简洁而有吸引力的PPT布局&#xff1a; - 选择简洁大方的背…

数据库常见面试题--MySQL

梳理面试过程中数据库相关的常见问题&#xff0c;需要说明的是&#xff0c;这篇文章主要是基于MySQL数据库&#xff0c;其他类型的数据库还请自行参考使用。 数据库概述 为什么使用数据库 1、数据库增删改查更方便 2、提供了事务的能力 本质是更好的管理数据。 数据库体系结…

(部署服务器系列一)虚拟机模拟部署服务器

1、下载安装vmware 15 &#xff08;win7最高支持版&#xff09; 2、下载安装CentOS 配置2核2g&#xff08;最少&#xff09;磁盘100g&#xff08;不会实际占有&#xff09;选择时区-上海配置分区&#xff1a;https://blog.csdn.net/qq_35363507/article/details/127390889 &a…

借助PLC-Recorder,汇川中型PLC(AM、AC系列,CODESYS平台)2ms高速采集的方法

高速数据采集要保证速度&#xff0c;也要保证时刻的准确性。在windows系统里&#xff0c;时间稳定性是个很难的问题。如果PLC发送的数据里带有时间信息&#xff0c;则可以由PLC来保证采样周期的稳定性。 从V2.12版本开始&#xff0c;PLC-Recorder软件可以处理发送电文里的时间…

Ubuntu输入正确密码重新跳到登录界面

Ubuntu输入正确密码重新跳到登录界面 问题描述 输入正确的密码登录后闪一下又回到锁屏界面 输入正确的密码后还是回到这个界面 产生的原因 /etc/profile或者/etc/enviroment出现了问题,导致无法正常登录 该错误产生的原因不止一个 这里是因为/etc/profile或者/etc/enviromen出…

收银系统商品定价设计思考

一、背景 因为门店系统里商品总共也就几万款&#xff0c;一直以来都是根据条码由总部统一定价销售&#xff0c;现在有加盟店&#xff0c;各门店也有进行各自促销活动的需求&#xff0c;这就需要放开门店自主定价权&#xff0c;所以近段时间系统在商品定价上做了扩展。 二、商…

如何在雷电模拟器上安装Magisk并加载movecert模块抓https包(一)

环境&#xff1a;win10 64位&#xff0c;雷电模拟器版本4.0.78&#xff0c;Android版本7.1.2。 前几天写了一篇文章如何在逍遥模拟器上加载Magisk模块-CSDN博客&#xff0c;因为最近很忙&#xff0c;所以直到今天才有空写这一篇&#xff0c;记录如何在雷电模拟器上安装Magisk并…

基于uniapp的商城外卖小程序

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

LCD12864驱动开发

目录 一、概述 二、方框图 三、模块接口说明 1、串口接口管脚信号 2、并行接口 四、模块主要硬件构成说明 1、RS&#xff0c;R/W配4种模式&#xff1a; 2、E信号 五、指令说明 六、读写时序图 6.1 数据传输过程 6.2、时序图 6.3、串口读写时序 七、交流参数 八、软件…

TCP/IP(十五)拥塞控制

一 拥塞控制 ① 拥塞控制必要性 思考&#xff1a; 为什么要有拥塞控制呀,不是有流量控制了吗&#xff1f; ② 拥赛窗口 cwnd 什么是拥塞窗口? 和发送窗口有什么关系呢?明白&#xff1a; cwnd、swnd、rwnd 缩写 含义 ③ 如何知道当前网络是否出现了拥塞呢&#xff1f;…

2023年【危险化学品生产单位安全生产管理人员】及危险化学品生产单位安全生产管理人员模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 危险化学品生产单位安全生产管理人员考前必练&#xff01;安全生产模拟考试一点通每个月更新危险化学品生产单位安全生产管理人员模拟考试题题目及答案&#xff01;多做几遍&#xff0c;其实通过危险化学品生产单位安…

微信开发工具构建npm and git切换分支

目录 git切换分支NPM构建 git切换分支 案例&#xff1a; 再次查看分支就会发现自己的分支已切换&#xff0c;然后需要重新构建NPM一次 NPM构建 记得安装一下这个&#xff0c;然后在构建 如果未安装NPM&#xff0c;这时候需要打开命令端&#xff0c;安装操作&#xff0c;操作…

Configuration Change派发到App进程

整体时序 // DisplayContent.java boolean updateDisplayOverrideConfigurationLocked(Configuration values,ActivityRecord starting, boolean deferResume,ActivityTaskManagerService.UpdateConfigurationResult result) {int changes 0;boolean kept true;mAtmService.d…

Andriod学习笔记(一)

写在前面的话 App开发的编程语言Java和KotlinXML App连接的数据库App工程目录结构模块级别的编译配置文件清单文件 界面显示与逻辑处理 安卓是一种基于Linux内核的自由及开放源代码的操作系统&#xff0c;主要使用于移动设备。 Mininum SDK表示安卓该版本以上的设备都可以运行该…

Restclient-cpp库介绍和实际应用:爬取www.sohu.com

概述 Restclient-cpp是一个用C编写的简单而优雅的RESTful客户端库&#xff0c;它可以方便地发送HTTP请求和处理响应。它基于libcurl和jsoncpp&#xff0c;支持GET, POST, PUT, PATCH, DELETE, HEAD等方法&#xff0c;以及自定义HTTP头部&#xff0c;超时设置&#xff0c;代理服…

节日灯饰灯串灯出口欧洲CE认证检测

灯串&#xff08;灯带&#xff09;&#xff0c;这个产品的形状就象一根带子一样&#xff0c;再加上产品的主要原件就是LED&#xff0c;因此叫做灯串或者灯带。2022年&#xff0c;我国灯具及相关配件产品出口总额超过460亿美元。其中北美是最大的出口市场。其次是欧洲市场&#…

智能工厂MES系统,终端设备支持手机、PDA、工业平板、PC

一、开源项目简介 源计划智能工厂MES系统(开源版) 功能包括销售管理&#xff0c;仓库管理&#xff0c;生产管理&#xff0c;质量管理&#xff0c;设备管理&#xff0c;条码追溯&#xff0c;财务管理&#xff0c;系统集成&#xff0c;移动端APP。 二、开源协议 使用GPL-3.0开…