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

news2025/1/13 2:37:00

文章目录

  • 1、点多边形测试
    • 1.1、计算像素点是在轮廓内部、外部或边界上:cv::pointPolygonTest()
    • 1.2、计算最小值和最大值及其位置:cv::minMaxLoc()
    • 1.3、实战案例
  • 2、矩
    • 2.1、计算多边形或光栅化形状的三阶以下的所有力矩:cv::moments()
    • 2.2、计算轮廓面积:cv::contourArea()
    • 2.3、计算曲线长度或闭合轮廓周长:cv::arcLength()
    • 2.4、实战案例
  • 3、凸包
    • 3.1、计算凸包:cv::convexHull()
    • 3.2、实战案例
  • 4、映射
    • 4.1、像素重映射:cv::remap()
    • 4.2、实战案例
  • 5、反向投影
    • 5.1、将指定通道从输入阵列复制到输出阵列的指定通道:cv::mixChannels()
    • 5.2、计算直方图的反向投影:cv::calcBackProject()
    • 5.3、实战案例

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Regularization

在图像中的特征处理&#xff1a; 平均亮度的去除 SVD Whiten PCA 参数初始化策略 数据归一化&#xff1a; 标准归一化缩放归一化PCA/SVD 数据归一化对梯度的影响 归一化后&#xff0c;可以更快的搜索到最优值点 正则化 数据增强与早停 图像数据的增强主要是通过算法…

测试新手如何去学习接口自动化测试 ?从这一套测试框架开始 。

接口自动化测试框架系列之整体介绍 1.接口自动化测试1.自动化框架概述2.搭建自动化测试框架3.测试框架中所使用到的技术 1.接口自动化测试 自动化测试虽然是测试中比较热的一门技术 &#xff0c;但凡一个测试岗位&#xff0c;你几乎都能看到有自动化测试的要求 。但不得不说&a…

关于深度学习方面的一些概念

1、自学习&#xff08;self-taught learning&#xff09;与半监督学习   自学习(self-taughtlearning)是不要求未标注数据 X u X_u Xu​和已标注数据 X l X_l Xl​来自同样的分布。另外一种带限制性的方式也被称为半监督学习&#xff0c;它要求 X u X_u Xu​和 X l X_l Xl​服…

[快速入门前端17] CSS 选择器(6) 选择器总结

基本选择器 选择器说明语法通配符作用范围为所有标签&#xff0c;用于页面整体样式* { color: red }元素作用于同种标签&#xff0c;不能进行差异化样式设定p { color: red }类别作用于我们自行设定的类别&#xff0c;是使用频率最高的选择器.myClass { color: red }ID选取当前…

15JS06——流程控制-循环

目标&#xff1a; 1、循环 2、for循环 3、双重for循环 4、while循环 5、do while循环 6、continue break 7、命名规范以及语法格式 一、循环 1、循环的目的 可以重复执行某些代码 2、JS中的循环 for循环 while循环 do…while循环 二、for循环 在程序中&#xff0c;一…

机器学习 - 决策树

决策树是一种流程图&#xff0c;可以帮助我们根据以前的经验进行决策 比如&#xff0c;一个人将尝试决定他/她是否应该参加喜剧节目 下面是要用到的数据集 AgeExperienceRankNationalityGo36109UKNO42124USANO2346NNO5244USANO43218USAYES 读取并打印数据集 import pandas…

Transformer、Bert、Gpt对比系列,超详细介绍transformer的原理,bert和gpt的区别

一、Transformer架构图 Transformer 是一种用于序列到序列学习的神经网络模型&#xff0c;主要用于自然语言处理任务&#xff0c;如机器翻译、文本摘要等。它在2017年由 Google 提出&#xff0c;采用了注意力机制来对输入序列进行编码和解码。 Transformer 模型由编码器和解码…

随身WIFI折腾日记(五)---远程视频监控

六、远程视频监控 为了实现基于随身WIFI的网络摄像头&#xff0c;我参考了如下视频课程&#xff1a; 韦东山老师手把手带你从0开始自己做一个视频监控项目 系统由三部分组成&#xff1a;推流端(随身WIFI)、流媒体服务器(云服务器)、拉流端(浏览器/VLC播放器) 方案&#xff1…

NetworkInterface类

文章目录 1. 简介2. 工厂方法3. 获取方法 1. 简介 NetworkInterface类表示一个本地IP地址。这可以是一个物理接口&#xff0c;如额外的以太网卡&#xff08;常见于防火墙和路由器&#xff09;&#xff0c;也可以是一个虚拟接口&#xff0c;与机器的其它IP地址绑定到同个物理硬…

