文章目录
直方图相关概念
直方图(Histogram)是一种图表,用于显示数据集的分布情况。它通过将数据范围划分为若干个连续的区间(称为“桶”或“箱”—bins),然后统计每个区间内的数据点数量,以条形图的形式展示这些数量。横轴表示数据的取值范围,纵轴表示每个区间内数据点的数量或频率。例如下图(转载自025 - 二维直方图_哔哩哔哩_bilibili)
颜色灰度级
- 灰度级:在灰度图像中,每个像素的亮度值,可以是0到255之间的整数,其中0表示黑色,255表示白色,中间的值表示不同程度的灰色。
- 直方图:一种柱状图,用于表示每个灰度级在图像中出现的频率。
作用
- 图像分析:直方图可以帮助分析图像的整体亮度分布,了解图像是过暗、过亮还是对比度适中。
- 图像增强:通过直方图均衡化等技术,可以增强图像的对比度,使图像细节更加清晰。
- 图像分割:在图像处理和计算机视觉中,可以利用直方图来确定图像的阈值,进行目标和背景的分割。
- 图像对比度调整:根据直方图调整图像对比度,使图像更符合视觉需求。
应用场景
- 医学影像处理:在医学图像(如X射线、CT、MRI)中,通过直方图分析可以增强图像,帮助医生更好地诊断。
- 摄影和图像编辑:摄影师和图像编辑软件利用直方图调整图像的曝光、对比度和颜色分布,以达到理想的视觉效果。
- 监控系统:在监控图像中,直方图可以帮助调整图像质量,使得图像在不同光照条件下保持较好的可见性。
- 计算机视觉:在目标检测、识别和跟踪中,直方图作为特征描述子之一,可以用于比较和匹配图像。
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)是一种图像处理技术,用于增强图像的对比度。其目的是通过调整灰度级分布,使得图像的灰度级在直方图上的分布更加均匀,从而提升图像的视觉效果。
均衡化作用
- 增强图像对比度:通过均衡化,原本对比度较低的图像可以变得更加清晰,细节更加明显。
- 改善图像质量:直方图均衡化可以使图像在视觉上更加均衡和舒适,特别是在光照条件不佳的情况下。
- 提高图像处理算法的效果:许多图像处理和计算机视觉算法在对比度较高的图像上表现更好,直方图均衡化可以作为预处理步骤提高这些算法的性能。
均衡化效果
左边为原始图像,右边为均衡化之后的图像
均衡化数学原理
直方图均衡化的数学原理涉及图像的累积分布函数(CDF)和灰度级映射。以下是其具体步骤和公式:
步骤
-
计算图像的直方图:
设原始图像有 (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,…,L−1 -
计算累积分布函数(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=0∑ip(j),i=0,1,2,…,L−1 -
映射旧灰度级到新灰度级:
利用累积分布函数将旧的灰度级 (i) 映射到新的灰度级 (i’),映射关系为:
i ′ = ⌊ ( L − 1 ) ⋅ c ( i ) ⌋ i' = \left\lfloor (L-1) \cdot c(i) \right\rfloor i′=⌊(L−1)⋅c(i)⌋
向下取整 -
生成均衡化后的图像:
使用上述映射关系,将原始图像中的每个像素灰度级 (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)=(L−1)⋅∫0Xp(x)dx
对于离散情况,映射关系可以写为:
Y i = ( L − 1 ) ⋅ ∑ j = 0 i p ( j ) Y_i = (L-1) \cdot \sum_{j=0}^{i} p(j) Yi=(L−1)⋅j=0∑ip(j)
示例
假设一个简单的8位灰度图像,其灰度级范围为 (0) 到 (255)。原始图像的灰度直方图如下:
灰度级 (i) | 像素数量 (n_i) | 概率密度函数 (p(i)) | 累积分布函数 (c(i)) | 新灰度级 (i’) |
---|---|---|---|---|
0 | 500 | 0.05 | 0.05 | 12 |
1 | 1000 | 0.10 | 0.15 | 38 |
2 | 1500 | 0.15 | 0.30 | 76 |
3 | 2000 | 0.20 | 0.50 | 127 |
4 | 1500 | 0.15 | 0.65 | 165 |
5 | 1000 | 0.10 | 0.75 | 191 |
6 | 500 | 0.05 | 0.80 | 204 |
7 | 500 | 0.05 | 0.85 | 217 |
8 | 500 | 0.05 | 0.90 | 229 |
9 | 500 | 0.05 | 0.95 | 242 |
10 | 500 | 0.05 | 1.00 | 255 |
根据上述表格,新灰度级的映射关系已经确定,可以用于生成均衡化后的图像。
直方图均衡化通过这种方式重新分配灰度级,使得图像的整体对比度得到增强,从而使图像细节更加清晰。
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 色彩空间。