OpenCV 直方图概念,直方图均衡化原理详解

直方图相关概念

直方图(Histogram)是一种图表,用于显示数据集的分布情况。它通过将数据范围划分为若干个连续的区间(称为“桶”或“箱”—bins),然后统计每个区间内的数据点数量,以条形图的形式展示这些数量。横轴表示数据的取值范围,纵轴表示每个区间内数据点的数量或频率。例如下图(转载自025 - 二维直方图_哔哩哔哩_bilibili

在这里插入图片描述

颜色灰度级

  • 灰度级:在灰度图像中,每个像素的亮度值,可以是0到255之间的整数,其中0表示黑色,255表示白色,中间的值表示不同程度的灰色。
  • 直方图:一种柱状图,用于表示每个灰度级在图像中出现的频率。

作用

  1. 图像分析:直方图可以帮助分析图像的整体亮度分布,了解图像是过暗、过亮还是对比度适中。
  2. 图像增强:通过直方图均衡化等技术,可以增强图像的对比度,使图像细节更加清晰。
  3. 图像分割:在图像处理和计算机视觉中,可以利用直方图来确定图像的阈值,进行目标和背景的分割。
  4. 图像对比度调整:根据直方图调整图像对比度,使图像更符合视觉需求。

应用场景

  1. 医学影像处理:在医学图像(如X射线、CT、MRI)中,通过直方图分析可以增强图像,帮助医生更好地诊断。
  2. 摄影和图像编辑:摄影师和图像编辑软件利用直方图调整图像的曝光、对比度和颜色分布,以达到理想的视觉效果。
  3. 监控系统:在监控图像中,直方图可以帮助调整图像质量,使得图像在不同光照条件下保持较好的可见性。
  4. 计算机视觉:在目标检测、识别和跟踪中,直方图作为特征描述子之一,可以用于比较和匹配图像。

C++ 使用OpenCV绘制直方图

单通道直方图

顾名思义,就是分析单通道图像的亮度分布情况,一般用于灰度图像,下方代码,则是展示了分析三通道图像的其中一个通道的直方图

void show_hist_demo(Mat &image){
    Mat hist;
    vector<Mat> bgr_channels;
    split(image,bgr_channels);
    int histSize = 256; // 从 0 到 255
    float range[] = { 0, 256 }; // 强度范围 灰度等级
    const float* histRange = { range };  // calcHist支持分析多张图片,这里只提供一张图片,因此,只传入一个范围数组。分析多张图片,往后添加范围数组即可
    calcHist(&bgr_channels[0], 1, 0, Mat(), hist, 1, &histSize, &histRange, true, false);
    // 绘制直方图
    int hist_w = 512, hist_h = 400;
    int bin_w = cvRound((double) hist_w / histSize);
    Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));  // 空白图像 用来显示结果
    normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    for (int i = 1; i < histSize; i++) {
        line(histImage,
             Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
             Point(bin_w * i, hist_h - cvRound(hist.at<float>(i))),
             Scalar(255), 2, 8, 0);
    }

    imshow("Source image", image);
    imshow("Histogram", histImage);
    waitKey(0);
}

关键代码分析:

vector<Mat> bgr_channels;
split(image,bgr_channels);

将一张彩色三通道图像分离成三个通道 B G R Mat对象。便于下文分析单个通道的分析。

calcHist函数分析

函数原型

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);

参数介绍

const Mat* images:

  • 这是指向图像数组的指针,可以处理单张或多张图像。在单通道或者多通道图像的处理中,这个数组将包含所有需要计算直方图的图像。

int nimages:

  • 这个参数指定了 images 数组中图像的数量。例如,如果你只处理一张图像,这个值就应该是 1。

const int* channels:

  • 这是一个包含了需要计算直方图的通道索引的数组。例如,对于 BGR 图像,如果你只想计算蓝色通道的直方图,你可以传递 0。

InputArray mask:

  • 这是一个可选参数,用于定义一个掩码。如果掩码非空,函数将只计算掩码内像素的直方图。这可以用于计算图像特定区域的直方图。

OutputArray hist:

  • 这是一个输出参数,用来存储计算得到的直方图。通常,这是一个多维数组,其中每个维度对应于一个通道或一个特定的直方图轴。

int dims:

  • 这指定了直方图的维度数量。对于简单的灰度图像直方图,这个值是 1。对于包含多个通道的图像,这个值可以更高。

