1 直方图
灰度级范围为 [0,L−1] 的数字图像的直方图是离散函数 h(rk)=nk , 其中 rk 是第k级灰度值,nk 是图像中灰度为 rk 的像素个数。在实践中,经常用乘积 MN 表示的图像像素的总数除它的每个分量来归一化直方图,通常 M 和 N 是图像的行和列的位数。因此,归一化后的直方图由 p(rk)=nk/MN 给出,其中 k=0,1,...,L−1 。简单地说, p(rk) 是灰度级 rk 在图像中出现的概率的一个估计。归一化直方图的所有分量之和应等于1。
在OPENCV3.0中有相关函数如下,其中有3个版本,这是经常用到的一个:
CV_EXPORTS void 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 );
images, 是要求的Mat的指针,这里可以传递一个数组,可以同时求很多幅图片的直方图,前提是他们的深度相同,CV_8U或者CV_32F,尺寸相同。通道数可以不同;
nimages, 源图像个数;
channels, 传递要加入直方图计算的通道。该函数可以求多个通道的直方图。通道序号从0开始依次递增。假如第一幅图像有3个通道,第二幅图像有两个通道。则:images[0]的通道序号为0、1、2,images[1]的通道序号则为3、4;如果想通过5个通道计算直方图,则传递的通道channels为int channels[5] = {0, 1, 2, 3, 4, 5}。
mask, 掩码矩阵,没有掩码,则传递空矩阵就行了。如果非空则掩码矩阵大小必须和图像大小相同,在掩码矩阵中非空元素将被计算到直方图内。
hist, 输出直方图;
dims, 直方图维度,必须大于0,并小于CV_MAX_DIMS(32);
histSize, 直方图中每个维度级别数量,比如灰度值(0-255),如果级别数量为4,则灰度值直方图会按照[0, 63],[64,127,[128,191],[192,255],也称为bin数目,这里是4个bin。如果是多维的就需要传递多个。每个维度的大小用一个int来表示。所以histSize是一个数组;
ranges, 一个维度中的每一个bin的取值范围。如果uniform == true,则range可以用一个具有2个元素(一个最小值和一个最大值)的数组表示。如果uniform == false,则需要用一个具有histSize + 1个元素(每相邻的两个元素组成的取值空间对应的bin的取值范围)的数组表示。如果统计多个维度则需要传递多个数组。所以ranges,是一个二维数组。如下代码是uniform == false时的情况:
int nHistSize[] = { 5 };
// range有6个元素,每个元素,组成5个bin的取值范围
float range[] = { 0, 70,100, 120, 200,255 };
const float* fHistRanges[] = { range };
Mat histR, histG, histB;
// 这里的uniform == false
calcHist(&matRGB[1], 1, &nChannels, Mat(), histB, 1, nHistSize, fHistRanges, false, false);
uniform, 表示直方图中一个维度中的各个bin的宽度是否相同,详细解释见ranges中介绍;
accumulate, 在计算直方图时是否清空传入的hist。true,则表示不清空,false表示清空。该参数一般设置为false。只有在想要统计多个图像序列中的累加直方图时才会设置为true。例如:
calcHist(mat1, 1, &nChannels, Mat(), hist, 1, nHistSize, fHistRanges, true, false);
calcHist(mat2, 1, &nChannels, Mat(), hist, 1, nHistSize, fHistRanges, true, true);
以上代码统计了mat1和mat2中图像数据的直方图到hist中。也就是说hist中的直方图数据是从matRGB1和mat2中统计出来的,不单单是mat1的数据,也不单单是mat2的数据。如果第二行中最后一个参数为false则hist中统计的数据单单是mat2的,mat1的数据被清空了。
1.1 统计灰度直方图
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <string>
using namespace cv;
int main()
{
std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
Mat matSrc = imread(strPath + "panda.jpg");
Mat matRGB[3];
split(matSrc, matRGB);
int Channels[] = { 0 };
int nHistSize[] = { 256 };
float range[] = { 0, 255 };
const float* fHistRanges[] = { range };
Mat histR, histG, histB;
// 计算直方图
calcHist(&matRGB[0], 1, Channels, Mat(), histB, 1, nHistSize, fHistRanges, true, false);
calcHist(&matRGB[1], 1, Channels, Mat(), histG, 1, nHistSize, fHistRanges, true, false);
calcHist(&matRGB[2], 1, Channels, Mat(), histR, 1, nHistSize, fHistRanges, true, false);
// 创建直方图画布
int nHistWidth = 800;
int nHistHeight = 600;
int nBinWidth = cvRound((double)nHistWidth / nHistSize[0]);
Mat matHistImage(nHistHeight, nHistWidth, CV_8UC3, Scalar(255, 255, 255));
// 直方图归一化
normalize(histB, histB, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
normalize(histG, histG, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
normalize(histR, histR, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
// 在直方图中画出直方图
for (int i = 1; i < nHistSize[0]; i++)
{
line(matHistImage,
Point(nBinWidth * (i - 1), nHistHeight - cvRound(histB.at<float>(i - 1))),
Point(nBinWidth * (i), nHistHeight - cvRound(histB.at<float>(i))),
Scalar(255, 0, 0),
2,
8,
0);
line(matHistImage,
Point(nBinWidth * (i - 1), nHistHeight - cvRound(histG.at<float>(i - 1))),
Point(nBinWidth * (i), nHistHeight - cvRound(histG.at<float>(i))),
Scalar(0, 255, 0),
2,
8,
0);
line(matHistImage,
Point(nBinWidth * (i - 1), nHistHeight - cvRound(histR.at&