• opencv图像的直方图,二维直方图,直方图均衡化


    opencv图像的直方图,二维直方图,直方图均衡化

    一、图像的直方图

    1、什么是图像的直方图:
    • 要理解直方图,绕不开“亮度”这个概念,人们把亮度分为0到255共256个数值,数值越大,代表的亮度越高,其中0代表纯黑色,最暗区域,255表示最亮,纯白色,而中间的数字就是不同亮度的灰色;
    • 图像的直方图是一种统计图,它显示了图像中各个灰度级别的分布情况(也就是统计一幅图某个亮度像素的数量),通常它的横轴代表像素的灰度级别(从0到255),而纵轴代表该灰度级别在图像中出现的频次或概率;
    2、直方图的作用:
    • 可视化图像亮度分布:直方图能够帮助我们直观地了解图像的亮度分布情况,从而为图像处理提供重要参考;
    • 对比度调整:通过观察直方图,我们可以确定图像的对比度是否合适,从而决定是否需要进行对比度调整;
    • 灰度级别选择:直方图可以帮助我们选择合适的灰度级别,以使图像的细节更加清晰;
    3、如何绘制图像的直方图:

    在opencv中,可以使用 cv::calcHist()函数来计算图像的直方图,这个函数可以接受一个通道的图像(灰度图像)或多个通道的图像(彩色图像);

    (1)cv::calcHist()函数原型:英文单词 calculator histogram
    void cv::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:输入图像,可以是单通道或多通道图像;
    nimages:输入图像的个数(可以输入多张图像);
    channels:需要统计直方图的第几通道,比如{0}表示统计第一个通道,{0, 1, 2}表示统计所有三个通道;
    mask:掩膜,用于指定计算直方图的区域(必须是一个8(CV_8U)的数组并且和images的数组大小相同);
    hist:输出的直方图数组,calcHist函数只是计算直方图的数据,直方图数据需要一个cv::Mat类型的变量来存储;
    dims:输出直方图的维度(
    	由channels值决定dims的数值,比如int channels[] = { 0 }则dims=1int channels[] = { 0, 1 }则dims=2;
    	对于灰度图像dims为1,因为我们只考虑了一个通道(亮度通道);
    	对于彩色图像,通常会考虑多个通道,比如在HSV色彩空间中,dims为2(H和S两个通道) 
    );
    histSize:指的是直方图横坐标分成多少个区间,就是bin的个数(用于控制直方图的精细度);
    把直方图横坐标ranges分成histSize个区间(比如ranges=180,histSize=30,则横坐标被分成了30个小竖条,每个小竖条的长度为6);
    ranges:横轴代表像素的灰度级别,ranges相当于横坐标的取值范围(
    	对于灰度图像,单通道只有1个range,灰度级别的范围从0255(
    		int bins = 256;               		// X轴被分成了256个小区间
    		int histSize[] = { bins };   		// histSize只有1个bins
    		float xRanges[] = { 0, 256 }; 		// X轴的取值范围
    		const float* ranges[] = { xRanges };    // ranges只有1个
    	);
    	对于彩色图像有多个通道就有多个range,对于每个通道,ranges指定了取值范围,通常在彩色图像中,H(色相)的范围是0180S(饱和度)V(明度)的范围是0255(
    		// H通道X轴被分成了30个小区间,S通道X轴被分成了32个小区间
    		int hueBins = 30, satBins = 32;	  
    		int histSize[] = { hueBins, satBins };			// histSize有2个bins
    		float hueRanges[] = { 0, 180 };				// H通道X轴的取值范围
    		float satRanges[] = { 0, 256 };				// S通道X轴的取值范围
    		const float* ranges[] = { hueRanges, satRanges };    	// ranges有2个
    	));
    uniform:是否对得到的直方图数组进行归一化处理;
    accumulate:在多个图像时,是否累积计算像素值的个数;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    (2)代码示例:
    cv::cvRound()函数:将浮点数值四舍五入为最接近的整数;
    // 函数在图像处理过程中经常用于处理像素值,特别是当需要将浮点数转换为整数时,可以保留最接近的整数值
    int cvRound(double value)
    
    参数解释:
    value:待四舍五入的浮点数;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    灰度图像直方图:
    #include 
    #include 
    #include 
    
    using namespace cv;
    using namespace std;
    
    int main() {
    
    	// 读取灰度图像
    	cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg", cv::IMREAD_GRAYSCALE);
    
    	if (image.empty()) {
    		std::cerr << "Error: 无法读取图像文件." << std::endl;
    		return -1;
    	}
    
    	// 直方图计算的输出值,是一个cv::Mat对象
    	cv::Mat hist;
    	// X轴被分成了256个小区间
    	int bins = 256;
    	// 灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
    	int histSize[] = { bins };
    	// X轴的取值范围
    	float xRanges[] = { 0, 256 };
    	// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
    	// const float* ranges = xRanges;
    	const float* ranges[] = { xRanges };
    	// 我们要计算灰度图像的第0个通道,所以这里channels定义成{0}
    	int channels[] = { 0 };
    	calcHist(&image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
    
    	// 待绘制的目标图像,将直方图计算的输出值hist,绘制到histImage图像上
    	int histWidth = 512, histHeight = 400;
    	int binWidth = cvRound( (double)histWidth/bins );
    	cv::Mat histImage(histHeight, histWidth, CV_8UC3, cv::Scalar(0, 0, 0));
    	// 使用normalize()函数将直方图的值缩放到图像的高度范围内
    	normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
    	// 使用cv::line()函数绘制直线
    	for (int i = 1; i<bins; i++) {
    		cv::line(
    			histImage,
    			Point(binWidth*(i - 1), histHeight - cvRound(hist.at<float>(i - 1))),
    			Point(binWidth*(i), histHeight - cvRound(hist.at<float>(i))),
    			Scalar(255, 0, 0),
    			2,
    			LINE_8,
    			0
    		);
    	}
    
    	// 显示图像和直方图
    	cv::imshow("Image", image);
    	cv::imshow("Histogram", histImage);
    
    	waitKey();
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    运行结果:
    在这里插入图片描述

    彩色图像直方图:

    对于彩色图像,你可以分别计算其颜色通道(蓝色、绿色、红色)的直方图,或者将其转换为灰度图像后计算整体的亮度直方图;

    在这个示例中,我们首先读取了一个彩色图像,然后使用 cv::split() 函数将图像分离成蓝色、绿色和红色通道,接着,我们分别计算了每个通道的直方图,最后我们绘制了各个通道的直方图并显示了原始图像和直方图;

    #include 
    #include 
    #include 
    
    using namespace cv;
    using namespace std;
    
    int main() {
    
    	// 读取彩色图像
    	cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg");
    
    	if (image.empty()) {
    		std::cerr << "Error: 无法读取图像文件." << std::endl;
    		return -1;
    	}
    
    	// 分离通道
    	std::vector<cv::Mat> bgrPlanes;
    	cv::split(image, bgrPlanes);
    
    	// 直方图计算的输出值,是一个cv::Mat对象
    	cv::Mat bHist, gHist, rHist;
    	int bins = 256;	// B,G,R通道,X轴都被分成了256个小区间
    
    	// B通道直方图计算
    	// B通道,灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
    	int bHistSize[] = { bins };
    	// B通道,X轴的取值范围,亮度级别
    	float bXRanges[] = { 0, 256 };
    	// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
    	// const float* ranges = bXRanges;
    	const float* bRanges[] = { bXRanges };
    	// 图像通道被分离后,B通道图像只有一个通道,第0个通道,所以这里channels定义成{0}
    	int bChannels[] = { 0 };
    	calcHist(&bgrPlanes[0], 1, bChannels, Mat(), bHist, 1, bHistSize, bRanges, true, false);
    
    	// G通道直方图计算
    	// G通道,灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
    	int gHistSize[] = { bins };
    	// G通道,X轴的取值范围,亮度级别
    	float gXRanges[] = { 0, 256 };
    	// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
    	// const float* ranges = gXRanges;
    	const float* gRanges[] = { gXRanges };
    	// 图像通道被分离后,G通道图像只有一个通道,第0个通道,所以这里channels定义成{0}
    	int gChannels[] = { 0 };
    	calcHist(&bgrPlanes[1], 1, gChannels, Mat(), gHist, 1, gHistSize, gRanges, true, false);
    
    	// R通道直方图计算
    	// R通道,灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
    	int rHistSize[] = { bins };
    	// R通道,X轴的取值范围,亮度级别
    	float rXRanges[] = { 0, 256 };
    	// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
    	// const float* ranges = rXRanges;
    	const float* rRanges[] = { rXRanges };
    	// 图像通道被分离后,R通道图像只有一个通道,第0个通道,所以这里channels定义成{0}
    	int rChannels[] = { 0 };
    	calcHist(&bgrPlanes[2], 1, rChannels, Mat(), rHist, 1, rHistSize, rRanges, true, false);
    
    	// 绘制直方图
    	int histWidth = 512, histHeight = 400;
    	int binWidth = cvRound( (double)histWidth/bins );
    	cv::Mat histImage(histHeight, histWidth, CV_8UC3, cv::Scalar(0, 0, 0));
    	normalize(bHist, bHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
    	normalize(gHist, gHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
    	normalize(rHist, rHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
    
    	for (int i = 1; i<bins; i++) {
    		// 绘制蓝色分量直方图
    		cv::line(
    			histImage,
    			cv::Point( binWidth*(i-1), histHeight-cvRound( bHist.at<float>(i-1) ) ),
    			cv::Point( binWidth*(i), histHeight-cvRound( bHist.at<float>(i) ) ),
    			cv::Scalar(255, 0, 0),
    			2,
    			LINE_8,
    			0
    		);
    		// 绘制绿色分量直方图
    		cv::line(
    			histImage,
    			cv::Point( binWidth*(i - 1), histHeight-cvRound( gHist.at<float>(i-1) ) ),
    			cv::Point( binWidth*(i), histHeight-cvRound( gHist.at<float>(i) ) ),
    			cv::Scalar(0, 255, 0),
    			2,
    			LINE_8,
    			0
    		);
    		// 绘制红色分量直方图
    		cv::line(
    			histImage,
    			cv::Point( binWidth*(i - 1), histHeight-cvRound( rHist.at<float>(i-1) ) ),
    			cv::Point( binWidth*(i), histHeight-cvRound( rHist.at<float>(i) ) ),
    			cv::Scalar(0, 0, 255),
    			2,
    			LINE_8,
    			0
    		);
    	}
    
    	// 显示图像和直方图
    	cv::imshow("Image", image);
    	cv::imshow("Histogram", histImage);
    
    	cv::waitKey(0);
    	return 0;
    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    运行结果:
    在这里插入图片描述

    二、二维直方图

    1、什么是二维直方图:

    二维直方图是对彩色图像进行分析时的一个重要工具,与一维直方图不同,它同时考虑了两个通道的信息,通常是颜色空间中的两个分量,例如在HSV色彩空间中的H(色相)和S(饱和度);

    2、二维直方图与直方图有哪些不同:
    • 维度:直方图是一维的,它只考虑了图像的亮度或色彩信息;而二维直方图考虑了两个通道的信息,因此是二维的;
    • 通道:直方图通常只针对一个通道(灰度图只有亮度通道);而二维直方图可以同时考虑多个通道,通常是颜色空间中的两个分量;
    3、二维直方图的作用:
    • 颜色分布分析:通过二维直方图,我们可以了解图像中各个颜色组合的分布情况,有助于理解图像的颜色特性;
    • 图像分割与对象识别:在图像分割和对象识别任务中,二维直方图可用于将图像的颜色特征映射到特定的空间,从而实现对图像中的对象进行定位和识别;
    • 颜色调整与匹配:通过分析二维直方图,我们可以进行颜色调整,使图像的颜色分布更加符合预期;
    4、如何绘制二维直方图:

    首先读取了一个彩色图像,然后将其从BGR颜色空间转换为HSV颜色空间,接着我们分离了H和S通道,并指定了直方图的维度、通道数、亮度级别个数和范围,然后我们使用 cv::calcHist()计算了二维直方图并使用 cv::rectangle()绘制;

    #include 
    #include 
    
    using namespace cv;
    using namespace std;
    
    int main() {
    
    	// 读取彩色图像
    	cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg");
    
    	if (image.empty()) {
    		std::cerr << "Error: 无法读取图像文件." << std::endl;
    		return -1;
    	}
    
    	// 2D 直方图
    	Mat hsv, hist;
    	cvtColor(image, hsv, COLOR_BGR2HSV);
    	// H通道X轴被分成了30个小区间,S通道X轴被分成了32个小区间
    	int hueBins = 30, satBins = 32;
    	// 彩色图像有多个ranges,所以histSize跟ranges对应也要定义多个,定义成数组的形式
    	int histSize[] = { hueBins, satBins };
    	float hueRanges[] = { 0, 180 };	// H通道X轴的取值范围
    	float satRanges[] = { 0, 256 };	// S通道X轴的取值范围
    	// 彩色图像有多个ranges,定义成数组的形式
    	const float* ranges[] = { hueRanges, satRanges };
    	// 我们要计算HSV图像的第0个通道和第1个通道的直方图,所以这里channels也要定义多个
    	int channels[] = { 0, 1 };
    	calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
    
    	// 画出计算后的直方图
    	double maxVal = 0;
    	minMaxLoc(hist, 0, &maxVal, 0, 0);
    	int scale = 10;
    	Mat histImage = Mat::zeros(satBins*scale, hueBins*scale, CV_8UC3);
    	for (int h=0; h<hueBins; h++) {
    		for (int s=0; s<satBins; s++){
    			float binVal = hist.at<float>(h, s);
    			int intensity = cvRound( binVal*255 / maxVal );
    			rectangle(
    				histImage, 
    				Point( h*scale, s*scale ),
    				Point( (h+1)*scale - 1, (s+1)*scale - 1 ),
    				Scalar::all(intensity),
    				-1
    			);
    		}
    	}
    	applyColorMap(histImage, histImage, COLORMAP_JET);
    
    	imshow("Image", image);
    	imshow("H-S Histogram", histImage);
    
    	cv::waitKey(0);
    	return 0;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    运行结果:
    在这里插入图片描述

    三、直方图均衡化

    直方图均衡化是一种用于增强图像对比度的图像处理技术,它通过重新分配图像的像素值,使得整个亮度范围得到充分利用,从而使图像看起来更清晰和具有更好的对比度;图像直方图均衡化可以用于图像增强,对输入图像进行直方图均衡化处理,提升后续对象检测的准确率在opencv人睑检测的代码演示中已经很常见,此外对医学影像图像与卫星遥感图像也经常通过直方图均衡化来提升图像质量;

    1、直方图均衡化的原理:
    • 计算直方图:首先计算原始图像的灰度直方图,这个直方图描述了图像中各个灰度级别的分布情况;
    • 计算累积分布函数(CDF):将灰度直方图转换为累积分布函数,CDF表示了每个灰度级别的累积概率;
    • 映射新的像素值:对于每个像素,将其原始灰度值映射到新的值,使得新的值在整个亮度范围内均匀分布;
    2、opencv中,可以使用 cv::equalizeHist()函数来实现直方图均衡化:
    (1)cv::equalizeHist()函数原型:
    void cv::equalizeHist(
    	InputArray src, 
    	OutputArray dst
    );
    
    参数解释:
    src:输入图像(灰度图像);
    dst:输出图像,用于存储均衡化后的结果;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    (2)代码示例:
    灰度直方图均衡化:
    #include 
    #include 
    
    using namespace cv;
    using namespace std;
    
    int main() {
    
    	// 读取灰度图像
    	cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg", cv::IMREAD_GRAYSCALE);
    
    	if (image.empty()) {
    		std::cerr << "Error: 无法读取图像文件." << std::endl;
    		return -1;
    	}
    
    	// 均衡化灰度直方图
    	cv::Mat equalizedImage;
    	cv::equalizeHist(image, equalizedImage);
    
    	 cv::imshow("Original Gray Image", grayImage);
      	 cv::imshow("Equalized Gray Image", equalizedImage);
    
    	cv::waitKey(0);
    	return 0;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行结果:
    在这里插入图片描述

    彩色直方图均衡化:

    cv::equalizeHist()函数只支持单通道图像直方图的均衡化,所以我们要对彩色图像直方图做均衡化,首先将彩色图像转换为HSV色彩空间,再将图像HSV通道分离,因为equalizeHist()函数均衡化的是图像的亮度,所以我们只需要对亮度通道(V通道)进行均衡化,然后在合并通道得到合并后的HSV图像,最后将其转换回BGR色彩空间以显示;

    #include 
    #include 
    
    using namespace cv;
    using namespace std;
    
    int main() {
    
    	// 读取彩色图像
    	cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg", cv::IMREAD_COLOR);
    
    	if (image.empty()) {
    		std::cerr << "Error: 无法读取图像文件." << std::endl;
    		return -1;
    	}
    
    	// 将图像从BGR色彩空间转换为HSV色彩空间
    	cv::Mat hsvImage;
    	cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV);
    
    	// 均衡化HSV通道的直方图
    	std::vector<cv::Mat> channels;
    	cv::split(hsvImage, channels);
    
    	cv::equalizeHist(channels[2], channels[2]);
    
    	cv::Mat equalizedHSV;
    	cv::merge(channels, equalizedHSV);
    
    	cv::cvtColor(equalizedHSV, equalizedHSV, cv::COLOR_HSV2BGR);
    
    	cv::imshow("Original Color Image", image);
    	cv::imshow("Equalized Color Image", equalizedHSV);
    
    	cv::waitKey(0);
    	return 0;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    运行结果:
    在这里插入图片描述

  • 相关阅读:
    如何利用CodeQL挖掘CVE-2020-10199
    索引使用与注意事项
    css取消移动端长按元素背景色
    线性代数(七) 矩阵分析
    p15~p22基本链表容器和高级链表容器迭代器
    Python学习 day01(注意事项)
    网页设计有用的资源 (持续更新)
    【继承练习题--多态-- 动态绑定-- 重写】
    持续性能优化:确保应用保持高性能
    [java]java读取excel
  • 原文地址:https://blog.csdn.net/qq_33867131/article/details/133683713