您现在的位置是:首页 >其他 >Opencv C++图像处理:点多边形测试 + 矩 + 凸包 + 映射 + 反向投影网站首页其他

Opencv C++图像处理:点多边形测试 + 矩 + 凸包 + 映射 + 反向投影

胖墩会武术 2024-06-20 06:51:33
简介Opencv C++图像处理:点多边形测试 + 矩 + 凸包 + 映射 + 反向投影

1、点多边形测试

1.1、计算像素点是在轮廓内部、外部或边界上:cv::pointPolygonTest()

#include <opencv2/imgproc.hpp>
函数说明:double cv::pointPolygonTest( InputArray contour, Point2f pt, bool measureDist );
输入参数:
				contour					输入轮廓。
				pt						测试点坐标。
				measureDist 			是否返回距离值。
								
返回值:			若measureDist=True,返回点到最近轮廓边的有符号距离。
				若measureDist=False时,返回值分别为+1(内部)、-1(外部)和0(边界上)。

1.2、计算最小值和最大值及其位置:cv::minMaxLoc()

该函数不适用于多通道阵列。如果需要在所有通道中找到最小或最大元素,请首先使用Mat::整形将数组重新解释为单个通道。或者,您可以使用extractImageCOI、mixChannels或split提取特定频道。

#include <opencv2/core.hpp>
函数说明:void cv::minMaxLoc( InputArray src, double * minVal, double * maxVal = 0, Point * minLoc = 0, Point * maxLoc = 0, InputArray mask = noArray() );
输入参数:
				src							输入单通道阵列。
				minVal						最小值;如果不需要,则使用NULL。
				maxVal = 0 					最大值;如果不需要,则使用NULL。
				minLoc = 0					最小位置(在2D情况下);如果不需要,则使用NULL。
				maxLoc = 0					最大位置(在2D情况下);如果不需要,则使用NULL。
				mask = noArray()			用于选择子阵列的可选掩码。如果掩码不是空数组,则在指定的数组区域中搜索极值。

1.3、实战案例

在这里插入图片描述

#include <opencv2/opencv.hpp>
//using namespace cv;
//using namespace std;

