OpenCV直方图计算函数calcHist的使用

  • 操作系统:ubuntu22.04
  • OpenCV版本:OpenCV4.9
  • IDE:Visual Studio Code
  • 编程语言:C++11

功能描述

图像的直方图是一种统计表示方法,用于展示图像中不同像素强度(通常是灰度值或色彩强度)出现的频率分布。具体来说,它将图像的整个色调范围(如0到255对于8位灰度图像)划分为若干个离散的bins(或区间),然后统计每个bin内像素值出现的次数,并以柱状图的形式展示出来。

通过图像的直方图,我们可以直观地了解到图像的亮度分布、对比度信息以及色彩分布情况。例如,如果一个图像的直方图集中在较暗的色调上,说明图像整体偏暗;如果直方图覆盖了较广的范围且分布均匀,表明图像具有良好的动态范围和对比度。

直方图对于图像处理和分析非常重要,常用于自动曝光、图像均衡化(如直方图均衡化)、图像分割、颜色校正等多种图像处理任务中。

calcHist函数

cv::calcHist函数用于计算一个或多个数组的直方图

函数原型1

cpp 复制代码
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 
)		

参数1

  • 参数 images 源图像。它们都应该具有相同的深度,即CV_8U,CV_16U或CV_32F,并且具有相同的尺寸. 每个数组可以有任意数量的通道。

  • 参数 nimages 源图像个数.

  • 参数 channels 计算直方图的各个维度上所选择的通道列表.第一个数组的通道编号从0开始,一直到images[0].channels()-1;第二个数组的通道编号则从images[0].channels()开始,一直到images[0].channels() + images[1].channels()-1,以此类推.

    对于第一个图像数组images[0],其通道编号从0开始,一直到该数组通道数减一。

    第二个图像数组images[1]的通道编号紧接着第一个数组的通道编号之后,从images[0].channels()开始,直到images[0].channels() + images[1].channels()-1。

    同样的规则应用于后续的图像数组,每个数组的通道编号都是紧接在前一个数组的通道编号之后。

    这样的编号方式允许在计算直方图或其他涉及跨多个图像的通道操作时,可以统一地引用所有图像的通道,而不需要单独考虑每个图像的通道编号。这在处理复杂图像分析任务时特别有用,比如计算多图像的联合直方图或特征提取等。

  • 参数 mask 可选参数掩码。如果矩阵不为空,它必须是一个与images[i]相同尺寸的8位数组。非零的掩码元素标记出了将在直方图中计数的数组元素。.

  • 参数hist 输出的直方图,它是一个稠密或稀疏的dims维数组.

  • 参数dims 直方图的维度必须是正数,并且不能超过CV_MAX_DIMS(在当前OpenCV版本中等于32).

  • 参数histSize 在每一维度上的直方图大小的数组。

  • 参数ranges 数组ranges包含了dims个数组,每个数组表示直方图在对应维度上的bin边界。当直方图是均匀的(uniform=true),对于每个维度i,只需要指定第0个直方图bin的下界(包含) L 0 L_0 L0和最后一个binhistSize[i]-1的上界(不包含) U histSize [ i ] − 1 U_{\texttt{histSize}[i]-1} UhistSize[i]−1。也就是说,在均匀直方图的情况下,ranges[i]是一个包含2个元素的数组,分别表示该维度上bin的起始和结束边界。

    然而,当直方图是非均匀的(uniform=false),ranges[i]则包含histSize[i]+1个元素: L 0 , U 0 = L 1 , U 1 = L 2 , . . . , U histSize[i] − 2 = L histSize[i] − 1 , U histSize[i] − 1 L_0, U_0=L_1, U_1=L_2, ..., U_{\texttt{histSize[i]}-2}=L_{\texttt{histSize[i]}-1}, U_{\texttt{histSize[i]}-1} L0,U0=L1,U1=L2,...,UhistSize[i]−2=LhistSize[i]−1,UhistSize[i]−1。这里的Lj和Uj分别表示第j个bin的下界和上界。在非均匀直方图中,数组中的元素若不在 L 0 L_0 L0和 U histSize[i] − 1 U_{\texttt{histSize[i]}-1} UhistSize[i]−1之间,则不会被计入直方图.

  • 参数 uniform 这是一个标志,指示直方图是否为均匀直方图(参见上述说明)

  • 参数 accumulate 累积标志。如果设置了这个标志,那么在分配直方图时,直方图不会在开始时被清零。这个特性使你能够在不同时刻从多组数组中计算单一的直方图,或者随时间更新直方图。

