OpenCV入门(20):图像处理之直方图
在图像处理中,直方图是一种非常重要的工具,它可以帮助我们了解图像的像素分布情况。通过分析图像的直方图,我们可以进行图像增强、对比度调整、图像分割等操作。
一、什么是图像直方图?
图像直方图是图像像素强度分布的图形表示,对于灰度图像,直方图显示了每个灰度级(0 到 255)在图像中出现的频率,对于彩色图像,我们可以分别计算每个通道(如 R、G、B)的直方图。
- 直方图: 表示图像中像素强度的分布情况,横轴表示像素强度值,纵轴表示该强度值的像素数量。
- 灰度直方图: 针对灰度图像的直方图,表示每个灰度级的像素数量。
- 颜色直方图: 针对彩色图像的直方图,分别表示每个颜色通道(如 BGR)的像素强度分布。
简单来说,直方图是一个统计图表,它显示了图像中每个像素强度值(比如亮度值0-255)出现的频率(即像素个数)。
- 横坐标 (X轴):通常表示像素的强度值(例如,对于 8 位灰度图,就是 0 到 255)。
- 纵坐标 (Y轴):表示具有该强度值的像素数量。
下面是一个一个典型的灰度图像及其直方图:

二、直方图有什么用?
直方图非常有用,它可以告诉我们很多关于图像的信息:
- 图像亮度与对比度:
- 如果直方图的峰值主要集中在左边(低亮度值),说明图像偏暗。
- 如果峰值集中在右边(高亮度值),说明图像偏亮。
- 如果峰值分布很窄,说明图像对比度较低。
- 如果峰值分布很广,说明图像对比度较高。
- 图像内容分析:通过直方图的形状,可以大致推断图像的内容。例如,一张大部分是天空的图片,其直方图的蓝色通道可能会在某个区域有高峰。
- 图像阈值化:直方图可以帮助我们选择合适的阈值,用于将图像分割成前景和背景(例如,大津法就是基于直方图的)。
- 图像均衡化:通过拉伸直方图,使其分布更均匀,可以增强图像的对比度,这就是“直方图均衡化”。
- 图像匹配与检索:比较不同图像的直方图,可以作为一种简单的图像相似性度量方法。
OpenCV 提供了丰富的直方图计算和操作函数:
| 功能 | 函数 | 说明 |
|---|---|---|
| 计算直方图 | cv::calcHist() |
计算图像的直方图。 |
| 直方图均衡化 | cv::equalizeHist() |
增强图像的对比度。 |
| 直方图比较 | cv::compareHist() |
比较两个直方图的相似度。 |
| 绘制直方图 | matplotlib.pyplot.plot() |
使用 Matplotlib 绘制直方图。 |
三、计算直方图
3.1 API
OpenCV提供了一个非常方便的函数 cv::calcHist 来计算直方图。让我们看看它的基本用法。
void 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 );
参数说明:
- images:输入的图像列表,通常是一个包含单通道或多通道图像的列表。例如
[img]。 - channels:需要计算直方图的通道索引。对于灰度图像,使用
[0];对于彩色图像,可以使用[0]、[1]、[2]分别计算蓝色、绿色和红色通道的直方图。 - mask:掩码图像。如果指定了掩码,则只计算掩码区域内的像素。如果不需要掩码,可以传入
None。 - hist:输出的直方图数组。
- histSize:直方图的 bin 数量。对于灰度图像,通常设置为
[256],表示将灰度级分为 256 个 bin。 - ranges:像素值的范围。对于灰度图像,通常设置为
[0, 256],表示像素值的范围是 0 到 255。 - accumulate:是否累积直方图。如果设置为
True,则直方图不会被清零,而是在每次调用时累积。
3.2 示例:灰度图直方图
计算一张灰度图像的直方图:
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main() {
// 1. 加载图像
Mat src = imread("lena.jpg", IMREAD_GRAYSCALE); // 以灰度模式加载
if (src.empty()) {
cout << "无法加载图像!" << endl;
return -1;
}
// 2. 设置直方图参数
Mat hist; // 用于存储直方图结果
int histSize[] = { 256 }; // Bin的数量 (0-255,共256个)
// 像素值范围 (对于8位图像是0-255)
// 注意:上限是不包含的,所以是256
float hranges[] = { 0.0f, 256.0f };
const float* ranges[] = { hranges };
int channels[] = { 0 }; // 我们只处理灰度图的第0个通道
// 3. 计算直方图
calcHist(&src, // 输入图像 (注意是地址)
1, // 图像数量
channels, // 通道列表
Mat(), // 无掩码
hist, // 输出直方图
1, // 直方图维度 (1D)
histSize, // 每个维度的bin数量
ranges // 每个维度的取值范围
);
// (可选) 打印一些直方图的值(hist现在是一个256x1的Mat)
// for (int i = 0; i < histSize[0]; i++) {
// cout << "灰度值 " << i << ": " << hist.at<float>(i, 0) << " 个像素" << endl;
// }
// 4. (重要!) 可视化直方图
// `calcHist` 计算出来的是数值,我们还需要把它画出来才能直观看到。
int hist_w = 512; // 直方图图像的宽度
int hist_h = 400; // 直方图图像的高度
int bin_w = cvRound((double)hist_w / histSize[0]); // 每个bin在图像中的宽度
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(20, 20, 20)); // 创建一个黑色背景的图像用于绘制
// 归一化直方图的值到 [0, hist_h] 区间,这样才能画在图上
// `normalize` 函数会找到hist中的最大值,然后按比例缩放所有值
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// 绘制直方图的每个bin
for (int i = 1; i < histSize[0]; 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(200, 200, 200), // 线条颜色 (浅灰色)
2, 8, 0);
}
// 5. 显示原图和直方图
imshow("Src Image", src);
imshow("Histogram Image", histImage);
waitKey(0); // 等待按键
return 0;
}
效果图如下所示:

3.3 示例:彩色图直方图 (分别计算B, G, R通道)
对于彩色图(通常是BGR顺序),我们可以为每个颜色通道分别计算直方图:
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main() {
// 1. 加载彩色图像
Mat src = imread("lena.jpg", IMREAD_COLOR);
if (src.empty()) {
cout << "无法加载图像!" << endl;
return -1;
}
// 2. 将图像分割成B, G, R三个通道
vector<Mat> bgr_planes;
split(src, bgr_planes); // bgr_planes[0] 是 B, bgr_planes[1] 是 G, bgr_planes[2] 是 R
// 3. 设置直方图参数 (与灰度图类似)
int histSize[] = { 256 };
float range[] = { 0, 256 };
const float* histRange[] = { range };
bool uniform = true;
bool accumulate = false;
// 4. 分别计算B, G, R三个通道的直方图
Mat b_hist, g_hist, r_hist;
// 计算B通道直方图
// 注意:split后的bgr_planes[0]已经是单通道图像,所以channels参数是{0}
// 如果直接用src计算,那么channels可以是{0}代表B, {1}代表G, {2}代表R
int b_channels[] = {0};
calcHist(&bgr_planes[0], 1, b_channels, Mat(), b_hist, 1, histSize, histRange, uniform, accumulate);
// 计算G通道直方图
int g_channels[] = {0}; // 对于bgr_planes[1] (G通道图像) 来说,它自己的通道索引是0
calcHist(&bgr_planes[1], 1, g_channels, Mat(), g_hist, 1, histSize, histRange, uniform, accumulate);
// 计算R通道直方图
int r_channels[] = {0}; // 对于bgr_planes[2] (R通道图像) 来说,它自己的通道索引是0
calcHist(&bgr_planes[2], 1, r_channels, Mat(), r_hist, 1, histSize, histRange, uniform, accumulate);
// 5. 绘制直方图 (与灰度图类似,但要画三条线)
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / histSize[0]);
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(20, 20, 20));
// 归一化B, G, R直方图到 [0, histImage.rows]
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// 绘制
for (int i = 1; i < histSize[0]; i++) {
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0); // 蓝色
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))),
Scalar(0, 255, 0), 2, 8, 0); // 绿色
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))),
Scalar(0, 0, 255), 2, 8, 0); // 红色
}
// 6. 显示
imshow("Src Image", src);
imshow("Histogram Image (B, G, R)", histImage);
waitKey(0);
return 0;
}
在同一张 histImage 上用不同颜色(蓝、绿、红)绘制三个通道的直方图线条。效果图如下所示:

四、二维直方图
二维直方图用于描述图像中两个通道之间的联合分布。例如,对于彩色图像中的 Hue(色调)和 Saturation(饱和度),我们可以构建一个二维直方图来反映颜色的联合统计信息,这在颜色分割、目标检测等任务中非常有用。
4.1 API
同样使用 cv::calcHist 函数,但需要同时传入两个通道的数据,并设置维数为 2
void 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 );
4.2 示例代码
计算 HSV 图像中 Hue 和 Saturation 的二维直方图:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取彩色图像并转换到 HSV 空间
Mat img = imread("lena.jpg");
if (img.empty()) {
cout << "加载图像失败!" << endl;
return -1;
}
Mat hsv;
cvtColor(img, hsv, COLOR_BGR2HSV);
// 设置二维直方图参数
int hBins = 50, sBins = 60;
int histSize[2] = { hBins, sBins };
// H 范围:0~180,S 范围:0~256
float hRanges[] = { 0, 180 };
float sRanges[] = { 0, 256 };
const float* ranges[] = { hRanges, sRanges };
int channels[] = { 0, 1 }; // 使用 H 和 S 通道
// 计算二维直方图
Mat hist;
calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
// 归一化直方图到 [0, 255]
normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());
// 将二维直方图显示为图像
int scale = 4; // 调整显示图像的尺寸因子
Mat histImg = Mat::zeros(sBins * scale, hBins * scale, CV_8UC3);
// 注意:hist 的维度为 [hBins x sBins]
// 这里遍历 hBins 和 sBins,绘制每个 bin 的强度
for (int h = 0; h < hBins; h++) {
for (int s = 0; s < sBins; s++) {
float binVal = hist.at<float>(h, s);
int intensity = cvRound(binVal);
// 使用颜色填充直方图的每个小矩形区域
rectangle(histImg,
Point(h * scale, s * scale),
Point((h + 1) * scale - 1, (s + 1) * scale - 1),
Scalar(intensity, intensity, intensity),
FILLED);
}
}
// 转为彩色热力图
applyColorMap(histImg, histImg, COLORMAP_JET);
imshow("Src Image", img);
imshow("Histogram Image (B, G, R)", histImg);
waitKey(0);
return 0;
}
此示例展示了如何计算 HSV 图像中 Hue 和 Saturation 的二维直方图,并将统计结果绘制为一幅热图,直观反映颜色分布情况。效果图如下所示:

五、直方图均衡化
直方图均衡化是一种图像增强技术,其目标是改善图像对比度。通过重新分配像素的灰度值,使得直方图变得更均匀,达到突出细节和边缘的效果。该方法常用于灰度图像,但对于彩色图像,通常先将图像转换到 YCrCb 或 HSV 空间,仅对亮度通道进行均衡化,再转换回原空间。
5.1 API
OpenCV 提供了 cv::equalizeHist 函数,该函数针对单通道图像进行均衡化。
void equalizeHist( InputArray src, OutputArray dst );
src:源图像 Matsrc:目标图像 Mat
5.2 示例:灰度图像直方图均衡化
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取灰度图像
Mat gray = imread("lena.jpg",IMREAD_GRAYSCALE);
if (gray.empty()) {
cerr << "加载图像失败!" << endl;
return -1;
}
// 进行直方图均衡化
Mat equalized;
equalizeHist(gray, equalized);
// 显示原图和均衡化后的图像
imshow("Src Image", gray);
imshow("Histogram Image", equalized);
waitKey(0);
return 0;
}
运行后右边图像的对比度明显增强,效果图如下所示:

5.3 示例:彩色图像直方图均衡化
对于彩色图像,我们可以先转换为 YCrCb 空间,对 Y 通道进行均衡化,再转换回 BGR 空间。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取彩色图像
Mat img = imread("lena.jpg");
if (img.empty()) {
cerr << "加载图像失败!" << endl;
return -1;
}
// 转换到 YCrCb 空间
Mat img_ycrcb;
cvtColor(img, img_ycrcb, COLOR_BGR2YCrCb);
// 分离通道,均衡化 Y 通道
vector<Mat> channels;
split(img_ycrcb, channels);
equalizeHist(channels[0], channels[0]);
merge(channels, img_ycrcb);
// 转换回 BGR 空间
Mat img_equalized;
cvtColor(img_ycrcb, img_equalized, COLOR_YCrCb2BGR);
imshow("Src Image", img);
imshow("Histogram Image", img_equalized);
waitKey(0);
return 0;
}
效果图如下所示:

六、自适应直方图均衡
自适应直方图均衡(Adaptive Histogram Equalization, AHE)是一种改进的直方图均衡化方法,它不是对整幅图像进行全局均衡,而是将图像划分为多个小区域(称为子块或窗口),在每个子块上分别执行直方图均衡化,以增强局部对比度。
6.1 概述
基本步骤:
- 划分子块(Tiles): 将图像分成多个小的子区域(如 8×8 或 16×16);
- 计算直方图均衡化: 对每个子块单独计算累积分布函数(CDF)并应用均衡化;
- 插值平滑(Interpolation): 由于不同子块的均衡化结果可能不连续,采用双线性插值使其过渡平滑,避免块状效应;
- 边界处理: 处理图像边缘区域,防止伪影出现。
对比普通直方图均衡化的优势:
- 适用于局部光照不均的图像,增强细节;
- 适用于对比度较低的区域,而不会过度增强对比度已高的区域;
- 解决了全局均衡化可能导致的过度曝光或伪影问题。
改进方法:CLAHE(对比度受限自适应直方图均衡化)
CLAHE(Contrast Limited Adaptive Histogram Equalization)是在 AHE 基础上增加了对比度限制,防止某些局部区域对比度过高而产生噪声。其核心思想是对每个子块的直方图设定一个对比度限制阈值,若某个灰度级的频率超过阈值,则将超出的部分均匀分配到所有灰度级,最终进行均衡化。
6.2 示例代码
在 OpenCV 中,自适应直方图均衡化可通过 cv::createCLAHE() 实现,例如:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取彩色图像
Mat img = imread("lena.jpg", IMREAD_GRAYSCALE);
if (img.empty()) {
cerr << "加载图像失败!" << endl;
return -1;
}
// 自适应直方图均衡
Ptr<CLAHE> clahe = createCLAHE(2.0, Size(8, 8)); // 限制对比度 2.0,窗口大小 8×8
Mat dst;
clahe->apply(img, dst);
imshow("Original", img);
imshow("CLAHE", dst);
waitKey(0);
return 0;
}
效果图如下所示:

七、直方图的应用
- 图像增强: 通过直方图均衡化,可以增强图像的对比度,使细节更加清晰。
- 图像分割: 过分析直方图,可以确定阈值,用于图像分割。
- 图像匹配: 通过比较直方图,可以判断两幅图像的相似度,用于图像匹配和检索。
- 颜色分析: 通过颜色直方图,可以分析图像的颜色分布,用于颜色校正和风格化处理。
八、总结
本文详细介绍了 OpenCV C++ 中的直方图分析技术,包括:
- 图像直方图:利用
cv::calcHist计算灰度图像的直方图,并绘制统计结果。 - 二维直方图:通过统计两个通道(如 HSV 中的 H 和 S)的联合分布,构建二维直方图热图,展示颜色分布信息。
- 直方图均衡化:使用
cv::equalizeHist对灰度图像进行均衡化,同时介绍了彩色图像均衡化的常用方法(基于YCrCb色彩空间). - 自适应均衡化:一种改进的直方图均衡化方法,它不是对整幅图像进行全局均衡,而是将图像划分为多个小区域(称为子块或窗口),在每个子块上分别执行直方图均衡化,以增强局部对比度

浙公网安备 33010602011771号