const int* histSize:

  • 这是一个数组,指定了每个维度的 bin 数量。例如,如果你在每个颜色通道上想有 256 个 bins,你可以传递 {256, 256, 256}

const float\** ranges:

  • 这是一个指向数组的指针,数组中每个元素定义了直方图的值范围。对于标准的 8 位图像,这通常是 {0, 256}

bool uniform:

  • 这个布尔值指定直方图是否均匀。如果为 true,表示所有的 bin 的宽度都是相同的。这通常可以提高计算的效率。

bool accumulate:

  • 如果这个值被设为 true,则直方图在调用时不会被清空。这允许从多个数组中累积直方图,或者用于时间上连续的直方图更新。

举个栗子

 calcHist(&bgr_channels[0], 1, 0, Mat(), hist, 1, &histSize, &histRange, true, false);

这行代码则是 只计算通道B(蓝色)像素值分布,只计算一张图片,第一个通道,不需要使用掩码,输出到hist,数量为1维,范围为0-255的灰度等级,使所有的bin的宽度相同,调用完毕之后清空直方图,不进行累计。

使用OpenCV API来绘制直方图

上述代码已经得到了结果,存放在hist 中, 接下来需要创建一个空的图像,将结果绘制上去。

1.首先进行归一化,目的确保 条目的数量不超过空白图像的高度 ,因此将条目的数量 归一到0到最高点的范围之内。

 normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

2.遍历 0-255 每个条目数量,使用 OpenCV line API 绘制到图像中。使用 line 函数绘制直方图时,通过 Point 类创建线的起点和终点。这段代码是用来将计算出的直方图的数据可视化为一个图像。每一次循环中的 line 函数调用都会在直方图图像上绘制一条小线段,这些线段合起来形成了完整的直方图。

bin_w * (i - 1):这计算了当前 bin 的起始水平位置

hist_h - cvRound(hist.at<float>(i - 1)):这计算了线段起点的垂直位置。hist_h 是直方图图像的总高度,hist.at<float>(i - 1) 从直方图数据中获取当前 bin (i-1) 的值(这个值表示该强度值的像素数量),使用 cvRound 将其四舍五入到最近的整数。从 hist_h 中减去这个值将坐标翻转,因为在图像坐标中,y 轴是向下增长的。

  for (int i = 1; i < histSize; i++) {
        line(histImage,
             Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
             Point(bin_w * i, hist_h - cvRound(hist.at<float>(i))),
             Scalar(255), 2, 8, 0);
    }

效果图:

在这里插入图片描述

彩色三通道直方图

可以直接调用calcHist函数输出三个通道的直方图,也可以分三次调用单通道直方图代码,本文采用了后者

void show_dims_hist_demo(Mat &image){
    // 计算直方图
    vector<Mat> bgr_channels;
    split(image,bgr_channels);
    int histSize = 256; // 从 0 到 255
    float range[] = { 0, 256 }; // 强度范围 灰度等级
    const float* histRange = { range };
    int hist_w = 512, hist_h = 400;
    int bin_w = cvRound((double) hist_w / histSize);
    Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));  // 使用彩色图像以显示三个通道
    vector<Scalar> colors = {Scalar(255, 0, 0), Scalar(0, 255, 0), Scalar(0, 0, 255)};  // 蓝、绿、红
    for (int c = 0; c < 3; c++) {
        Mat hist;
        calcHist(&bgr_channels[c], 1, 0, Mat(), hist, 1, &histSize, &histRange, true, false);
        normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
        // 绘制直方图
        for (int i = 1; i < histSize; i++) {
            line(histImage,
                 Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
                 Point(bin_w * i, hist_h - cvRound(hist.at<float>(i))),
                 colors[c], 2, 8, 0);
        }
    }
    imshow("Histogram", histImage);
    waitKey(0);
}

与单通道唯一不同的是,调用了for循环,绘制了三条曲线代表 B G R 三个通道

效果图:

在这里插入图片描述

直方图均衡化概念

直方图均衡化(Histogram Equalization)是一种图像处理技术,用于增强图像的对比度。其目的是通过调整灰度级分布,使得图像的灰度级在直方图上的分布更加均匀,从而提升图像的视觉效果

