[Opencv][C++]模板匹配--汇总

该代码实现了一个基于OpenCV的模板匹配类,支持单模板单目标、多目标以及多角度匹配。通过调整容许度来控制匹配的精度,使用金字塔采样提高检测速度。类中包含创建模板、单目标匹配、多目标匹配和多角度匹配的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 一、环境及说明

   环境:VS2015 + opencv4.2.0 x64 自编译版本

   说明:

           1.支持单模板单目标匹配、单模板多目标匹配、单模板多目标多角度匹配
           2.容许度:match后的分数限制,可以根据需要自己调整
           3.单模板多目标多角度的匹配,建议尽量使用较大容许度
           4.使用金字塔采样创建模板,可以大大加快检测速度

二、代码

#pragma once
#include <vector>
#include "opencv2\opencv.hpp"

//金字塔层级
#define DOWN_LEVEL 3
//容许度
#define TOLERANCE_MAX 0.98

//针对于多目标,多角度
struct MatchResult
{
	float Score;
	cv::RotatedRect ROI;

	MatchResult() {};
	MatchResult(float _score, cv::RotatedRect _roi) :Score(_score), ROI(_roi) {};
};

class EasyTemplate
{
public:
	EasyTemplate();
	~EasyTemplate();
public:
	//创建模板图像,
	//Img:模板图片;pryDown:是否使用低采样:低采样会加快检测、降低分数
	bool Mark(cv::Mat &Img,bool pryDown =false);

	//进行模板匹配,要求图像无缩放(或者和模板同步缩放)
	//Img:待检测图片;roi:最佳匹配矩形;score:匹配分数
	bool Match(cv::Mat &Img, cv::Rect &roi, float &score);

	//多角度模板匹配,在函数内部改角度步长
	//Img:待检测图片;roi:最佳匹配矩形;score:匹配分数
	bool Match(cv::Mat &Img, cv::RotatedRect &roi, float &score);

	//多角度,多模板  模板匹配
	//Img:待检测图片;results:最佳匹配矩形集合
	//tolerance:容许度
	bool Match(cv::Mat &Img, std::vector<MatchResult> &results,double tolerance= TOLERANCE_MAX);
private:
	bool gPryDownUsed;//是否使用低采样
	cv::Mat gTemplate;//模板图像,单通道
};
#include "stdafx.h"
#include "EasyTemplate.h"

EasyTemplate::EasyTemplate()
{
	gPryDownUsed = false;
}


EasyTemplate::~EasyTemplate()
{

}

bool EasyTemplate::Mark(cv::Mat &Img, bool pryDown)
{
	//确保有图像
	if (Img.empty())
		return false;

	//确保输入的是单通道图像
	if (Img.channels() != 1)
		cv::cvtColor(Img, gTemplate, cv::COLOR_BGR2GRAY);
	else
		gTemplate = Img.clone();

	//是否使用降低采样
	gPryDownUsed = pryDown;

	//返回结果
	return true;
}


