【Opencv入门】RGB三通道直方图的计算与绘制


一、 直方图概述 Overview of histogram

直方图(Histogram),又称质量分布图,是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。
直方图是图像处理中的一个有着广泛应用的工具,直方图本质是概率分布的图形化,同时直方图也可以用来表示向量。

二、直方图的建立 Establishment of histogram

下面以一幅分辨率为8*8、8级灰度的图像来介绍如何建立一幅直方图

在这里插入图片描述
以Pi(i = 0、1、2、3…7)表示八级灰度每一级的概率,Ni(i = 0、1、2、3…7)表示88共64个像素点中各级灰度出现的次数,则Pi = Ni/(88),即灰度出现的次数对像素点总数的归一化。
由此绘制出的Pi-灰度的关系图就是灰度直方图。横坐标为灰度值,纵坐标为该灰度值出现的概率。
当然一般是以8位表示一个像素点,灰度一般有256级。
在这里插入图片描述
从这张直方图上可以很直观地看到,它的灰度值以0、1、2为主,显然是一张偏暗的图片。

对于图像,不光可以建立灰度直方图,也可以自己设计方法建立任意形式的直方图。如可以对RGB图像三个颜色通道分别建立直方图,得到彩色直方图。也可以针对图像的HSV色彩模型,建立HSC直方图。
两个直方图的比较可以使用欧几里德距离,马氏距离等。Opencv中的compareHist函数提供了四种方法:Correlation、Chi-Square,Intersection,Bhattacharyya距离。
可以想象,如果两个直方图的欧几里德距离为零的话,那么就应该是两个完全一样的直方图。

三、直方图的作用 The function of histogram

1)图像匹配

比较两幅图像的直方图,可以得到两幅图像的相似程度。其本质是对比灰度出现的概率是否相似。
在图像匹配的时候,也可以对其他的特征量建立直方图,来进行匹配。
同样的图像直方图显然相同。反之却不一定成立,这是因为直方图只能记录各级灰度出现的概率,却丢失了空间信息。
如下图就是一个明显的反例:
在这里插入图片描述
那么为何要对灰度进行归一化呢,假设我们有一张原图和一张对原图进行放大缩小后的图片,两种图片实际上是同一张图片,只是大小不同,它们的直方图理应是相同的。但是如果不仅归一化,仅仅是以各级灰度出现的次数作为纵坐标,由于放大缩小后的图像与原图像素点存在差异,它呈现出来的纵坐标也是不同的,这就导致了两张相同的图片直方图不同,因此要进行归一化。

2)判断成像质量

下面以几张图片作为示例:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3)二值化阈值

在图像二值化的过程中,我们可以通过分析直方图选择一个适当的阈值,把灰度图像转换为二值化图像,通常的目的是分离前景和背景
在这里插入图片描述
以一张256级灰度的细胞的图片为例,下面是它的直方图
在这里插入图片描述
可以明显地看到,它的直方图呈现双峰性,它的前景分布集中在一个区域,背景分布集中在另一个区域,这时候我们只要选择直方图中的谷值,就能得到较为理想的二值化图像。
在这里插入图片描述

四、编程实现 Programming implementation

通过Opencv编程实现读取一张图片计算并显示RGB三通道直方图。
这里用的Opencv版本是4.40。
当然这里可以用Opencv自带的函数calcHist()函数计算直方图,自己写一个算直方图的函数只是为了能够加深对直方图和OpencvMat类的理解。

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
#define hist_rgb_width  800
#define hist_rgb_height 400

