OpenCV4(C++)—— 直方图

news2024/12/30 3:48:17

文章目录

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


前言

直方图(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_8U
				OutputArray 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.coloradmin.cn/o/1083000.html

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

相关文章

vue3+ts项目03 element-plus、vue-router、pinia

yarn add element-plus yarn add element-plus/icons-vue修改main.ts import { createApp } from vue import App from ./App.vueimport ElementPlus from element-plus import element-plus/dist/index.css import zhCn from element-plus/dist/locale/zh-cn.mjsconst app c…

Arduino程序设计(十四)舵机控制实验(SG90)

舵机控制实验 前言一、SG90舵机1、SG90舵机简介2、硬件电路连线3、Servo库常用函数 二、舵机实验1、舵机0~180来回转动2、串口控制舵机转动固定角度 总结 前言 本文介绍SG90舵机控制原理及实验&#xff0c;主要内容有&#xff1a;1、介绍SG90舵机&#xff1b;2、舵机0~180来回…

Android---java内存模型与线程

Java 内存模型翻译自 Java Memory Model&#xff0c;简称 JMM。它所描述的是多线程并发、CPU 缓存等方面的内容。 在每一个线程中&#xff0c;都会有一块内部的工作内存&#xff0c;这块内存保存了主内存共享数据的拷贝副本。但在 Java 线程中并不存在所谓的工作内存&#xff0…

发行版兴趣小组季度动态:Anolis OS 支持大热 AI 软件栈,引入社区合作安全修复流程

发行版兴趣小组&#xff08;Special Interest Group&#xff09; &#xff1a;旨在为龙蜥社区构建、发布和维护一个稳定的操作系统发行版。 秋天的季节&#xff0c;发行版兴趣小组在 AI、安全、国产 OS 领域同样也是硕果累累。一起来看一下第三季度发行版兴趣小组的成果总结有…

IPv4报文头部

1、version&#xff08;版本&#xff09;:用于标识封装是IPv4还是IPv6 2、IHL&#xff08;头部长度&#xff09;&#xff1a;描述了数据包头的内容长度 3、Type of service&#xff08;服务类型&#xff09;&#xff1a;用于标识DSCP或IP优先级&#xff0c;用于Qos识别 4、T…

野火开发板使用FlyMcu一键ISP下载时

1.ISP 一键下载 野火开发板使用FlyMcu一键ISP下载时&#xff0c;记得拔掉JTAG那个20针的东西&#xff0c;要不然一直芯片超时不连接。 bsp:9600&#xff0c;使用共写入2KB,进度100%,耗时16641毫秒。 bsp:115200&#xff0c;共写入2KB,进度100%,耗时2188毫秒。 bsp:115200&#…

[modern c++] 函数式编程与 std::ref

参考&#xff1a; std::ref, std::cref - cppreference.comhttps://en.cppreference.com/w/cpp/utility/functional/ref 正文&#xff1a; 如果不涉及函数式编程&#xff0c;那么基本上不需要使用到 std::ref &#xff0c; 这个功能式是用来解决函数式编程时入参只能进行值传…

ai语音机器人OKCC的空号检测

一、空号检测模块介绍 空号检测的原理&#xff1a;空号检测是利用现代通信技术和互联网技术结合而成&#xff0c;采用批量拨电话号码的方式&#xff0c;过滤空号、停机、无效号码。业内又称空号筛选、空号过滤。空号检测技术的成果是去除号码中的无效号码&#xff0c;包括…

代理IP端口是什么意思呢?

今天&#xff0c;咱们来聊聊一个小众但很有料的话题——代理IP端口&#xff0c;它可是你纵横互联网世界的好搭子哦&#xff01; 首先&#xff0c;我们得先弄明白&#xff0c;代理IP端口是个啥? 代理IP端口就像是通往网络世界的门票&#xff0c;是你和代理服务器之间的桥梁。…

“解锁1688商品详情接口:提升电商转化率的秘密武器!“

1688商品详情接口&#xff08;又称详情页API&#xff09;是一种供卖家使用的接口&#xff0c;可以让卖家通过该接口维护商品的详细信息&#xff0c;包括商品价格、图片、属性等等。 通过使用该接口&#xff0c;卖家可以在商品详情页中提供更加详尽、直观的商品描述&#xff0c…

总线带宽计算公式

原文连接&#xff1a;总线带宽计算公式&#xff08;解析&#xff09;-

Linux Shell 实现一键部署hfish

hfish前言 HFish是一款社区型免费蜜罐&#xff0c;侧重企业安全场景&#xff0c;从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发&#xff0c;为用户提供可独立操作且实用的功能&#xff0c;通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知和威胁情报领域的能…

PromptScript:轻量级 DSL 脚本,加速多样化的 LLM 测试与验证

TL&#xff1b;DR 版本 PromptScript 是一个轻量级的 Prompt 调试用的 DSL &#xff08;Yaml&#xff09;脚本&#xff0c;以用于快速使用、构建 Prompt。 PromptScript 文档&#xff1a;https://framework.unitmesh.cc/prompt-script Why PromptScript &#xff1f; 几个月前&…

JAVA设计模式-装饰者模式

一.概念 装饰器模式(Decorator Pattern)&#xff0c;动态地给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰器模式比生成子类更灵活。 —-《大话设计模式》 允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。这种类型的设计模式属…

多列等高实现

预期效果 多列等高,左右两列高度自适应且一样,分别设置不同背景色效果预览: 分别由6种方法实现 1、使用padding + margin + overflow 实现多列等高效果,具有良好的兼容性; 2、border实现多列等高,左边框宽度为200px,左列浮动,伪元素清除浮动; 3、父元素线性渐变背景色…

Mac热门软件推荐Paste mac 中文激活版 剪切板工具

Paste for Mac是一款运行在Mac OS平台上的剪切板小工具。它拥有华丽的界面效果&#xff0c;并且每一条记录可显示&#xff08;预览&#xff09;文本、图片等记录的完整内容。此外&#xff0c;Paste for Mac可以记录最近指定条数的剪切板信息&#xff0c;方便用户随时调用&#…

L15D1 设备分类、设备号申请和注销

一、Linux设备分类 &#xff08;一&#xff09;linux的文件种类&#xff1a; -&#xff1a;普通文件&#xff1a;文件IOd&#xff1a;目录文件p&#xff1a;管道文件s&#xff1a;本地socket文件&#xff1a;网络编程l&#xff1a;链接文件c&#xff1a;字符设备b&#xff1a…

18__call__函数的调用

目录 把对象当成函数一样调用 吼吼~补充一个小的知识点哦~就是括号里的参数&#xff01; 具体的应用场景 原先一个实例化对象&#xff0c;并不会被调用&#xff0c;但是可以引入call class Person:pass pPerson() p() 这个时候引入__call__ 把对象当成函数一样调用 class…

PLC电梯控制系统

目录 PLC电梯控制系统 1电梯简介 1.1电梯的基本分类 1.1.1按用途分类 1.1.2 按驱动系统分类 1.2 电梯的型号 1.3电梯的主要参数及规格尺寸 1.4电梯控制技术 1.5常用交流调速电梯的特点 1.6电梯的工作原理 2 PLC可编程序控制器 2.1 PLC的起源与发展 2.2 PLC控制系统…

视频监控系统/安防视频平台EasyCVR广场视频细节优化

安防视频监控系统/视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频汇聚平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;可实现视频监控直播、视频轮播、…