OpenCV10-图像直方图:直方图绘制、直方图归一化、直方图比较、直方图均衡化、直方图规定化、直方图反射投影

OpenCV10-图像直方图:直方图绘制、直方图归一化、直方图比较、直方图均衡化、直方图规定化、直方图反射投影


1.直方图的绘制

图像直方图就是统计图像中每个灰度值的个数,之后将灰度值作为横轴,以灰度值个数或者灰度值所占比率作为纵轴的统计图。通过直方图,可以看出图像中哪些灰度值数目较多,哪些较少,可以通过一定的方法将灰度值较为集中的区域映射到较为稀疏的区域,从而使图像在像素灰度值上的分布更加符合期望状态。在通常情况下,像素灰度值代表亮暗程度,因此通过直方图,可以分析图像亮暗对比度,并调整图像的亮暗程度。

在OpenCV中,只提供了图像直方图的统计函数calcHist(),该函数能够统计出图像中每个灰度值的个数,但是,对于直方图的绘制,需要自行进行:

cpp 复制代码
void calcHist(
    const Mat* images,   // 待统计直方图的图像数组,数据类型只能是CV_8U、CV_16U、CV_32F,不同图像的通道数可以不同
    int nimages,         // 输入图像数量
    const int* channels, // 需要统计的通道索引数组,第一个图像的通道索引号从0到images[0].channels()-1,第二个图像的通道索引从image[0].channels()到image[0].channels()+image[1].channels()-1,以此类推
    InputArray mask,      // 可选的操作掩码,如果是空矩阵表示图像中所有位置的像素都计入直方图中
    OutputArray hist,     // 输出的统计直方图结果,时dims维度的数组
    int dims,  // 需要计算直方图的维度,必须是整数,不能大于CV_MAX_DIMS
    const int* histSize,  // 存放每个维度直方图的数组的尺寸
    const float** ranges, // 每个图相同道中灰度值的取值范围
    bool uniform = true,  // 直方图是否均匀的标志
    bool accumulate = false // 是否累积统计直方图的标志,如果累积,那么在统计新图像时,之前的统计结果不会被清除,该参数主要用于统计多个图像整体的直方图。
);

由于该函数具有较多参数,并且每个参数都较为复杂,因此在使用该函数时只统计单通道图像的灰度值分布,对于多个通道图像,可以将图像每个通道分离后再进行统计。

cpp 复制代码
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	//设置提取直方图的相关变量
	Mat hist;  //用于存放直方图计算结果
	const int channels[1] = { 0 };  //通道索引
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };  //像素灰度值范围
	const int bins[1] = { 256 };  //直方图的维度,其实就是像素灰度值的最大值
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);  //计算图像直方图

	cout << "hist.channels(): " << hist.channels() << endl; // 1

	double maxval = 0.0;
	minMaxLoc(hist, 0, &maxval);
	cout << "hist maxval: " << maxval << endl; // 最多的一个灰度值数量有3896

	//准备绘制直方图
	int hist_w = 512;   // 256个灰度值,每个灰度值画成矩形,矩形宽度为2
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width * (i - 1), hist_h - 1),
			// cvRound四舍五入取整,因为灰度值数量太多,所以除以了15
			Point(width * i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 15)),
			Scalar(255, 255, 255), -1);
	}
	namedWindow("histImage", WINDOW_AUTOSIZE);
	imshow("histImage", histImage);
	imshow("gray", gray);

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

由于图像中灰度值像素数目较多,因此将每个灰度值数目缩小为原来的 1 15 \frac{1}{15} 151 后再进行绘制。

2.直方图归一化

前面完成了对一幅图像像素灰度值的统计,并成功绘制了图像的直方图。直方图可以用来表示图像的明亮程度,从理论上讲,通过对同一个图像缩放后得到的两幅尺寸不一样的图像将具有大致相似的直方图分布特性,因此用灰度值的数目作为统计结果具有一定的局限性。

OpenCV提供了normalize函数实现多种形式的归一化功能:

cpp 复制代码
void normalize(
    InputArray src, 
    InputOutputArray dst, 
    double alpha = 1, // 在范围归一化的情况下,归一化到下限边界的标准值
    double beta = 0,  // 在范围归一化时的上限范围,他不用于标准规范化 
    int norm_type = NORM_L2,  // 归一化过程中数据范数种类标志,常用的可选参数如下所示
    int dtype = -1, // 输出数据类型,默认即可 
    InputArray mask = noArray() // 掩码矩阵
);
/*
该函数通过alpha设置将数据缩放到最大范围,通过norm_type参数选择计算范数的种类,之后将输入矩阵中的每个数据分别除以求取得范数数值,最后得到缩放的结果。


norm_type:计算不同的范数,最后的结果也不相同。
NORM_INF: x/max
NORM_L1: x/sum(ary)
NORM_L2: x/sqrt( sum(pow(i,2)) )
NORM_MINMAX: (x-min)/(max-min)
*/

下面通过不同方式归一化数组 [2.0, 8.0, 10.0],并且分别用灰度值所占比例除以数据最大值的方式对图像进行归一化操作。为了更加直观展示直方图归一化后的结果,将每个灰度值所占比例放大了30倍,并将直方图的图像高度作为1进行绘制:

cpp 复制代码
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	//system("color F0");  //更改输出界面颜色
	vector<double> positiveData = { 2.0, 8.0, 10.0 };
	vector<double> normalized_L1, normalized_L2, normalized_Inf, normalized_L2SQR;
	//测试不同归一化方法
	normalize(positiveData, normalized_L1, 1.0, 0.0, NORM_L1);  //绝对值求和归一化
	cout << "normalized_L1=[" << normalized_L1[0] << ", "
		<< normalized_L1[1] << ", " << normalized_L1[2] << "]" << endl;
	normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2);  //模长归一化
	cout << "normalized_L2=[" << normalized_L2[0] << ", "
		<< normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;
	normalize(positiveData, normalized_Inf, 1.0, 0.0, NORM_INF);  //最大值归一化
	cout << "normalized_Inf=[" << normalized_Inf[0] << ", "
		<< normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;
	normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX);  //偏移归一化
	cout << "normalized_MINMAX=[" << normalized_L2SQR[0] << ", "
		<< normalized_L2SQR[1] << ", " << normalized_L2SQR[2] << "]" << endl;
	//将图像直方图归一化
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray, hist;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage_L1 = Mat::zeros(hist_h, hist_w, CV_8UC3);
	Mat histImage_Inf = Mat::zeros(hist_h, hist_w, CV_8UC3);
	Mat hist_L1, hist_Inf;
	normalize(hist, hist_L1, 1, 0, NORM_L1, -1, Mat());
	for (int i = 1; i <= hist_L1.rows; i++)
	{
		rectangle(histImage_L1, Point(width * (i - 1), hist_h - 1),
			Point(width * i - 1, hist_h - cvRound(30 * hist_h * hist_L1.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());
	for (int i = 1; i <= hist_Inf.rows; i++)
	{
		rectangle(histImage_Inf, Point(width * (i - 1), hist_h - 1),
			Point(width * i - 1, hist_h - cvRound(hist_h * hist_Inf.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow("histImage_L1", histImage_L1);
	imshow("histImage_Inf", histImage_Inf);

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}
normalized_L1=[0.1, 0.4, 0.5]
normalized_L2=[0.154303, 0.617213, 0.771517]
normalized_Inf=[0.2, 0.8, 1]
normalized_MINMAX=[0, 0.75, 1]

结果显示,无论是否进行归一化,或者采用那种归一化方法,直方图的分布特性都不会改变。

3.直方图比较

通过两幅图像的直方图特性比较两幅图像的相似程度。从一定程度上来讲,虽然两幅图像的直方图分布相似不代表两幅图像相似,但是两幅图像相似则两幅图像的直方图分布一定相似。例如,在通过差值对图像进行缩放后,虽然图形的直方图不会与之前完全一致,但是两者之间一定具有很高的相似性,因而可以通过比较两幅图像的直方图分布相似性对图像进行初步的筛选与识别。

OpenCV中提供了compareHist函数用于比较两个图像直方图相似性:

cpp 复制代码
double compareHist(
    InputArray H1, 
    InputArray H2, 
    int method
);

// method
enum HistCompMethods {
    HISTCMP_CORREL        = 0, // 相关法
    HISTCMP_CHISQR        = 1, // 卡方法
    HISTCMP_INTERSECT     = 2, // 直方图相交法
    HISTCMP_BHATTACHARYYA = 3, // 巴氏距离法
    HISTCMP_HELLINGER     = HISTCMP_BHATTACHARYYA, // 3
    HISTCMP_CHISQR_ALT    = 4, // 替代卡方法
    HISTCMP_KL_DIV        = 5  // 相对熵法
};

示例程序:

cpp 复制代码
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void drawHist(Mat& hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width * (i - 1), hist_h - 1),
			Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray, hist, gray2, hist2, gray3, hist3;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	resize(gray, gray2, Size(), 0.5, 0.5);
	gray3 = imread("lena.png", IMREAD_GRAYSCALE);
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	// gray:苹果  gray2:苹果缩小  gray3:lena
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	calcHist(&gray2, 1, channels, Mat(), hist2, 1, bins, ranges);
	calcHist(&gray3, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist, NORM_INF, "hist");
	drawHist(hist2, NORM_INF, "hist2");
	drawHist(hist3, NORM_INF, "hist3");
	//原图直方图与原图直方图的相关系数
	double hist_hist = compareHist(hist, hist, HISTCMP_CORREL);
	cout << "apple_apple=" << hist_hist << endl;
	//原图直方图与缩小原图直方图的相关系数
	double hist_hist2 = compareHist(hist, hist2, HISTCMP_CORREL);
	cout << "apple_apple256=" << hist_hist2 << endl;
	//两张不同图像直方图相关系数
	double hist_hist3 = compareHist(hist, hist3, HISTCMP_CORREL);
	cout << "apple_lena=" << hist_hist3 << endl;
	/*
	apple_apple=1
	apple_apple256=0.998067
	apple_lena=0.285314
	*/
	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

4.直方图均衡化

如果一个图像的直方图都集中在一个区域那么整体图像的对比度比较小,不便于图像中纹理识别。如果通过映射关系,将图像中灰度值的范围扩大,增加原来两个灰度值之间的差值,就可以提高图像的对比度,进而将图像中的纹理凸显出来,这个过程称为图像直方图均衡化。

OpenCV中提供了equalizeHist函数用于将图像的直方图均衡化:

cpp 复制代码
void equalizeHist(
    InputArray src, // CV_8UC1
    OutputArray dst
);

该函数只能对单通道的灰度图进行直方图均衡化。

cpp 复制代码
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void drawHist(Mat& hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width * (i - 1), hist_h - 1),
			Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat img = imread("gearwheel.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray, hist, hist2;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat equalImg;
	equalizeHist(gray, equalImg);  //将图像直方图均衡化
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
	drawHist(hist, NORM_INF, "hist");
	drawHist(hist2, NORM_INF, "hist2");
	imshow("原图", gray);
	imshow("均衡化后的图像", equalImg);

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

5.直方图规定化(直方图匹配)

直方图均衡化函数可以自动地改变图像直方图的分布形式,这种方式极大地简化了直方图均衡化过程中需要的操作步骤,但是该函数不能指定均衡化后的直方图分布形式。在某些特定条件下,需要将直方图映射成指定的分布形式,这种将直方图映射成指定分布形式的算法成为直方图规定化或直方图匹配。

直方图规定化与直方图均衡化相似,都是对图像的直方图分布形式进行改变,只是直方图均衡化后的图像是均匀分布的,而直方图规定化后的直方图可以任意指定,即在执行直方图规定化操作时,首先要知道变换后的灰度直方图分布形式,进而确定变换函数,直方图规定化有目的地增强某个灰度区间。

不同图像间像素数目不同,为了使两个图像直方图能够匹配,需要使用概率形式表示每个灰度值在图像像素中所占的比例。在理想状态下,经过图像直方图匹配操作后,图像直方图分布形式应于目标分布一致,因此两者间的累积概率(小于等于某一灰度值的像素数目占所有像素的比例)分布也一致。

用 V s V_s Vs 表示原图像直方图的各个灰度级的累积概率,用 V z V_z Vz 表示匹配后直方图的各个灰度级累积概率,由原图像中灰度值 n n n 映射成 r r r 的条件:
n , r = a r g min ⁡ n , r ∣ V s ( n ) − V z ( n ) ∣ n,r = arg \min_{n,r} |V_s(n) - V_z(n)| n,r=argn,rmin∣Vs(n)−Vz(n)∣

下面说明直方图匹配过程:

运算 步骤和结果
原图像灰度级 0 1 2 3 4 5 6 7
原直方图概率 0.19 0.24 0.2 0.17 0.09 0.05 0.03 0.02
原直方图累积概率 0.19 0.43 0.63 0.8 0.89 0.94 0.97 0.99
目标直方图概率 0 0 0 0.16 0.19 0.29 0.2 0.16
目标直方图累积概率 0 0 0 0.16 0.35 0.64 0.84 1
匹配的灰度值 3 4 5 6 6 7 7 7
映射关系 0->3 1->4 2->5 3->6 4->6 5->7 6->7 7->7

1.计算原直方图概率

2.计算原直方图累积概率

3.计算目标直方图概率

4.计算目标直方图累积概率

5.确定映射:

原直方图灰度值0,累积概率 V s V_s Vs 为0.19,目标直方图累积概率 V z V_z Vz 可以为0.16,0.35...,但 V s V_s Vs 为0.19(灰度级为0)距离 V z V_z Vz 为0.16(灰度级为3)最小,因此有映射关系:0->3

这个寻找灰度值匹配的过程是直方图规定化的关键,在代码实现中可以通过构建元直方图累积概率与目标直方图累积概率之间的差值表,寻找原直方图中灰度值 n n n 的累积概率与目标直方图中所有灰度值累积概率差值的最小值,这个最小值对应的灰度值 r r r 就是 n n n 匹配后的灰度值。

在OpenCV中并没有提供直方图匹配的函数,需要自己根据算法实现图像直方图匹配。下面代码给出了实现直方图匹配的实例,该程序中待匹配的原图是一个整体偏暗的图像,目标直方图分配形式来自于一幅较为明亮的图像,经过图像直方图匹配操作之后,提高了图像的整体亮度,图像直方图的分布更加均匀:

cpp 复制代码
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void drawHist(Mat& hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width * (i - 1), hist_h - 1),
			Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat img1 = imread("histMatch.png");
	Mat img2 = imread("equalLena.png");
	if (img1.empty() || img2.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat hist1, hist2;
	//计算两张图像直方图
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
	calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
	//归一化两张图像的直方图
	drawHist(hist1, NORM_L1, "hist1");
	drawHist(hist2, NORM_L1, "hist2");
	//计算两张图像直方图的累积概率
	float hist1_cdf[256] = { hist1.at<float>(0) };
	float hist2_cdf[256] = { hist2.at<float>(0) };
	for (int i = 1; i < 256; i++)
	{
		hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
		hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);
	}
	//构建累积概率误差矩阵
	float diff_cdf[256][256];
	for (int i = 0; i < 256; i++)
	{
		for (int j = 0; j < 256; j++)
		{
			diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
		}
	}

	//生成LUT映射表
	Mat lut(1, 256, CV_8U);
	for (int i = 0; i < 256; i++)
	{
		// 查找源灰度级为i的映射灰度
		// 和i的累积概率差值最小的规定化灰度
		float min = diff_cdf[i][0];
		int index = 0;
		//寻找累积概率误差矩阵中每一行中的最小值
		for (int j = 1; j < 256; j++)
		{
			if (min > diff_cdf[i][j])
			{
				min = diff_cdf[i][j];
				index = j;
			}
		}
		lut.at<uchar>(i) = (uchar)index;
	}
	Mat result, hist3;
	LUT(img1, lut, result);
	imshow("待匹配图像", img1);
	imshow("匹配的模板图像", img2);
	imshow("直方图匹配结果", result);
	calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist3, NORM_L1, "hist3");  //绘制匹配后的图像直方图

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

6.直方图反向投影

如果一幅图像的某个区域中显示的是一种结构纹理或者一个独特的形状,那么这个区域的直方图就可以看作是这个结构或者形状的概率函数,在图像中寻找这种概率分布就是在图像中寻找该结构纹理或者独特形状。

反向投影(back projection)就是记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法。反射投影就是首先计算某一特征的直方图模型,然后使用该模型去寻找是否存在该特征。

cpp 复制代码
void calcBackProject(
    const Mat* images, 
    int nimages,
    const int* channels, // 前三个参数同calcHist
    InputArray hist,     // 模版图像直方图
    OutputArray backProject,  // 输出结果
    const float** ranges,// 同calcHist
    double scale = 1,    // 输出反向投影矩阵的比例因子
    bool uniform = true  // 同calcHist
);
/*
该函数用于在输入图像中寻找与指定图像最匹配的点或区域,即对图像直方图反向投影,输入参数和calcHist相似。
关注输入图像和模版图像的直方图,返回一幅图像。
*/

下面代码中,首先加载待反向投影图像和模版图像,模版图像从待反向投影图像中截取。之后将两幅图像由RGB颜色空间转成HSV空间,统计H-S通道的直方图,将直方图归一化后绘制H-S通道的二维直方图。最后将待反向投影图像和模版图像输入calcBackProject函数,得到对图像直方图反向投影结果。

cpp 复制代码
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void drawHist(Mat& hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width * (i - 1), hist_h - 1),
			Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat img = imread("apple.jpg");
	Mat sub_img = imread("sub_apple.jpg");
	Mat img_HSV, sub_HSV, hist, hist2;
	if (img.empty() || sub_img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	imshow("img", img);
	imshow("sub_img", sub_img);
	//转成HSV空间,提取S、V两个通道
	cvtColor(img, img_HSV, COLOR_BGR2HSV);
	cvtColor(sub_img, sub_HSV, COLOR_BGR2HSV);
	int h_bins = 32; int s_bins = 32;
	int histSize[] = { h_bins, s_bins };
	//H通道值的范围由0到179
	float h_ranges[] = { 0, 180 };
	//S通道值的范围由0到255
	float s_ranges[] = { 0, 256 };
	const float* ranges[] = { h_ranges, s_ranges };  //每个通道的范围
	int channels[] = { 0, 1 };  //统计的通道索引
	//绘制H-S二维直方图
	calcHist(&sub_HSV, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
	drawHist(hist, NORM_INF, "hist");  //直方图归一化并绘制直方图
	Mat backproj;
	calcBackProject(&img_HSV, 1, channels, hist, backproj, ranges, 1.0);  //直方图反向投影
	imshow("反向投影后结果", backproj);
	cout << "backproj.size(): " << backproj.size() << endl;

	cv::Mat imageWithMask(img.size(), img.type());
	img.copyTo(imageWithMask, backproj);
	imshow("使用反向投影掩码后结果", imageWithMask);

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

反向投影图像给出了输入图像中每个像素与目标模型的相似程度。具体来说,反向投影图像的每个像素值表示该像素在目标模型中的可能性或置信度。像素值越高,表示该像素更可能属于目标模型。上面backproj图形是0-1二值图,直接显示看到的将是黑色,当做掩膜可以看到效果。

相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
AI极客菌2 小时前
Controlnet作者新作IC-light V2:基于FLUX训练,支持处理风格化图像,细节远高于SD1.5。
人工智能·计算机视觉·ai作画·stable diffusion·aigc·flux·人工智能作画
阿_旭2 小时前
一文读懂| 自注意力与交叉注意力机制在计算机视觉中作用与基本原理
人工智能·深度学习·计算机视觉·cross-attention·self-attention
王哈哈^_^2 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
UestcXiye2 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
霁月风4 小时前
设计模式——适配器模式
c++·适配器模式
jrrz08284 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i4 小时前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波1074 小时前
Webserver(4.9)本地套接字的通信
c++