int main(int argc, const char* argv[]) 
{
	//(1)构建一张单通道8位400 x 400的图像,
	const int r = 100;
	cv::Mat src = cv::Mat::zeros(r * 4, r * 4, CV_8UC1);
	
	//(2)绘制自定义六边形(通过line绘制六次)
	std::vector<cv::Point2f> vert(6);
	vert[0] = cv::Point(3 * r / 2, static_cast<int>(1.34 * r));
	vert[1] = cv::Point(1 * r, 2 * r);
	vert[2] = cv::Point(3 * r / 2, static_cast<int>(2.866 * r));
	vert[3] = cv::Point(5 * r / 2, static_cast<int>(2.866 * r));
	vert[4] = cv::Point(3 * r, 2 * r);
	vert[5] = cv::Point(5 * r / 2, static_cast<int>(1.34 * r));
	for (int ii = 0; ii < 6; ii++)
	{
		cv::line(src, vert[ii], vert[(ii+1) % 6], cv::Scalar(255), 3, 8, 0);
	}
	
	//(3)轮廓检测
	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierachy;
	cv::Mat src_contours = src.clone();
	cv::findContours(src_contours, contours, hierachy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
	
	//(4)对图像中所有像素点进行【点多边形测试】:测试像素点是在多边形内部、边界或外部上
	cv::Mat raw_dist = cv::Mat::zeros(src_contours.size(), CV_32FC1);
	for (int row = 0; row < raw_dist.rows; row++)
	{
		for (int col = 0; col < raw_dist.cols; col++)
		{
			//输入参数:输入轮廓,测试点,是否返回距离值。(False:1表示在内部,0表示在边界上,-1表示在外部)(True表示返回实际距离值)
			double dist = cv::pointPolygonTest(contours[0], cv::Point2f(static_cast<float>(col), static_cast<float>(row)), true);
			raw_dist.at<float>(row, col) = static_cast<float>(dist);
		}
	}
	
	//(5)按内部、边界、外部三个区域分开,并且内/外部依据距离远近动态赋值,形成渐变色(也可以自定义为固定值)
	double minValue, maxValue;
	cv::minMaxLoc(raw_dist, &minValue, &maxValue, 0, 0, cv::Mat());		//计算最大值和最小值
	cv::Mat dst = cv::Mat::zeros(src.size(), CV_8UC3);
	for (int row = 0; row < dst.rows; row++)
	{
		for (int col = 0; col < dst.cols; col++)
		{
			float dist = raw_dist.at<float>(row, col);
			if (dist > 0)
				dst.at<cv::Vec3b>(row, col)[0] = (uchar)(abs(1.0 - (dist / maxValue)) * 255);		//内部
			else if (dist < 0)
				dst.at<cv::Vec3b>(row, col)[2] = (uchar)(abs(1.0 - (dist / minValue)) * 255);		//外部
			else
			{
				dst.at<cv::Vec3b>(row, col)[0] = (uchar)(abs(255 - dist));		//边界
				dst.at<cv::Vec3b>(row, col)[1] = (uchar)(abs(255 - dist));		//边界
				dst.at<cv::Vec3b>(row, col)[2] = (uchar)(abs(255 - dist));		//边界
			}
		}
	}
	
	//(6)显示图像
	cv::imshow("src", src);
	cv::imshow("dst", dst);
	cv::waitKey(0);
	return 0;
}

2、矩

cv::moments是OpenCV中一个常用的函数,用于计算图像区域的各阶矩。如:计算输入图像中一个区域的几何矩、中心矩和归一化中心矩。

  • 几何矩:是用来描述图像区域的形状和位置的;
  • 中心矩:是关于区域质心的矩,它可以用于计算区域的方向;
  • 归一化中心矩:是中心矩的归一化版本,可以用于不同尺度的比较。

常用于形状分析、图像识别、目标跟踪等领域。

  • 在形状分析方面,可以用来计算图像中各个对象的重心、面积、方向和轮廓等信息,从而实现图像分割和形状匹配。
  • 在图像识别方面,可以用来计算图像中特定对象的各种特征,如Hu矩,用于快速分类和识别目标。
  • 在目标跟踪方面,可以用来计算目标位置和方向,从而实现实时跟踪。

2.1、计算多边形或光栅化形状的三阶以下的所有力矩:cv::moments()

在这里插入图片描述

spatial moments(空间距)central moments(中心矩)central normalized moments(中心归一化矩)
double m00double mu20double nu20
double m10double mu11double nu11
double m01double mu02double nu02
double m20double mu30double nu30
double m11double mu21double nu21
double m02double mu12double nu12
double m30double mu03double nu03
double m21
double m12
double m03
#include <opencv2/imgproc.hpp>
函数说明:Moments cv::moments( InputArray array, bool binaryImage = false );
输入参数:
				array						光栅图像(单通道、8位或浮点2D阵列)或2D点(点或点2f)的阵列(1×N或N×1)。
				binaryImage = false			如果为True,则将所有非零图像像素视为1。该参数仅用于图像。
返回值:			moments.

备注:仅适用于Python绑定中的轮廓矩计算:请注意,输入数组的numpy类型应为np.int32或np.float32。
备注:由于轮廓矩是使用格林公式计算的,对于具有自相交的轮廓,您可能会得到看似奇怪的结果,例如蝴蝶形轮廓的零面积(m00)。

2.2、计算轮廓面积:cv::contourArea()

#include <opencv2/imgproc.hpp>
函数说明:double cv::contourArea( InputArray contour, bool oriented = false );
输入参数:		contour					2D点(轮廓顶点)的输入向量,存储在std::vector或Mat中。
				oriented = false		定向区域标志符。
								
返回值:			若oriented=true,返回一个带符号的面积值,符号值取决于轮廓方向(顺时针或逆时针)。
				若oriented=false(默认),返回面积的绝对值。

备注:与力矩类似,使用格林公式计算面积。因此,如果使用drawContours或fillPoly绘制轮廓,则返回的面积和非零像素数可能不同。此外,对于具有自相交的轮廓,该函数肯定会给出错误的结果。	

2.3、计算曲线长度或闭合轮廓周长:cv::arcLength()

#include <opencv2/imgproc.hpp>
函数说明:double cv::arcLength( InputArray curve, bool closed );
输入参数:		curve				2D点的输入矢量,存储在std::vector或Mat中。
				closed 				曲线是否闭合的标志符。	
				
返回值:			若closed=true,返回闭合轮廓周长。
				若closed=false,返回曲线长度。

2.4、实战案例

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

#include <opencv2/opencv.hpp>
//using namespace std;
//using namespace cv;

int main(int argc, char** argv)
{
	//(1)输入图像
	cv::Mat src = cv::imread("test.jpg");
	if (!src.data)
	{
		std::cout << "can't read image!" << std::endl;
		return -1;
	}

	//(2)图像处理
	cv::Mat src_gray_, src_blur, src_canny;
	cv::cvtColor(src, src_gray_, cv::COLOR_BGR2GRAY);
	cv::GaussianBlur(src_gray_, src_blur, cv::Size(3, 3), 0, 0);
	cv::Canny(src_blur, src_canny, 0, 160);			//该参数极大影响最终效果
	//cv::Canny(blur_src, canny_src, 80, 160);		//该参数极大影响最终效果


	//(3)轮廓检测
	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierachy;
	cv::findContours(src_canny, contours, hierachy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));

	//(4)计算每个轮廓的矩。矩中心点center=(x0, y0)。【其中:x0=m10/m00,y0=m01/m00】
	std::vector<cv::Moments> contours_moments(contours.size());
	std::vector<cv::Point2f> ccs(contours.size());
	for (size_t i = 0; i < contours.size(); i++)
	{
		//输入参数:输入图像,是否返回二值化图像
		contours_moments[i] = cv::moments(contours[i]);		//计算矩
		ccs[i] = cv::Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
	}

	//(5)绘制轮廓和圆(打印轮廓面积 + 弧长)
	cv::Mat dst = cv::Mat::zeros(src.size(), CV_8UC3);		//空矩阵
	//cv::Mat dst = src.clone();
	cv::RNG rng(12345);
	for (size_t i = 0; i < contours.size(); i++)
	{
		if (contours[i].size() < 10) 		//轮廓筛选(过滤较小的轮廓)
			continue;
		cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));		//生成随机数
		cv::drawContours(dst, contours, i, color, 2, 8, hierachy, 0, cv::Point(0, 0));
		cv::circle(dst, ccs[i], 2, color, 2, 8);
		std::cout << "当前为第[i]个轮廓:" << i << "【轮廓中心点】x=" << ccs[i].x << ", y=" << ccs[i].y << "【轮廓面积contourArea】" << cv::contourArea(contours[i]) << "【轮廓弧长arcLength】" << cv::arcLength(contours[i], true) << std::endl;
	}

	//(6)显示图像
	cv::imshow("src", src);
	cv::imshow("gray", src_gray_);
	cv::imshow("blur", src_blur);
	cv::imshow("canny", src_canny);
	cv::imshow("dst", dst);
	cv::waitKey(0);
	return 0;
}

