直方图广泛运用于很多计算机视觉运用当中,通过标记帧与帧之间显著的边缘和颜色的统计变化,来检测视频中场景的变化。在每个兴趣点设置一个有相近特征的直方图所构成 “标签”,用以确定图像中的兴趣点。边缘、色彩、角度等直方图构成了可以被传递给目标识别分类器的一个通用特征类型。色彩和边缘的直方图序列还可以用来识别网络视频是否被复制。
其实,简单来说,直方图就是对数据进行统计的一种方法,并且将统计值组织到一系列实现定义好的 bin 当中。其中, bin 为直方图中经常用到的一个概念,可以译为 “直条” 或 “组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、方向、色彩或任何其他特征。且无论如何,直方图获得的是数据分布的统计图。通常直方图的维数要低于原始数据。
图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此一张较暗图片的直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。CV 领域常借助图像直方图来实现图像的二值化。
直方图的意义如下:
● 直方图是图像中像素强度分布的图形表达方式。
● 它统计了每一个强度值所具有的像素个数。
直方图是对数据的统计集合,并将统计结果分布于一系列预定义的 bins 中。这里的数据不仅仅指的是灰度值,且统计数据可能是任何有效描述图像的特征。
假设有一个矩阵包含一张图像的信息(灰度值 0 - 255),既然已知数字的范围包含 256 个值,于是可以按一定规律将这个范围分割成子区域(也就是 bins)。如:
然后再统计每一个 bin(i) 的像素数目。采用这一方法来统计上面的数字矩阵,可以得到下图(其中 x 轴表示 bin,y 轴表示各个 bin 中的像素个数):
直方图的一些术语和细节:
● dims:需要统计的特征数目。在上例中,dims = 1 ,因为仅仅统计了灰度值(灰度图像)。
● bins:每个特征空间子区段的数目,可译为 “直条” 或 “组距”,在上例中, bins = 16。
● range:每个特征空间的取值范围。在上例中,range = [0, 255]。
直方图的计算在 OpenCV 中可使用 calcHist() 函数,而计算完成之后,可以采用 OpenCV 中的绘图函数,如绘制矩形的 rectangle() 函数,绘制线段的 line() 来完成。
calcHist() 函数用于计算一个或多个阵列的直方图。
void calcHist(
const Mat* images, //输入的数组或数据集
int nimages, //输入数组的个数
const int* channels, //需要统计的通道(dim)索引
InputArray mask, //可选的操作掩码,用于标记出统计直方图的数组元素数据
OutputArray hist, //输出的目标直方图
int dims, //需要计算的直方图的维数
const int* histSize, //存放每个直方图尺寸的数组
const float** ranges, //每一维数值的取值范围
bool uniform = true, //指示直方图是否均匀的标识符
bool accumulate = false //累计标识符,主要是允许多从多个阵列中计算单个直方图,
//或者用于在特定的时间更新直方图。
)
● 第一个参数:const Mat* 类型的 iamges,输入的数组(或数据集),它们需为相同的深度(CV_8U 或 CV_32F)和相同的尺寸。
● 第二个参数:int 类型的 nimages,输入数组的个数,也就是第一个参数中存放了多少张 “图像”,有几个原数组。
● 第三个参数:const int* 类型的 channels,需要统计的通道(dim)索引。第一个数组通道从 0 到 images[0].channels() - 1,而第二个数组通道从 images[0].channels() 计算到 images[0].channels() + images[1].channels() - 1。
● 第四个参数:InputArray 类型的 mask,可选的操作掩码。如果此掩码不为空,那么它必须为 8 位,并且与 images[i] 有同样的大小和尺寸。这里的非零掩码元素用于标记出统计直方图的数组元素数据。
● 第五个参数:OutputArray 类型的 hist,输出的目标直方图,一个二维数组。
● 第六个参数:int 类型的 dims,需要计算的直方图的维数,必须是正数,且不大于 CV_MAX_DIMS(在 OpenCV3 中等于 32)。
● 第七个参数:const int* 类型的 histSize,存放每个维度的直方图尺寸的数组。
● 第八个参数:const float** 类型的 ranges,表示每一个维度数组(第六个参数 dims)的每一维的边界阵列,可以理解为每一维数值的取值范围。
● 第九个参数:bool 类型的 uniform,指示直方图是否均匀的标识符,有默认值 true。
● 第十个参数:bool 类型的 accumulate,累计标识符,有默认值 false。若其为 true,直方图在配置阶段不会被清零。此功能主要是允许多从多个阵列中计算单个直方图,或者用于在特定的时间更新直方图。
minMaxLoc() 函数的作用是在数组中找到全局最小值和最大值。
void minMaxLoc(
InputArray src,
double* minVal,
double* maxVal = 0,
Point* minLoc = 0,
Point* maxLoc = 0,
InputArray mask = noArray()
)
● 第一个参数:InputArray 类型的 src,输入的单通道阵列。
● 第二个参数:double* 类型的 minVal,返回最小值的指针。若无需返回,此参数设置为 NULL。
● 第三个参数:double* 类型的 maxVal,返回最大值的指针,若无需返回,此参数设置为 NULL。
● 第四个参数:Point* 类型的 minLoc,返回最小位置的指针(二维情况下)。若无需返回,此值置为 NULL。
● 第五个参数:Point* 类型的 maxLoc,返回最大位置的指针(二维情况下)。若无需返回,此值置为 NULL。
● 第六个参数:InputArray 类型的 mask,用于选择子阵列的可选掩膜。
下面的示例说明如何计算彩色图像的色调,饱和度二维直方图。
● 色调(Hue),饱和度(Saturation)。所以 H-S 直方图就是 色调——饱和度 直方图。
#include
#include
#include
using namespace std;
using namespace cv;
int main() {
system("color 2F");
//载入原图,转化为 HSV 颜色模型
Mat srcImage, hsvImage;
srcImage = imread("1.jpg");
cvtColor(srcImage, hsvImage, COLOR_BGR2HSV);
//将色调量化为 30 个等级,将饱和度量化为 32 个等级
int hueBinNum = 30; //色调的直方图直条数量
int saturationBinNum = 32; //饱和度的直方图直条数量
int histSize[] = { hueBinNum,saturationBinNum };
//定义色调的变化范围为 0 到 179
float hueRanges[] = { 0,180 };
//定义饱和度的变化范围为 0(黑、白、灰)到 255(纯光谱颜色)
float saturationRanges[] = { 0,256 };
const float* ranges[] = { hueRanges,saturationRanges };
MatND dstHist; //Mat 一般指二维矩阵,MatND 指多维矩阵( > 2 )
//calcHist 函数中将计算第 0 通道和第 1 通道的直方图
int channels[] = { 0,1 };
//正式调用 calcHist ,进行直方图计算
calcHist(
&hsvImage, //输入的数组
1, //数组个数为 1
channels, //通道索引
Mat(), //不使用掩膜
dstHist, //输出的目标直方图
2, //需要计算的直方图的维度为 2
histSize, //存放每个维度的直方图尺寸的数组
ranges, //每一维数值的取值范围数组
true, //指示直方图是否均匀的标识符,true 表示均匀的直方图
false //累计标识符,false 表示直方图在配置阶段会被清零
);
//为绘制直方图准备参数
double maxValue = 0; //最大值
//查找数组和子数组的全局最小值和最大值存入 maxValue 中
minMaxLoc(dstHist, 0, &maxValue, 0, 0);
int scale = 10; //直方图放大倍数
Mat histImage = Mat::zeros(saturationBinNum * scale, hueBinNum * 10, CV_8UC3);
//双重循环,实现直方图绘制
for (int hue = 0; hue < hueBinNum; hue++) {
for (int saturation = 0; saturation < saturationBinNum; saturation++) {
//直方图直条的值
float binValue = dstHist.at<float>(hue, saturation);
int intensity = cvRound(binValue * 255 / maxValue); //强度
//正式进行绘制
rectangle(
histImage,
Point(hue * scale, saturation * scale),
Point((hue + 1) * scale - 1, (saturation + 1) * scale - 1),
Scalar::all(intensity),
FILLED
);
}
}
imshow("素材图", srcImage);
imshow("H-S 直方图", histImage);
waitKey(0);
return 0;
}
#include
#include
#include
using namespace std;
using namespace cv;
int main() {
system("color 2F");
Mat srcImage = imread("1.jpg", 0);
imshow("原图", srcImage);
if (!srcImage.data) {
printf("图像读取失败!");
return 0;
}
MatND dstHist;
int dims = 1;
float hranges[] = { 0,255 };
const float *ranges[] = { hranges }; //这里需为 const 类型
int size = 256;
int channels = 0;
//计算图像的直方图
calcHist(&srcImage, 1, &channels, Mat(), dstHist, dims, &size, ranges);
int scale = 1;
Mat dstImage(size * scale, size, CV_8U, Scalar(0));
//获取最大值和最小值
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
//绘制出直方图
int hpt = saturate_cast<int>(0.9 * size);
for (int i = 0; i < 256; i++) {
float binValue = dstHist.at<float>(i); //注意 hist 中是 float 类型
int realValue = saturate_cast<int>(binValue * hpt / maxValue);
rectangle(dstImage, Point(i * scale, size - 1),
Point((i + 1) * scale - 1, size - realValue), Scalar(255));
}
imshow("一维直方图", dstImage);
waitKey(0);
return 0;
}
Mat特指2维矩阵,MatND是多维矩阵(>=3维)
#include
#include
#include
using namespace std;
using namespace cv;
int main() {
system("color 2F");
Mat srcImage = imread("1.jpg");
imshow("素材图", srcImage);
if (!srcImage.data) {
printf("图像读取失败!");
return 0;
}
int bins = 256;
int hist_size[] = { bins };
float range[] = { 0,256 };
const float *ranges[] = { range };
MatND redHist, greenHist, blueHist;
int channels_r[] = { 0 };
//进行直方图的计算(红色分量部分)
calcHist(&srcImage, 1, channels_r, Mat(), //不使用掩膜
redHist, 1, hist_size, ranges, true, false);
//进行直方图计算(绿色分量部分)
int channels_g[] = { 1 };
calcHist(&srcImage, 1, channels_g, Mat(),
greenHist, 1, hist_size, ranges, true, false);
//进行直方图计算(蓝色分量部分)
int channels_b[] = { 2 };
calcHist(&srcImage, 1, channels_b, Mat(),
blueHist, 1, hist_size, ranges, true, false);
//准备参数绘制三色直方图
double maxValue_red, maxValue_green, maxValue_blue;
minMaxLoc(redHist, 0, &maxValue_red, 0, 0);
minMaxLoc(greenHist, 0, &maxValue_green, 0, 0);
minMaxLoc(blueHist, 0, &maxValue_blue, 0, 0);
int scale = 1;
int histHeight = 256;
//bins * 3 是因为要绘制三个通道,每个通道的像素取值在 0-bins
Mat histImage = Mat::zeros(histHeight, bins * 3, CV_8UC3);
//开始绘制
for (int i = 0; i < bins; i++) {
float binValue_red = redHist.at<float>(i);
float binValue_green = greenHist.at<float>(i);
float binValue_blue = blueHist.at<float>(i);
//计算高度时的乘除与下面绘图的 histHeight - intensity 是为了便于显示,否则有的色度很低
//要绘制的高度
int intensity_red = cvRound(binValue_red * histHeight / maxValue_red);
int intensity_green = cvRound(binValue_green * histHeight / maxValue_green);
int intensity_blue = cvRound(binValue_blue * histHeight / maxValue_blue);
//绘制红色分量直方图
rectangle(histImage, Point(i * scale, histHeight - 1),
Point((i + 1) * scale - 1, histHeight - intensity_red),
Scalar(255,0,0));
//绘制绿色分量直方图
rectangle(histImage, Point((i + bins) * scale, histHeight - 1),
Point((i + bins + 1) * scale - 1, histHeight - intensity_green),
Scalar(0, 255, 0));
//绘制分量直方图
rectangle(histImage, Point((i + bins * 2) * scale, histHeight - 1),
Point((i + bins * 2 + 1) * scale - 1, histHeight - intensity_blue),
Scalar(0, 0, 255));
}
imshow("图像的 RGB 直方图", histImage);
waitKey(0);
return 0;
}