bool EasyTemplate::Match(cv::Mat &Img, cv::Rect &roi, float &score)
{
	score = 0.0;
	roi = cv::Rect(-1, -1, 0, 0);
	//确保有图像
	if (Img.empty() || gTemplate.empty())
		return false;

	//模板尺寸小于被识别图像
	if (Img.cols < gTemplate.cols || Img.rows < gTemplate.rows)
		return false;

	//确保输入的是单通道图像
	cv::Mat GrayImg;
	if (Img.channels() != 1)
		cv::cvtColor(Img, GrayImg, cv::COLOR_BGR2GRAY);
	else
		GrayImg = Img.clone();

	//用于直方图计算
	cv::Mat ScoreImg = GrayImg.clone();
	//用于模板匹配计算
	cv::Mat TempImg = gTemplate.clone();
	//使用降低采样
	if (gPryDownUsed)
	{
		for (int i = 0; i < DOWN_LEVEL; i++)
		{
			cv::pyrDown(GrayImg, GrayImg, cv::Size(GrayImg.cols / 2, GrayImg.rows / 2));
			cv::pyrDown(TempImg, TempImg, cv::Size(TempImg.cols / 2, TempImg.rows / 2));
		}
	}

	//结果图像,要求32f
	int rows = GrayImg.rows - TempImg.rows + 1;  //结果图像的行数:W-w+1,W:原图像的行数,w:模板图像的行数
	int cols = GrayImg.cols - TempImg.cols + 1;  //结果图像的列数:H-h+1,H:原图像的列数,h:模板图像的列数
    //创建结果图像
	cv::Mat ResultImg(rows, cols, CV_32FC1);
	//开始模板匹配
	//TM_CCOEFF_NORMED:标准相关性系数匹配,数字越大越好
	//TM_SQDIFF:平方差匹配,约接近0越好
	cv::matchTemplate(GrayImg, TempImg, ResultImg, cv::TM_CCORR_NORMED);
	//最大值
	double maxValue;
	cv::Point maxLocation;
	///获取最大、最小像素值及其位置
	minMaxLoc(ResultImg, 0, &maxValue, 0, &maxLocation);

	//剔除
	if (maxValue < TOLERANCE_MAX)
		return false;

	//获取区域
	roi.x = maxLocation.x;
	roi.y = maxLocation.y;
	roi.width = TempImg.cols;
	roi.height = TempImg.rows;

	//使用降低采样
	if (gPryDownUsed)
	{
		roi.x = roi.x*pow(2, DOWN_LEVEL);
		roi.y = roi.y*pow(2, DOWN_LEVEL);
		roi.width = roi.width*pow(2, DOWN_LEVEL);
		roi.height = roi.height*pow(2, DOWN_LEVEL);
	}

	//这里使用直方图做相似度处理
	//直方图比较
	const int channels[1] = { 0 }; //通道索引
	float inRanges[2] = { 0,255 };  //像素范围
	const float* ranges[1] = { inRanges };//像素灰度级范围
	const int bins[1] = { 256 };   //直方图的维度

	cv::Mat hist1, hist2;
	//计算直方图并且归一化
	cv::calcHist(&gTemplate, 1, channels, cv::Mat(), hist1, 1, bins, ranges);
	cv::normalize(hist1, hist1, 1, 0, cv::NORM_L1);
	//计算直方图并且归一化
	cv::calcHist(&ScoreImg(roi).clone(), 1, channels, cv::Mat(), hist2, 1, bins, ranges);
	cv::normalize(hist2, hist2, 1, 0, cv::NORM_L1);
	//匹配计算相似度
	float similarity =cv::compareHist(hist1,hist2,cv::HISTCMP_INTERSECT);
	//权值计算
	score = similarity;
	//返回结果
	return true;
}

//图像旋转一定角度,
//坏处:轮廓边缘增强了,需要处理边缘
cv::Mat ImageRotate(cv::Mat Img, double Angle)
{
	cv::Mat Rotated;
	double alpha = -Angle * CV_PI / 180.0;//convert angle to radian format 

	cv::Point2f srcP[3];
	cv::Point2f dstP[3];

	srcP[0] = cv::Point2f(0, Img.rows);
	srcP[1] = cv::Point2f(Img.cols, 0);
	srcP[2] = cv::Point2f(Img.cols, Img.rows);

	//rotate the pixels
	for (int i = 0; i<3; i++)
		dstP[i] = cv::Point2f(srcP[i].x*cos(alpha) - srcP[i].y*sin(alpha), srcP[i].y*cos(alpha) + srcP[i].x*sin(alpha));

	double minx, miny, maxx, maxy;
	minx = std::min(std::min(std::min(dstP[0].x, dstP[1].x), dstP[2].x), float(0.0));
	miny = std::min(std::min(std::min(dstP[0].y, dstP[1].y), dstP[2].y), float(0.0));
	maxx = std::max(std::max(std::max(dstP[0].x, dstP[1].x), dstP[2].x), float(0.0));
	maxy = std::max(std::max(std::max(dstP[0].y, dstP[1].y), dstP[2].y), float(0.0));

	int w = maxx - minx;
	int h = maxy - miny;

	//translation
	for (int i = 0; i < 3; i++)
	{
		if (minx < 0)
			dstP[i].x -= minx;
		if (miny < 0)
			dstP[i].y -= miny;
	}
	//旋转图像
	cv::Mat warpMat = cv::getAffineTransform(srcP, dstP);
	cv::warpAffine(Img, Rotated, warpMat, cv::Size(w, h),cv::INTER_NEAREST,cv::BORDER_CONSTANT,cv::Scalar(0));
	return Rotated;
}