3、凸包

定义:基于给定二维平面上的点集,将最外层的点连接起来构成的凸多边型即凸包。该多边形包含点集中所有的点。
凸包算法详解(convex hull)

在这里插入图片描述
Graham扫描算法

  • 11、首先选择 y 方向最低的点作为起始点p0,然后对p0进行极坐标扫描,依次添加p1…pn(排列顺序根据极坐标的角度大小,逆时针方向决定)
  • 22、添加任意 pi 点到凸包中,若导致左转向(逆时针),则添加该点到凸包;反之,若导致右转向(顺时针),则删除该点。

3.1、计算凸包:cv::convexHull()

#include <opencv2/imgproc.hpp>
函数说明:void cv::convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true );
输入参数:
				points						输入2D点集,存储在std::vector或Mat中。
				hull						输出凸包。
									(1)指数的整数向量。在第一种情况下,hull元素是原始数组中凸包点的基于0的索引(因为凸包的点集是原始点集的子集)。
									(2)点的向量。在第二种情况下,hull元素是凸船体点本身。
				clockwise = false			定位标志。true表示输出凸包为顺时针方向。否则为逆时针方向。假设的坐标系是X轴向右,Y轴向上。
				returnPoints = true			操作标记。true则返回凸包点。否则返回凸包点的索引。
									当输出数组为std::vector时,该标志被忽略。
									输出取决于vector的类型:(1)std::vector<int>则returnPoints=false;2)std::vector<Point>则returnPoints=true

3.2、实战案例

在这里插入图片描述

#include <opencv2/opencv.hpp>
//using namespace std;
//using namespace cv;

