详解什么叫二维直方图,并利用OpenCV的函数calcHist()绘制图像的H-S二维直方图

在阅读这篇博文前,请大家先阅读一下我的上一篇博文,链接如下:

对OpenCV的函数calcHist()进行透彻解析,并利用它实例绘制一个一维灰度直方图

图像的二维直方图是图像在两个维度上的直方图联合统计结果。

上面这句话有点抽象,举个简单的例子,大家就能清晰明了的理解了。

设图像A有两个通道,其两个通道的数据分别如下:

使用calcHist()计算两个通道的二维直方图的参数设置如下:

int channels[] = { 0, 1};
int histSize[] = { 2, 3};
float Ranges_1[] = { 0, 4};
float Ranges_2[] = { 0, 6};
const float *ranges[] = {Ranges_1, Ranges_2};

由于两个通道的histSize值分别为2和3,则最终计算得到的二维直方图数据,即calcHist中的输出参数“hist”将是一个二维的矩阵,并且其行数为2,列数为3,总共2×3=6个元素。

下面我们分别来说这6个元素的含义并求这6个元素的值,明白了这6个元素的含义和值的求法,便知道了二维直方图的含义。

首先我们要知道对于第1个通道而言,其histSize的值为2,ranges为0~4,则0~4被划分为两个区间,

第一个区间为[0,2) ,第二个区间为[2,4) ,注意区间为左闭右开区间

对于第二个通道而言,其histSize的值为3,ranges为0~6,则0~6被划分为三个区间,

第一个区间为[0,2) ,第二个区间为[2,4) ,第三个区间为[4,6),注意区间为左闭右开区间

我们设his这个二维矩阵的左上角为坐标原点,则其各元素的含义和值如下:

(0,0) 表示同时满足以下两个条件的像素值个数:
第1通道的灰度值在区间[0,2)内且第2通道的灰度值在区间[0,2)内。结合两个通道的具体数值,我们可知其值为0

(0,1) 表示同时满足以下两个条件的像素值个数:
第1通道的灰度值在区间[0,2)内且第2通道的灰度值在区间[2,4)内。结合两个通道的具体数值,我们可知其值为3

(0,2) 表示同时满足以下两个条件的像素值个数:
第1通道的灰度值在区间[0,2)内且第2通道的灰度值在区间[4,6)内。结合两个通道的具体数值,我们可知其值为0

(1,0) 表示同时满足以下两个条件的像素值个数:
第1通道的灰度值在区间[2,4)内且第2通道的灰度值在区间[0,2)内。结合两个通道的具体数值,我们可知其值为0

(1,1) 表示同时满足以下两个条件的像素值个数:
第1通道的灰度值在区间[2,4)内且第2通道的灰度值在区间[2,4)内。结合两个通道的具体数值,我们可知其值为3

(1,2) 表示同时满足以下两个条件的像素值个数:
第1通道的灰度值在区间[2,4)内且第2通道的灰度值在区间[4,6)内。结合两个通道的具体数值,我们可知其值为0

所以这个hist二维矩阵的内容应该如下:

\large \begin{pmatrix} 0&3 &0 \\ 0&3 &0 \end{pmatrix}

我们用程序来实际验证一下看下是不是这个结果:

代码如下:

//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理需求也可联系博主
//图像处理技术交流QQ群 271891601

//OpenCV版本:3.0
//VS版本:2012

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>

using namespace cv;

using namespace std;

int main()
{
	Mat Image1( 2, 3, CV_8UC2, cv::Scalar(1,2) );

	for (int j = 0; j < Image1.cols; j++)
	{
		Image1.at<Vec2b>(1, j)[0] = 2;
	}

	for (int j = 0; j < Image1.cols; j++)
	{
		Image1.at<Vec2b>(0, j)[1] = 3;
	}

	int channels[] = { 0, 1};
	int histSize[] = { 2, 3};
	float Ranges_1[] = { 0, 4};
	float Ranges_2[] = { 0, 6};
	const float *ranges[] = {Ranges_1, Ranges_2};

	Mat dstHist;

	 calcHist(&Image1, 1, channels, Mat(), dstHist, 2, histSize, ranges, true, false);

	cout<<dstHist<<endl<<endl;

	return 0;
}