void Calc_histogram(cv::Mat src)
{
	/*定义直方图矩阵
	 *这里以一个256*3的矩阵存放各个通道各个灰度值的个数
	 *以数组下标标记灰度值,数组内容为该灰度值的个数
	 */
	float histogram[256][3] = { 0 };
	//定义vector作为三个通道的容器
	std::vector<cv::Mat>	channels;
	//分离三个通道像素值
	cv::split(src, channels);
	cv::Mat B = channels.at(0);
	cv::Mat G = channels.at(1);
	cv::Mat R = channels.at(2);
	//*****遍历*****
	//获取向量长度
	int height = src.rows;
	int width  = src.cols;
	long int size = height * width;
	for (int j = 0; j < height; ++j)
	{
		for (int i = 0; i < width; ++i)
		{
			++histogram[B.at<uchar>(j, i)][0];
			++histogram[G.at<uchar>(j, i)][1];
			++histogram[R.at<uchar>(j, i)][2];
		}
	}
	//*****归一化*****
	/* 由于直方图归一化以后的纵坐标小于1,需要放大以后显示才直观
	 * 而放大多少倍才能使现有的画布容纳所有放大后的线段
	 * 这里用三个变量记录归一化后最大的值,将最大值放大为画布高度400
	 * 最大值能容纳,其他的值也自然能容纳了
	 */
	float BMax = 0;
	float GMax = 0;
	float RMax = 0;
	for (int i = 0; i < 256; ++i)
	{
		histogram[i][0] = histogram[i][0] / size;
		histogram[i][1] = histogram[i][1] / size;
		histogram[i][2] = histogram[i][2] / size;

		if (histogram[i][0] > BMax)
			BMax = histogram[i][0];
		if (histogram[i][1] > GMax)
			GMax = histogram[i][1];
		if (histogram[i][2] > RMax)
			RMax = histogram[i][2];
	}
	cv::Mat dispMat(hist_rgb_height, hist_rgb_width, CV_8UC3, Scalar(0, 0, 0));
	cv::Point pt1, pt2;
	pt1.y = 400;
	pt1.x = 0;
	pt2.x = 0;

	int Coeficient = 0;
	//绘制R通道直方图
	Coeficient = (int)(400 / RMax);
	for (int i = 0; i < 256; ++i)
	{
		pt2.y = pt1.y - histogram[i][2] * Coeficient;
		cv::line(dispMat, pt1, pt2, Scalar(0, 0, 255), 1, 8, 0);
		pt2.x = ++pt1.x;
	}
	//绘制G通道直方图
	Coeficient = (int)(400 / GMax);
	for (int i = 0; i < 256; ++i)
	{
		pt2.y = pt1.y - histogram[i][1] * Coeficient;
		cv::line(dispMat, pt1, pt2, Scalar(0, 255, 0), 1, 8, 0);
		pt2.x = ++pt1.x;
	}
	//绘制B通道直方图
	Coeficient = (int)(400 / BMax);
	for (int i = 0; i < 256; ++i)
	{
		pt2.y = pt1.y - histogram[i][0] * Coeficient;
		cv::line(dispMat, pt1, pt2, Scalar(255, 0, 0), 1, 8, 0);
		pt2.x = ++pt1.x;
	}
	imshow("Histogram", dispMat);
}
int main()
{
	/*读入要计算直方图的图片
	 *注意这里要用“\\”
	 */
	cv::Mat src = imread("C:\\Users\\STAR ZHANG\\Pictures\\3.jpg");
	//调用函数
	Calc_histogram(src);
	/*WaitKey(n)是Opencv自带的函数
	 *其中n表示等待按键n毫秒后,关闭显示的窗口
	 *n为0或者WaitKey()(形参缺省)表示一直等待按键
	*/
	waitKey(0);
	return 0;
}

运行结果:
原图:
原图
分离出的RGB三通道直方图:
在这里插入图片描述

总结

直方图在数字图像处理中一个非常重要的概念,具有广泛的应用。 以上作为课堂学习和课外练习的总结整理,部分资料出自老师的课件。 我永远热爱hdms lizhu老师!
  • 10
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
绘制单通直方图,可以使用OpenCV中的calcHist函数来计算单通像的直方图,然后使用OpenCV中的plot函数将其绘制出来。 以下是一个简单的示例代码: ``` #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main() { Mat image = imread("test.jpg", 0); // 读取灰度像 int histSize = 256; // 直方图横轴(灰度级)的数量 float range[] = { 0, 256 }; // 像素值范围 const float* histRange = { range }; bool uniform = true, accumulate = false; Mat hist; // 存储直方图 calcHist(&image, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate); 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)); 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("Histogram", histImage); waitKey(); return 0; } ``` 在这个示例代码中,我们首先读取了一张灰度像,然后使用calcHist函数计算了该像的直方图。接下来,我们创建了一个512x400像素大小的空白像,用于绘制直方图。然后,我们将直方图归一化到像高度的范围内,并使用line函数绘制直方图。最后,我们显示了绘制出的直方图,并等待用户按下任意键后退出程序。 注意,这个示例代码只能绘制单通灰度像的直方图。如果要绘制多通像的直方图,需要对每个通分别计算直方图,并将它们绘制在同一个像上。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值