函数原型2

这是一个重载成员函数,提供是为了方便使用。它与上述函数的不同之处仅在于它接受的参数类型。

此版本的函数使用SparseMat作为输出类型。

在OpenCV中,SparseMat是一种专门用于存储稀疏矩阵的容器。与常规的矩阵存储方式相比,SparseMat在存储和处理包含大量零值或无效值的大型矩阵时更加高效。它仅存储非零或有效元素及其位置,从而显著减少了内存占用和计算成本。

cpp 复制代码
void cv::calcHist
(
	const Mat * 	images,
	int 	nimages,
	const int * 	channels,
	InputArray 	mask,
	SparseMat & 	hist,
	int 	dims,
	const int * 	histSize,
	const float ** 	ranges,
	bool 	uniform = true,
	bool 	accumulate = false 
)		

参数2

  • 参数 images 源图像。它们都应该具有相同的深度,即CV_8U,CV_16U或CV_32F,并且具有相同的尺寸. 每个数组可以有任意数量的通道。

  • 参数 nimages 源图像个数.

  • 参数 channels 计算直方图的各个维度上所选择的通道列表.第一个数组的通道编号从0开始,一直到images[0].channels()-1;第二个数组的通道编号则从images[0].channels()开始,一直到images[0].channels() + images[1].channels()-1,以此类推.

    对于第一个图像数组images[0],其通道编号从0开始,一直到该数组通道数减一。

    第二个图像数组images[1]的通道编号紧接着第一个数组的通道编号之后,从images[0].channels()开始,直到images[0].channels() + images[1].channels()-1。

    同样的规则应用于后续的图像数组,每个数组的通道编号都是紧接在前一个数组的通道编号之后。

    这样的编号方式允许在计算直方图或其他涉及跨多个图像的通道操作时,可以统一地引用所有图像的通道,而不需要单独考虑每个图像的通道编号。这在处理复杂图像分析任务时特别有用,比如计算多图像的联合直方图或特征提取等。

  • 参数 mask 可选参数掩码。如果矩阵不为空,它必须是一个与images[i]相同尺寸的8位数组。非零的掩码元素标记出了将在直方图中计数的数组元素。.

  • 参数hist 输出的直方图,使用SparseMat作为输出类型.

  • 参数dims 直方图的维度必须是正数,并且不能超过CV_MAX_DIMS(在当前OpenCV版本中等于32).

  • 参数histSize 在每一维度上的直方图大小的数组。

  • 参数ranges 数组ranges包含了dims个数组,每个数组表示直方图在对应维度上的bin边界。当直方图是均匀的(uniform=true),对于每个维度i,只需要指定第0个直方图bin的下界(包含) L 0 L_0 L0和最后一个binhistSize[i]-1的上界(不包含) U histSize [ i ] − 1 U_{\texttt{histSize}[i]-1} UhistSize[i]−1。也就是说,在均匀直方图的情况下,ranges[i]是一个包含2个元素的数组,分别表示该维度上bin的起始和结束边界。

    然而,当直方图是非均匀的(uniform=false),ranges[i]则包含histSize[i]+1个元素: L 0 , U 0 = L 1 , U 1 = L 2 , . . . , U histSize[i] − 2 = L histSize[i] − 1 , U histSize[i] − 1 L_0, U_0=L_1, U_1=L_2, ..., U_{\texttt{histSize[i]}-2}=L_{\texttt{histSize[i]}-1}, U_{\texttt{histSize[i]}-1} L0,U0=L1,U1=L2,...,UhistSize[i]−2=LhistSize[i]−1,UhistSize[i]−1。这里的Lj和Uj分别表示第j个bin的下界和上界。在非均匀直方图中,数组中的元素若不在 L 0 L_0 L0和 U histSize[i] − 1 U_{\texttt{histSize[i]}-1} UhistSize[i]−1之间,则不会被计入直方图.

  • 参数 uniform 这是一个标志,指示直方图是否为均匀直方图(参见上述说明)

  • 参数 accumulate 累积标志。如果设置了这个标志,那么在分配直方图时,直方图不会在开始时被清零。这个特性使你能够在不同时刻从多组数组中计算单一的直方图,或者随时间更新直方图。