//旋转图像,边缘处理
void UpdateImgAngle(cv::Mat src, cv::Mat mask, float angle, cv::Mat& dst, cv::Mat &dstmask, cv::RotatedRect &rotate)
{
	cv::Mat updated;
	updated = ImageRotate(src, angle);
	dstmask = ImageRotate(mask, angle);
	rotate = cv::RotatedRect(cv::Point(updated.cols/2, updated.rows/2),cv::Size(src.cols, src.rows), -angle);
	//处理一下边缘
	cv::bitwise_and(updated, updated, dst, dstmask);
}

bool EasyTemplate::Match(cv::Mat & Img, cv::RotatedRect & roi, float & score)
{
	score = 0.0;
	roi = cv::RotatedRect(cv::Point(-1,-1),cv::Size(0,0),0);
	//确保有图像
	if (Img.empty() || gTemplate.empty())
		return false;

	//模板尺寸小于被识别图像
	if (Img.cols < gTemplate.cols || Img.rows < gTemplate.rows)
		return false;

	//确保输入的是单通道图像
	cv::Mat GrayImg;
	if (Img.channels() != 1)
		cv::cvtColor(Img, GrayImg, cv::COLOR_BGR2GRAY);
	else
		GrayImg = Img.clone();

	//用于直方图计算
	cv::Mat ScoreImg = GrayImg.clone();
	//创建 mask 图像
	cv::Mat gTemplateMask(gTemplate.rows, gTemplate.cols,CV_8UC1,cv::Scalar::all(255));
	cv::Mat TempImg = gTemplate.clone();
	cv::Mat TempMaskImg = gTemplateMask.clone();
	//使用降低采样
	if (gPryDownUsed)
	{
		for (int i = 0; i < DOWN_LEVEL; i++)
		{
			cv::pyrDown(GrayImg, GrayImg, cv::Size(GrayImg.cols / 2, GrayImg.rows / 2));
			cv::pyrDown(TempImg, TempImg, cv::Size(TempImg.cols / 2, TempImg.rows / 2));
			cv::pyrDown(TempMaskImg, TempMaskImg, cv::Size(TempMaskImg.cols / 2, TempMaskImg.rows / 2));
		}
	}
	//步长(每一次检测后增加的角度)
	const int angle_step = 2;
	const int match_size = 360 / angle_step;
	//一共需要匹配match_size次
	float Angle_Max = 0;
	double Score_Max = 0.0f;
	cv::Size Size_Max;
	cv::Point Location_Max;
	for (int i = 0; i < match_size; i++)
	{
		//改变图像
		cv::RotatedRect rotate;
		cv::Mat nTemplate, nTemplateMask;
		UpdateImgAngle(TempImg, TempMaskImg,i*angle_step, nTemplate, nTemplateMask, rotate);

		//模板尺寸小于被识别图像
		if (GrayImg.cols < TempImg.cols || GrayImg.rows < TempImg.rows)
			continue;

		//
		//结果图像,要求32f
		int rows = GrayImg.rows - nTemplate.rows + 1;  //结果图像的行数:W-w+1,W:原图像的行数,w:模板图像的行数
		int cols = GrayImg.cols - nTemplate.cols + 1;  //结果图像的列数:H-h+1,H:原图像的列数,h:模板图像的列数
													 //创建结果图像
		cv::Mat ResultImg(rows, cols, CV_32FC1);
		//开始模板匹配
		//TM_CCOEFF_NORMED:标准相关性系数匹配,数字越大越好
		//TM_SQDIFF:平方差匹配,约接近0越好
		cv::matchTemplate(GrayImg, nTemplate, ResultImg, cv::TM_CCORR_NORMED, nTemplateMask);
		//最大值
		double maxValue = 0.0f;;
		cv::Point maxLocation(-1,-1);
		///获取最大、最小像素值及其位置
		cv::minMaxLoc(ResultImg, 0, &maxValue, 0, &maxLocation);

		//剔除较小的
		if (maxValue < TOLERANCE_MAX)
			continue;
		//
		if (maxValue > Score_Max)
		{
			//更新信息
			Score_Max = maxValue;
			//尺寸、角度、最佳匹配点
			Size_Max = nTemplate.size();
			Angle_Max = i*angle_step;
			Location_Max = maxLocation;
		}
	}
	cv::Size tempSize(gTemplate.cols, gTemplate.rows);
	//获取ROI
	cv::Rect matchedROI(Location_Max.x, Location_Max.y, Size_Max.width, Size_Max.height);
	//使用降低采样
	if (gPryDownUsed)
	{
		matchedROI.x = matchedROI.x*pow(2, DOWN_LEVEL);
		matchedROI.y = matchedROI.y*pow(2, DOWN_LEVEL);
		matchedROI.width = matchedROI.width*pow(2, DOWN_LEVEL);
		matchedROI.height = matchedROI.height*pow(2, DOWN_LEVEL);
	}
	//根据ROI中心点,创建旋转矩形
	roi = cv::RotatedRect(cv::Point(matchedROI.x + matchedROI.width / 2, matchedROI.y + matchedROI.height / 2), tempSize, -Angle_Max);

	//这里使用直方图做相似度处理
	//直方图比较
	const int channels[1] = { 0 }; //通道索引
	float inRanges[2] = { 0,255 };  //像素范围
	const float* ranges[1] = { inRanges };//像素灰度级范围
	const int bins[1] = { 256 };   //直方图的维度

	cv::Mat nTemplate, nTemplateMask;
	UpdateImgAngle(gTemplate, gTemplateMask, Angle_Max, nTemplate, nTemplateMask, cv::RotatedRect());

	cv::Mat hist1, hist2;
	//计算直方图并且归一化
	cv::calcHist(&nTemplate, 1, channels, nTemplateMask, hist1, 1, bins, ranges);
	cv::normalize(hist1, hist1, 1, 0, cv::NORM_L1);
	//计算直方图并且归一化
	cv::calcHist(&ScoreImg(matchedROI).clone(), 1, channels, cv::Mat(), hist2, 1, bins, ranges);
	cv::normalize(hist2, hist2, 1, 0, cv::NORM_L1);
	//匹配计算相似度
	float similarity = cv::compareHist(hist1, hist2, cv::HISTCMP_INTERSECT);
	//权值计算
	score = similarity;
	//返回结果
	return true;
}