int main(int argc, char** argv)
{
	//(1)输入图像
	cv::Mat src = cv::imread("test.jpg");
	if (!src.data)
	{
		std::cout << "can't read image!" << std::endl;
		return -1;
	}

	//(2)图像处理
	cv::Mat src_gray, src_blur, src_bin;
	cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY);
	cv::blur(src_gray, src_blur, cv::Size(3, 3));
	cv::threshold(src_blur, src_bin, 100, 255, cv::THRESH_BINARY);		//该参数极大影响最终效果

	//(3)轮廓检测
	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierachy;
	cv::findContours(src_bin, contours, hierachy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));

	//(4)计算凸包
	std::vector<std::vector<cv::Point>> convexs(contours.size());
	for (size_t i = 0; i < contours.size(); i++)
	{
		//输入参数:轮廓点,凸包,方向(默认False=逆时针),是否返回点个数(默认True)
		cv::convexHull(contours[i], convexs[i], false, true);
	}

	//(5)绘制凸包
	cv::Mat dst = cv::Mat::zeros(src.size(), CV_8UC3);		//空矩阵
	//cv::Mat dst = src.clone();							//复制原图
	std::vector<cv::Vec4i> empty(0);
	cv::RNG rng(12345);
	for (size_t k = 0; k < contours.size(); k++)
	{
		cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		cv::drawContours(dst, contours, k, color, 2, cv::LINE_8, hierachy, 0, cv::Point(0, 0));
		cv::drawContours(dst, convexs, k, color, 2, cv::LINE_8, empty, 0, cv::Point(0, 0));
	}

	//(6)显示图像
	cv::imshow("src", src);
	cv::imshow("src_gray", src_gray);
	cv::imshow("src_blur", src_blur);
	cv::imshow("src_bin", src_bin);
	cv::imshow("dst", dst);
	cv::waitKey(0);
	return 0;
}

4、映射

4.1、像素重映射:cv::remap()