均衡化作用

  1. 增强图像对比度:通过均衡化,原本对比度较低的图像可以变得更加清晰,细节更加明显。
  2. 改善图像质量:直方图均衡化可以使图像在视觉上更加均衡和舒适,特别是在光照条件不佳的情况下。
  3. 提高图像处理算法的效果:许多图像处理和计算机视觉算法在对比度较高的图像上表现更好,直方图均衡化可以作为预处理步骤提高这些算法的性能。

均衡化效果

左边为原始图像,右边为均衡化之后的图像

在这里插入图片描述

均衡化数学原理

直方图均衡化的数学原理涉及图像的累积分布函数(CDF)和灰度级映射。以下是其具体步骤和公式:

步骤

  1. 计算图像的直方图
    设原始图像有 (N) 个像素,每个像素的灰度级范围为 (0) 到 (L-1),计算每个灰度级 (i) 的像素数量 (n_i),并得到图像的概率密度函数(PDF):
    p ( i ) = n i N , i = 0 , 1 , 2 , … , L − 1 p(i) = \frac{n_i}{N}, \quad i = 0, 1, 2, \ldots, L-1 p(i)=Nni,i=0,1,2,,L1

  2. 计算累积分布函数(CDF)
    累积分布函数 (c(i)) 是概率密度函数 (p(i)) 的累加和:
    c ( i ) = ∑ j = 0 i p ( j ) , i = 0 , 1 , 2 , … , L − 1 c(i) = \sum_{j=0}^{i} p(j), \quad i = 0, 1, 2, \ldots, L-1 c(i)=j=0ip(j),i=0,1,2,,L1

  3. 映射旧灰度级到新灰度级
    利用累积分布函数将旧的灰度级 (i) 映射到新的灰度级 (i’),映射关系为:
    i ′ = ⌊ ( L − 1 ) ⋅ c ( i ) ⌋ i' = \left\lfloor (L-1) \cdot c(i) \right\rfloor i=(L1)c(i)
    向下取整

  4. 生成均衡化后的图像
    使用上述映射关系,将原始图像中的每个像素灰度级 (i) 映射为新的灰度级 (i’),得到均衡化后的图像。

数学公式

设原始图像的灰度级为 (X),均衡化后的灰度级为 (Y),其关系可以表示为:
Y = T ( X ) Y = T(X) Y=T(X)
其中 T(X) 是映射函数,定义为:
T ( X ) = ( L − 1 ) ⋅ ∫ 0 X p ( x )   d x T(X) = (L-1) \cdot \int_{0}^{X} p(x) \, dx T(X)=(L1)0Xp(x)dx

对于离散情况,映射关系可以写为:

Y i = ( L − 1 ) ⋅ ∑ j = 0 i p ( j ) Y_i = (L-1) \cdot \sum_{j=0}^{i} p(j) Yi=(L1)j=0ip(j)

示例

假设一个简单的8位灰度图像,其灰度级范围为 (0) 到 (255)。原始图像的灰度直方图如下:

灰度级 (i)像素数量 (n_i)概率密度函数 (p(i))累积分布函数 (c(i))新灰度级 (i’)
05000.050.0512
110000.100.1538
215000.150.3076
320000.200.50127
415000.150.65165
510000.100.75191
65000.050.80204
75000.050.85217
85000.050.90229
95000.050.95242
105000.051.00255

根据上述表格,新灰度级的映射关系已经确定,可以用于生成均衡化后的图像。

直方图均衡化通过这种方式重新分配灰度级,使得图像的整体对比度得到增强,从而使图像细节更加清晰。

OpenCV 直方图均衡化 API

OpenCV 提供了用于直方图均衡化的函数 equalizeHist。这个函数可以对单通道(通常是灰度图像)进行直方图均衡化

使用示例

int main()
{
    string imagePath = "C:\\Users\\Marxist\\Pictures\\coco\\raw_lenna.jpg";
    string video_path = "C:\\Users\\Marxist\\Videos\\Captures\\gtr.mp4";
    Mat image = imread(imagePath,IMREAD_GRAYSCALE);
    if(image.empty()){
        cout<<"file not exist!"<<endl;
        return -1;
    }
    Mat dst;
    equalizeHist(image,dst);
    imshow("raw image",image);
    imshow("equalizeHist image",dst);
    waitKey(0);
    return 0;
}

注: equalizeHist 只能用于单通道。可以对彩色图像的每个通道分别进行直方图均衡化,或更常见的是将彩色图像转换到 YCrCb 或 HSV 色彩空间,在亮度通道上应用直方图均衡化,然后再转换回 RGB 色彩空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Trump. yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值