void getNextMaxLoc(cv::Mat & result, cv::Point preMaxLocation, cv::Size sizeTemplate, double& dMaxValue, cv::Point &maxLocation)
{
	//以当前点为中心
	int iStartX = std::max(preMaxLocation.x - sizeTemplate.width,0);
	int iStartY = std::max(preMaxLocation.y - sizeTemplate.height,0);
	//涂黑
	cv::rectangle(result, cv::Rect(iStartX, iStartY, 2 * sizeTemplate.width , 2 * sizeTemplate.height), cv::Scalar::all(-1), -1);
	//得到下一个最大值
	cv::minMaxLoc(result, 0, &dMaxValue, 0, &maxLocation);
}

bool EasyTemplate::Match(cv::Mat & Img, std::vector<MatchResult> &results, double tolerance)
{
	results.clear();
	//确保有图像
	if (Img.empty() || gTemplate.empty())
		return false;

	//模板尺寸小于被识别图像
	if (Img.cols < gTemplate.cols || Img.rows < gTemplate.rows)
		return false;

	//确保输入的是单通道图像
	cv::Mat GrayImg;
	if (Img.channels() != 1)
		cv::cvtColor(Img, GrayImg, cv::COLOR_BGR2GRAY);
	else
		GrayImg = Img.clone();

	//用于直方图计算
	cv::Mat ScoreImg = GrayImg.clone();
	//创建 mask 图像
	cv::Mat gTemplateMask(gTemplate.rows, gTemplate.cols, CV_8UC1, cv::Scalar::all(255));
	cv::Mat TempImg = gTemplate.clone();
	cv::Mat TempMaskImg = gTemplateMask.clone();
	//使用降低采样
	if (gPryDownUsed)
	{
		for (int i = 0; i < DOWN_LEVEL; i++)
		{
			cv::pyrDown(GrayImg, GrayImg, cv::Size(GrayImg.cols / 2, GrayImg.rows / 2));
			cv::pyrDown(TempImg, TempImg, cv::Size(TempImg.cols / 2, TempImg.rows / 2));
			cv::pyrDown(TempMaskImg, TempMaskImg, cv::Size(TempMaskImg.cols / 2, TempMaskImg.rows / 2));
		}
	}
	//步长(每一次检测后增加的角度)
	const int angle_step = 2;
	const int match_size = 360 / angle_step;
	//一共需要匹配match_size次
	for (int i = 0; i < match_size; i++)
	{
		//改变图像
		cv::RotatedRect rotate;
		cv::Mat nTemplate, nTemplateMask;
		UpdateImgAngle(TempImg, TempMaskImg, i*angle_step, nTemplate, nTemplateMask, rotate);

		//模板尺寸小于被识别图像
		if (GrayImg.cols < TempImg.cols || GrayImg.rows < TempImg.rows)
			continue;
		//
		//结果图像,要求32f
		int rows = GrayImg.rows - nTemplate.rows + 1;  //结果图像的行数:W-w+1,W:原图像的行数,w:模板图像的行数
		int cols = GrayImg.cols - nTemplate.cols + 1;  //结果图像的列数:H-h+1,H:原图像的列数,h:模板图像的列数
													   //创建结果图像
		cv::Mat ResultImg(rows, cols, CV_32FC1);
		//开始模板匹配
		//TM_CCOEFF_NORMED  TM_CCORR_NORMED:数字越大越好
		//TM_SQDIFF:平方差匹配,约接近0越好
		cv::matchTemplate(GrayImg, nTemplate, ResultImg, cv::TM_CCORR_NORMED, nTemplateMask);
		//最大值
		double maxValue = 0.0f;;
		cv::Point maxLocation(-1, -1);
		///获取最大、最小像素值及其位置
		cv::minMaxLoc(ResultImg, 0, &maxValue, 0, &maxLocation);
		//剔除较小的
		if (maxValue < tolerance)
			continue;
		//
		while (true)
		{
			cv::Rect matchedROI(maxLocation.x, maxLocation.y, nTemplate.cols, nTemplate.rows);
			//使用降低采样
			if (gPryDownUsed)
			{
				matchedROI.x = matchedROI.x*pow(2, DOWN_LEVEL);
				matchedROI.y = matchedROI.y*pow(2, DOWN_LEVEL);
				matchedROI.width = matchedROI.width*pow(2, DOWN_LEVEL);
				matchedROI.height = matchedROI.height*pow(2, DOWN_LEVEL);
			}
			//
			//根据ROI中心点,创建旋转矩形
			cv::RotatedRect roi = cv::RotatedRect(cv::Point(matchedROI.x + matchedROI.width / 2, matchedROI.y + matchedROI.height / 2), gTemplate.size(), -i*angle_step);
			//剔除遮挡的
			bool isOverlap = false;
			for(MatchResult match:results)
			{
				cv::Rect temp=match.ROI.boundingRect() & roi.boundingRect();
				if (temp.area() != 0)
				{
					isOverlap = true;
					break;
				}
			}
			//找到好的
			if (!isOverlap)
			{
				MatchResult re(maxValue, roi);
				results.push_back(re);
			}
			//获取下一个
			getNextMaxLoc(ResultImg, maxLocation, nTemplate.size(), maxValue, maxLocation);
			if (maxValue < tolerance)
				break;
		}
	}
	
	return true;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值