#include <opencv2/imgproc.hpp>
函数说明: void cv::remap( InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation, int borderMode = BORDER_CONSTANT, const Scalar &borderValue = Scalar() );
输入参数:
				(1)src					源图像。
				(2)dst					目的图像。大小与map1相同,类型与src相同。
				(3map1					(x,y)点的第一个映射或只有x值的第一个映射,其类型为CV_16SC2, CV_32FC1或CV_32FC2。
				(4)map2					第二个y值的映射,其类型分别为CV_16UC1、CV_32FC1或none(如果map1为(x,y)点,则为空映射)。
				(5)interpolation			插值方法。不支持INTER_AREA和INTER_LINEAR_EXACT方法。
									cv::INTER_NEAREST			最近邻插值
									cv::INTER_LINEAR			双线性插值(默认)
									cv::INTER_CUBIC 			双三次插值
									cv::INTER_AREA 				使用像素面积关系进行重新采样。
									cv::INTER_LANCZOS4 			8x8邻域上的Lanczos插值
									cv::INTER_LINEAR_EXACT 		位精确双线性插值
									cv::INTER_NEAREST_EXACT		位精确最近邻插值。这将产生与PIL、scikit图像或Matlab中的最近邻方法相同的结果。
									cv::INTER_MAX 				插值代码掩码
									cv::WARP_FILL_OUTLIERS 		标志,填充所有目的地图像像素。如果其中一些对应于源图像中的异常值,则将其设置为零。
									cv::WARP_INVERSE_MAP 		标志,逆变换
				(6)borderMode = BORDER_CONSTANT			边界类型(即边界填充方式)。
									cv::BORDER_CONSTANT = 0 			iiiiii|abcdefgh|iiiiiii			常量法。填充常数值
									cv::BORDER_REPLICATE = 1 			aaaaaa|abcdefgh|hhhhhhh			复制法。复制最边缘像素
									cv::BORDER_REFLECT  = 2 			fedcba|abcdefgh|hgfedcb			反射法。以两边为轴
									cv::BORDER_WRAP = 3 				cdefgh|abcdefgh|abcdefg			外包装法。
									cv::BORDER_REFLECT_101 = 4 			gfedcb|abcdefgh|gfedcba			反射法。以最边缘像素为轴
									cv::BORDER_TRANSPARENT = 5 			uvwxyz|abcdefgh|ijklmno
									cv::BORDER_REFLECT101 = 6 			same as BORDER_REFLECT_101
									cv::BORDER_DEFAULT = 7 				same as BORDER_REFLECT_101
									cv::BORDER_ISOLATED = 8 			do not look outside of ROI
				(7)Scalar &borderValue = Scalar()			边界值(在边界不变的情况下)。缺省值是0。

备注:此函数不能原地操作。

4.2、实战案例

在这里插入图片描述

#include <opencv2/opencv.hpp>
//using namespace std;
//using namespace cv;

int main(int argc, char** argv)
{
	while (true)
	{
		//(1)输入图像
		cv::Mat src = cv::imread("test.jpg");
		if (!src.data)
		{
			std::cout << "can't read image!" << std::endl;
			return -1;
		}

		//(2)像素重映射的四种类型(自定义)
		int c = cv::waitKey(500);		//2.1、等待键盘事件
		if ((char)c == 27) 				//2.2、退出键:Esc
			break;
		int index = c % 4;				//2.3、根据输入值进行四种类型判断:[0, 1, 2, 3]
		
		cv::Mat map_x, map_y;
		map_x.create(src.size(), CV_32FC1);			//x映射表
		map_y.create(src.size(), CV_32FC1);			//y映射表
		for (int row = 0; row < src.rows; row++) 
		{
			for (int col = 0; col < src.cols; col++) 
			{
				switch (index) 
				{
				case 0:					//2.2.1、缩小一半
					if (col > (src.cols * 0.25) && col <= (src.cols*0.75) && row > (src.rows*0.25) && row <= (src.rows*0.75)) 
					{
						map_x.at<float>(row, col) = 2 * (col - (src.cols*0.25));
						map_y.at<float>(row, col) = 2 * (row - (src.rows*0.25));
					}
					else 
					{
						map_x.at<float>(row, col) = 0;
						map_y.at<float>(row, col) = 0;
					}
					break;
				case 1:					//2.2.2、沿着Y方向翻转
					map_x.at<float>(row, col) = (src.cols - col - 1);
					map_y.at<float>(row, col) = row;
					break;
				case 2:					//2.2.3、沿着X方向翻转
					map_x.at<float>(row, col) = col;
					map_y.at<float>(row, col) = (src.rows - row - 1);
					break;
				case 3:					//2.2.4、沿着XY方向同时翻转
					map_x.at<float>(row, col) = (src.cols - col - 1);
					map_y.at<float>(row, col) = (src.rows - row - 1);
					break;
				}
			}
		}

		//(3)像素重映射:将输入图像的所有像素根据指定规则进行映射,并形成新图像。
		cv::Mat dst;
		cv::remap(src, dst, map_x, map_y, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0, 255, 255));

		//(4)显示图像
		cv::imshow("src", src);
		cv::imshow("dst", dst);
	}

	return 0;
}

5、反向投影

反向投影是一种被广泛应用于医学影像处理,计算机视觉和计算机图形学中的图像重建技术。

  • 在医学领域,反向投影技术经常用于CT(计算机断层扫描仪)和PET(正电子发射断层扫描)成像中,用于3D图像重建和图像分割。
  • 在计算机视觉领域,反向投影在3D物体重建中被广泛使用。例如,通过将多个2D图像拼接起来重建3D物体,如在机器人领域中处理3D传感器数据或为虚拟现实应用程序创建3D模型。
  • 在计算机图形学中,反向投影可以用于对3D对象进行渲染和投射,如对电影制作和电子游戏开发。

5.1、将指定通道从输入阵列复制到输出阵列的指定通道:cv::mixChannels()

#include <opencv2/core.hpp>
函数说明:void cv::mixChannels( const Mat * src, size_t nsrcs, Mat * dst, size_t ndsts, const int * fromTo, size_t npairs );
输入参数:		(1)src				矩阵的输入数组或向量;所有矩阵必须具有相同的大小和相同的深度。
				(2)nsrcs 				src中的矩阵数。
				(3)dst				矩阵的输出阵列或向量;必须分配所有矩阵;它们的大小和深度必须与src[0]中的相同。
				(4)ndsts				dst中的矩阵数。
				(5)fromTo				索引对数组:指定要复制的通道以及复制的位置。
								fromTo[k*2]是src中输入通道的基于0的索引,fromTo[m*2+1]是dst中输出通道的索引;
								使用连续的通道编号:	第一个输入图像通道的索引从0到src[0].channels()-1;
													第二个输入图像频道的索引从src[0]到src[0].channels()+src[1].Channelss()-1;
													依此类推,输出图像通道使用相同的方案。
								特殊情况:当fromTo[k*2]为负时,相应的输出通道填充为零。
				(6)npairs 			fromTo中的索引对数。