ensp实践dhcp服务

ensp实践dhcp服务 1、dhcp接口分配模式2、dhcp接口地址池分配模式3、dhcp布拉布拉布拉 1、dhcp接口分配模式 1.1、路由器AR1配置dhcp动态获取 <Huawei>system-view [Huawei]interface g0/0/0 [Huawei-GigabitEthernet0/0/0]ip address 10.1.1.1 24 [Huawei-GigabitEthe…

PixiJS 源码解读:绘制矩形,底层都做了什么?

大家好&#xff0c;我是前端西瓜哥&#xff0c;今天带大家看一下 PixiJS 的源码实现。 PixiJS 是一个非常流行的 Canvas 库&#xff0c;start 数将近 4w。 使用 PixiJS 简单易用的 API&#xff0c;我们可以在浏览器页面的 Canvas 元素上高性能地绘制图形&#xff0c;实现流畅…

基于深度学习的交通标志检测和识别(从原理到环境配置/代码运行)

项目是一个基于Python和OpenCV的交通标志检测和识别项目,旨在使用计算机视觉和深度学习技术对交通标志进行检测和分类。本文将从介绍项目原理和框架开始,详细介绍该项目的实现过程和技术细节,最后给出项目的安装和使用方法。 前后结果对比 识别前 识别后 一、 项目原理和框…

随身WIFI折腾日记(一)---霓虹灯

引言 通过对高通410芯片的随身WIFI刷写Debain系统&#xff0c;我们已经拥有了一台带4G功能的迷你ARM64单板电脑。现在我们可以基于此此平台进行一下二次开发。 随身WIFI的优势就是价格低廉&#xff0c;性能和树莓派zero2、树莓派3b差不多。 硬件配置如下&#xff1a; msm89…

随身WIFI折腾日记(二)---文件传输和软件安装

二、文件传输 我们可以通过SCP和SFTP工具和随身WIFI(USB连接)进行数据传输&#xff0c;上图以scp工具为例。 将本地电脑文件传输至随身WIFI&#xff0c;本地电脑上输入如下指令即可&#xff1a; scp /path/to/local/file user192.168.68.1:/path/to/remote/directory/注意&…

RK3568平台开发系列讲解(驱动基础篇)10min带你获取、了解与编译Kernel源代码

🚀返回专栏总目录 文章目录 一、Kernel获取二、Kernel根目录2.1 Documentation/2.1 arch/2.2 block/2.3 boot.its2.4 drivers/2.5 firmware/2.6 fs/2.7 include/2.8 init/2.9 ipc/2.10 kernel/2.11 lib/2.12 lo

(转载)从0开始学matlab(第13天)—画图进阶

我们将讨论简单的二维图象(之前已有所介绍)的附加特性。这些特性将允许我们控制 x&#xff0c;y 轴上的值的范围&#xff0c;在一个坐标系内打印多个图象&#xff0c;或创建多个图&#xff0c;或在一个图象窗口内创建多个子图像&#xff0c;或提供更加强大的轨迹文本字符控制。…

【c语言】全部知识点总结

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

前端如何做单元测试? 看这篇就入门了

前言 对于现在的前端工程&#xff0c;一个标准完整的项目&#xff0c;通常情况单元测试是非常必要的。但很多时候我们只是完成了项目而忽略了项目测试。我认为其中一个很大的原因是很多人对单元测试认知不够&#xff0c;因此我写了这边文章&#xff0c;一方面期望通过这篇文章…

基于DDSRF正负序分离方法的不平衡电网PQ控制策略_平衡电流控制

0.前言 对于并网逆变器而言&#xff0c;电网会存在不平衡的情况。在这种情况下&#xff0c;不平衡的电网电压可以分解成为正序、负序和零序分量。并网逆变器通常期望能够实现单位功率因数并网&#xff0c;向电网注入对称的正弦电流&#xff0c;所以此时的微电网逆变器控制策略显…

DC-9靶机-简单谈一下端口敲门技术 (Port Knocking)

前言 在打靶机DC-9时&#xff0c;爆破SSH时一直显示失败&#xff0c;经过查阅才知道原来是对端口做了“隐藏”&#xff0c;需要通过 Port Knocking 来主动开启&#xff0c;由于平时接触到的机会不多&#xff0c;所以这里简单记录一下&#xff0c;加强一下印象&#xff0c;也希…