函数原型3

这是一个重载的成员函数,提供以方便使用。它与上述函数的不同之处仅在于它接受的参数类型。

此版本的函数仅支持均匀直方图。

ranges参数可以是一个空向量,或者是一个包含histSize.size()*2个元素的扁平化向量(即histSize.size()对元素)。每一对元素中的第一个和第二个元素分别指定了对应维度的下界和上界。

cpp 复制代码
void cv::calcHist	
(
	InputArrayOfArrays 	images,
	const std::vector< int > & 	channels,
	InputArray 	mask,
	OutputArray 	hist,
	const std::vector< int > & 	histSize,
	const std::vector< float > & 	ranges,
	bool 	accumulate = false 
)		

参数3

  • 参数 images 源图像。它们都应该具有相同的深度,即CV_8U,CV_16U或CV_32F,并且具有相同的尺寸. 每个数组可以有任意数量的通道。

  • 参数 channels 计算直方图的各个维度上所选择的通道列表.第一个数组的通道编号从0开始,一直到images[0].channels()-1;第二个数组的通道编号则从images[0].channels()开始,一直到images[0].channels() + images[1].channels()-1,以此类推.

    对于第一个图像数组images[0],其通道编号从0开始,一直到该数组通道数减一。

    第二个图像数组images[1]的通道编号紧接着第一个数组的通道编号之后,从images[0].channels()开始,直到images[0].channels() + images[1].channels()-1。

    同样的规则应用于后续的图像数组,每个数组的通道编号都是紧接在前一个数组的通道编号之后。

    这样的编号方式允许在计算直方图或其他涉及跨多个图像的通道操作时,可以统一地引用所有图像的通道,而不需要单独考虑每个图像的通道编号。这在处理复杂图像分析任务时特别有用,比如计算多图像的联合直方图或特征提取等。

  • 参数 mask 可选参数掩码。如果矩阵不为空,它必须是一个与images[i]相同尺寸的8位数组。非零的掩码元素标记出了将在直方图中计数的数组元素。.

  • 参数hist 输出的直方图,使用SparseMat作为输出类型.

  • 参数histSize 在每一维度上的直方图大小的数组。

  • 参数ranges 数组ranges包含了dims个数组,每个数组表示直方图在对应维度上的bin边界。当直方图是均匀的(uniform=true),对于每个维度i,只需要指定第0个直方图bin的下界(包含) L 0 L_0 L0和最后一个binhistSize[i]-1的上界(不包含) U histSize [ i ] − 1 U_{\texttt{histSize}[i]-1} UhistSize[i]−1。也就是说,在均匀直方图的情况下,ranges[i]是一个包含2个元素的数组,分别表示该维度上bin的起始和结束边界。

    然而,当直方图是非均匀的(uniform=false),ranges[i]则包含histSize[i]+1个元素: L 0 , U 0 = L 1 , U 1 = L 2 , . . . , U histSize[i] − 2 = L histSize[i] − 1 , U histSize[i] − 1 L_0, U_0=L_1, U_1=L_2, ..., U_{\texttt{histSize[i]}-2}=L_{\texttt{histSize[i]}-1}, U_{\texttt{histSize[i]}-1} L0,U0=L1,U1=L2,...,UhistSize[i]−2=LhistSize[i]−1,UhistSize[i]−1。这里的Lj和Uj分别表示第j个bin的下界和上界。在非均匀直方图中,数组中的元素若不在 L 0 L_0 L0和 U histSize[i] − 1 U_{\texttt{histSize[i]}-1} UhistSize[i]−1之间,则不会被计入直方图.

  • 参数 accumulate 累积标志。如果设置了这个标志,那么在分配直方图时,直方图不会在开始时被清零。这个特性使你能够在不同时刻从多组数组中计算单一的直方图,或者随时间更新直方图。