5.2、计算直方图的反向投影:cv::calcBackProject()

  • 反向投影:在输入图像中,查找与模板图像最匹配的点或区域,即定位模板图像出现在输入图像的位置。
  • 定位方法:不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。

假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)生成临时图像的直方图;
(3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
(4)直方图对比结果c,就是结果图像(0,0)处的像素值;
(5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
(6)重复(1)~(5)步直到输入图像的右下角。

#include <opencv2/imgproc.hpp>
函数说明:void cv::calcBackProject( const Mat * images, int nimages, const int * channels, InputArray hist, OutputArray backProject, const float ** ranges, double scale = 1, bool uniform = true );
输入参数:		(1)images					输入图像(CV_8U、CV_16U或CV_32F)。具有相同的深度和尺寸,且每一个都可以具有任意数量的通道。
				(2)nimages 				输入图像的数量。
				(3)channels				计算反向投影的通道列表。通道数量必须与直方图维度相匹配。
									第一个阵列通道的计数从0到images[0].channels()-1;
									第二个阵列通道的计数从images[0].channels()到images[0].channels() + images[1].channels()-1, and so on.4)hist					直方图。可以是密集的或稀疏的
				(5)backProject			目标反向投影阵列。与images[0]具有相同大小和深度的单通道阵列。
				(6)ranges 				每个维度中的直方图bin边界的数组。请参见calcHist。
				(7)scale = 1				可选比例因子,一般都设置成1。
				(8)uniform = true			直方图是否一致的标志符。

备注:该函数的执行效率非常的低。在使用之前需要注意图像大小,直方图维数,对比方式。
举例:对于1010 x 1010的RGB输入图像,10x10的模板图像,需要生成1百万次3维直方图,然后对比1百万次3维直方图。

5.3、实战案例

在这里插入图片描述

#include <opencv2/opencv.hpp>
//using namespace cv;
//using namespace std;

int main(int argc, const char* argv[]) 
{
	//(1)输入图像
	cv::Mat src = cv::imread("test.jpg");
	if (src.empty())
	{
		std::cout << "can't read image!" << std::endl;
		return -1;
	}

	//(2)图像处理
	cv::Mat src_hsv, src_hue;
	cv::cvtColor(src, src_hsv, cv::COLOR_BGR2HSV);			//格式转换
	src_hue.create(src_hsv.size(), src_hsv.depth());		//新建矩阵
	int nchannels[] = {0, 0};
	cv::mixChannels(&src_hsv, 1, &src_hue, 1, nchannels, 1);		//将制定通道从输入阵列复制到输出阵列的指定通道

	//(3)计算直方图和归一化
	cv::Mat h_hist;
	int bins = 12;
	float range[] = {0, 180};
	const float* histRanges = {range};
	cv::calcHist(&src_hue, 1, 0, cv::Mat(), h_hist, 1, &bins, &histRanges);
	cv::normalize(h_hist, h_hist, 0, 255, cv::NORM_MINMAX, -1, cv::Mat());

	//(4)计算直方图的反向投影
	cv::Mat Back_Project_Image;
	cv::calcBackProject(&src_hue, 1, 0, h_hist, Back_Project_Image, &histRanges, 1);
	
	//(5)计算反向投影的直方图
	int hist_h = 400;
	int hist_w = 400;
	cv::Mat Hist_Image(hist_w, hist_h, CV_8UC3, cv::Scalar(0, 0, 0));
	int bin_w = hist_w / bins;
	for(int ii = 1; ii < bins; ii++)
	{
		cv::rectangle(Hist_Image, cv::Point((ii - 1) * bin_w, (hist_h - cvRound(h_hist.at<float>(ii - 1) * (400 / 255)))), cv::Point(ii * bin_w, hist_h), cv::Scalar(0, 0, 255), -1);
	}
	
	//(6)显示图像
	cv::imshow("src", src);								//原图
	cv::imshow("BackProj", Back_Project_Image);			//反向投影
	cv::imshow("Histogram", Hist_Image);				//反向投影的直方图
	cv::waitKey(0);
	return 0;
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。