运行结果如下:

 可见,和我们手工计算出的结果是一样的。

至此,大家就应该知道二维直方图的含义了。

接下来我们利用OpenCV的函数calcHist()绘制图像的H-S二维直方图

对于彩色图像,我们通常需要同时考虑图像的色调(hue)和饱和度(saturation),所以有时候我们需要计算图像的“色调-饱和度二维直方图”,即H-S直方图。

下面给出利用OpenCV的函数calcHist()绘制彩色图像H-S直方图的示例代码。

代码中用到的图像下载链接:

链接:https://pan.baidu.com/s/1GQrl1ATIdarzXm3EdLJRzg?pwd=avsa 

//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理需求也可联系博主
//图像处理技术交流QQ群 271891601

//OpenCV版本:3.0
//VS版本:2012

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
 
#include <iostream>
 
using namespace cv;
using namespace std;
 
int main()
{
        Mat srcImage = imread("F:/material/images/P0005-BaoXiaofeng.jpg");
        imshow("【原图】", srcImage);
 
        Mat hsvImage;

        //因为要计算H-S的直方图,所以需要先进行颜色空间的转换
        cvtColor(srcImage, hsvImage, CV_BGR2HSV);

 
        //计算H-S二维直方图的参数配置
        int channels[] = { 0, 1 };
        Mat dstHist;
        int histSize[] = { 30, 32 };
        float HRanges[] = { 0, 180 };
        float SRanges[] = { 0, 256 };
        const float *ranges[] = { HRanges, SRanges };
 
        //参数配置好后,就可以调用calcHis函数计算直方图数据了
        calcHist(&hsvImage, 1, channels, Mat(), dstHist, 2, histSize, ranges, true, false);
 
        //calcHist函数调用结束后,dstHist变量中将储存直方图数据 
 
        ///接下来绘制直方图
        //首先先创建一个黑底的图像,为了可以显示彩色,所以该绘制图像是一个8位的3通道图像  
        Mat drawImage = Mat::zeros(Size(300, 320), CV_8UC3);
 
        //因为数据矩阵中的某个值的总个数可能会超出所定义的图像的尺寸,所以要对个数进行归一化处理  
        //先用 minMaxLoc函数来得到计算直方图中的最大数据,这个函数的使用方法大家一看函数原型便知
        double g_dHistMaxValue;
        minMaxLoc(dstHist, 0, &g_dHistMaxValue, 0, 0);
 
        //遍历直方图数据,对数据进行归一化和绘图处理 
        for (int i = 0; i < 30; i++)
        {
                for (int j = 0; j < 32; j++)
                {
                        int value = cvRound(dstHist.at<float>(i, j) * 255/ g_dHistMaxValue);
 
                        rectangle(drawImage, Point(10 * i, j * 10), Point((i + 1) * 10 - 1, (j + 1) * 10 - 1), Scalar(0,0,value), -1);
                }
        }
 
        imshow("【H-S二维联合直方图】", drawImage);
 
        waitKey(0);
 
        return 0;
}

上面的代码比较难理解的部分是:根据计算出的二维直方图数据绘制直方图的两句代码,即下面两句:

int value = cvRound(dstHist.at<float>(i, j) * 255/ g_dHistMaxValue);
rectangle(drawImage, Point(10 * i, j * 10), Point((i + 1) * 10 - 1, (j + 1) * 10 - 1), Scalar(0,0,value), -1);

这两句代码要补充说几句。

1 这两句代码把hist里的数据归一化到0~255。

2 这两句代码把二维直方图进行了按比例放大绘制,本来hist的大小是30×32的,但在绘制的时候我们把其放大到了300×320,这是为了便于我们观察。

3 我们是用三通道的彩色图绘制二维直方图的,这样做也是便于我们肉眼观察,每一块矩形我们都用不同程度的红色来绘制,其红色的程度值就是hist里对应点进行归一化后的值。

上面代码的运行结果如下图所示:

猜你喜欢

转载自blog.csdn.net/wenhao_ir/article/details/51190531#comments_22830006