代码示例

cpp 复制代码
#include "opencv2/core/utility.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int _brightness = 100;
int _contrast   = 100;
Mat image;
/* brightness/contrast callback function */
static void updateBrightnessContrast( int /*arg*/, void* )
{
    int histSize   = 64;
    int brightness = _brightness - 100;
    int contrast   = _contrast - 100;
    /*
     * The algorithm is by Werner D. Streidt
     * (http://visca.com/ffactory/archives/5-99/msg00021.html)
     */
    double a, b;
    if ( contrast > 0 )
    {
        double delta = 127. * contrast / 100;
        a            = 255. / ( 255. - delta * 2 );
        b            = a * ( brightness - delta );
    }
    else
    {
        double delta = -128. * contrast / 100;
        a            = ( 256. - delta * 2 ) / 255.;
        b            = a * brightness + delta;
    }
    Mat dst, hist;
    image.convertTo( dst, CV_8U, a, b );
    imshow( "image", dst );
    calcHist( &dst, 1, 0, Mat(), hist, 1, &histSize, 0 );
    Mat histImage = Mat::ones( 200, 320, CV_8U ) * 255;
    normalize( hist, hist, 0, histImage.rows, NORM_MINMAX, CV_32F );
    histImage = Scalar::all( 255 );
    int binW  = cvRound( ( double )histImage.cols / histSize );
    for ( int i = 0; i < histSize; i++ )
        rectangle( histImage, Point( i * binW, histImage.rows ), Point( ( i + 1 ) * binW, histImage.rows - cvRound( hist.at< float >( i ) ) ), Scalar::all( 0 ), -1, 8, 0 );
    imshow( "histogram", histImage );
}
const char* keys = { "{help h||}{@image|baboon.jpg|input image file}" };
int main( int argc, const char** argv )
{
    // Load the source image. HighGUI use.
    image = imread( "/media/dingxin/data/study/OpenCV/sources/images/white.jpg", cv::IMREAD_GRAYSCALE );
    if ( image.empty() )
    {
        std::cerr << "Cannot read image file" << std::endl;
        return -1;
    }
    Size sz2Sh( 300, 400 );

    resize( image, image, sz2Sh, 0, 0, INTER_LINEAR_EXACT );

    namedWindow( "image", 0 );
    namedWindow( "histogram", 0 );
    createTrackbar( "brightness", "image", &_brightness, 200, updateBrightnessContrast );
    createTrackbar( "contrast", "image", &_contrast, 200, updateBrightnessContrast );
    updateBrightnessContrast( 0, 0 );
    waitKey();
    return 0;
}

运行结果

原图:

直方图:

你可以调整亮度的滑动块,或者对比度的滑动块,来观察直方图的变化,从而更好地去理解直方图的概念。

相关推荐
视***间10 小时前
视程空间全景红外AI智能相机VPP SC6N0-IR:工业安全的全天候智能守护卫士
人工智能·机器人·边缘计算·ai算力开发板·全景红外·vr红外
爱写代码的汤二狗10 小时前
第3章 应用解构:一眼看穿应用的本质
人工智能·经验分享·创业创新
吴佳浩 Alben10 小时前
Vibe Coding 时代:Vue 消失了还是 React 太强?
前端·vue.js·人工智能·react.js·语言模型·自然语言处理
llm大模型算法工程师weng10 小时前
Palantir 商业化关键时间点深度解析:从政府基本盘到 AI 爆发的战略跃迁
人工智能
飞哥数智坊10 小时前
OpenClaw 中国行济南站圆满结束
人工智能
飞哥数智坊10 小时前
openclaw 最近版本的崩溃与抢救
人工智能
起个名字总是说已存在10 小时前
github开源AI Vibe Coding训练你的AI编程工具
人工智能·开源·github
饼干哥哥10 小时前
OpenClaw真变态!我跑通了跨境电商的10个落地场景
人工智能
Mintopia10 小时前
为什么同样写代码,有的人越写越轻松,有的人越写越乱
人工智能
hhzz10 小时前
Openclaw案例之构建《全自动化、高适配、可定制”的AI绘画生产体系》
人工智能·ai作画·自动化·openclaw