模板匹配(Match Template) 修改版

本文详细介绍了模板匹配的基本原理及实现方法,包括匹配流程、不同匹配方式的特点,并提供了C#示例代码。

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

 

作者:王先荣 http://www.cnblogs.com/xrwang/archive/2010/02/05/MatchTemplate.html

 

修改者:MadTurtle------对于王先生的版本进行了修改,因为有些地方不对。


前言
    模板匹配是在图像中寻找目标的方法之一。Come On, Boy.我们一起来看看模板匹配到底是怎么回事。

 

模板匹配的工作方式
    模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。
    假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
  (1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;(改者注:其实每次匹配都是在模板的中心点对应的位置来给像素赋值,即第一次比较应该是将模板的(temp.width/2,temp.height/2)中心点开始的1/4面积同输入图像进行比较,匹配得到的结果c保存到模板中心点所在像素值中,具体参照《Learning OpenCV》,所以最终用来保留匹配结果的图像大小应该是size =  (images->width - patch_size.x + 1,images->height - patch_size.y + 1))

  (2)用临时图像和模板图像进行对比,对比结果记为c;
  (3)对比结果c,就是结果图像(0,0)处的像素值;
  (4)切割输入图像从(0,1)至(10,11)的临时图像,对比,并记录到结果图像;
  (5)重复(1)~(4)步直到输入图像的右下角。
    大家可以看到,直方图反向投影对比的是直方图,而模板匹配对比的是图像的像素值;模板匹配比直方图反向投影速度要快一些,但是我个人认为直方图反向投影的鲁棒性会更好。

 

模板匹配的匹配方式
    在OpenCv和EmguCv中支持以下6种对比方式:
    CV_TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;最好的匹配值为0;匹配越差,匹配值越大。
    CV_TM_CCORR 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
    CV_TM_CCOEFF 相关系数匹配法:1表示完美的匹配;-1表示最差的匹配。
    CV_TM_SQDIFF_NORMED 归一化平方差匹配法
    CV_TM_CCORR_NORMED 归一化相关匹配法
    CV_TM_CCOEFF_NORMED 归一化相关系数匹配法
    根据我的测试结果来看,上述几种匹配方式需要的计算时间比较接近(跟《学习OpenCv》书上说的不同),我们可以选择一个能适应场景的匹配方式。

 

模板匹配的示例代码
    下面是模板匹配的C#版本代码:

模板匹配
   
//模板匹配
private void btnCalc_Click(object sender, EventArgs e)
{
//输入图像
Image<Bgr, Byte> imageInput = new Image<Bgr, byte>((Bitmap)pbInput.Image);
//模板图像
Image<Bgr, Byte> imageTemplate = new Image<Bgr, byte>((Bitmap)pbTemplate.Image);
//缩放因子,更小的图像可以提高处理速度
double scale = 1d;
double.TryParse(txtScale.Text, out scale);
if (scale != 1d)
{
imageInput = imageInput.Resize(scale, INTER.CV_INTER_LINEAR);
imageTemplate = imageTemplate.Resize(scale, INTER.CV_INTER_LINEAR);
}
//色彩空间
string colorSpace = (string)cmbColorSpace.SelectedItem;
IImage imageInput2, imageTemplate2;
if (colorSpace == "Gray")
{
imageInput2 = imageInput.Convert<Gray, Byte>();
imageTemplate2 = imageTemplate.Convert<Gray, Byte>();
}
else if (colorSpace == "HSV")
{
imageInput2 = imageInput.Convert<Hsv, Byte>();
imageTemplate2 = imageTemplate.Convert<Hsv, Byte>();
}
else
{
imageInput2 = imageInput.Copy();
imageTemplate2 = imageTemplate.Copy();
}
//匹配方式数组
TM_TYPE[] tmTypes = new TM_TYPE[] { TM_TYPE.CV_TM_SQDIFF, TM_TYPE.CV_TM_SQDIFF_NORMED, TM_TYPE.CV_TM_CCORR, TM_TYPE.CV_TM_CCORR_NORMED, TM_TYPE.CV_TM_CCOEFF, TM_TYPE.CV_TM_CCOEFF_NORMED };
//输出图像(匹配结果)
Image<Gray, Single>[] imageResults = new Image<Gray, float>[tmTypes.Length];
//依次执行每种匹配,并归一化结果
int i = 0;
double totalTime = 0d; //总共用时
double time; //每种匹配的用时
Stopwatch sw = new Stopwatch();
txtResult.Text += string.Format("开始执行匹配(色彩空间:{0},缩放因子:{1})/r/n", colorSpace, scale);
foreach (TM_TYPE tmType in tmTypes)
{
sw.Start();
//模板匹配(注意:因为接口IImage中没有名为MatchTemplate的定义,所以需要进行强制转换)
//Image<Gray, Single> imageResult = imageInput2.MatchTemplate(imageTemplate2, tmType);
Image<Gray, Single> imageResult;
if (colorSpace == "Gray")
imageResult = ((Image<Gray, Byte>)imageInput2).MatchTemplate((Image<Gray, Byte>)imageTemplate2, tmType);
else if (colorSpace == "HSV")
imageResult = ((Image<Hsv, Byte>)imageInput2).MatchTemplate((Image<Hsv, Byte>)imageTemplate2, tmType);
else
imageResult = ((Image<Bgr, Byte>)imageInput2).MatchTemplate((Image<Bgr, Byte>)imageTemplate2, tmType);
sw.Stop();
time = sw.Elapsed.TotalMilliseconds;
totalTime += time;
sw.Reset();
//归一化结果
CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
//找到最匹配的点,以及该点的值
double bestValue;
Point bestPoint;
FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
//在最匹配的点附近画一个跟模板一样大的矩形
Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Size.Width / 2, bestPoint.Y - imageTemplate.Size.Height / 2), imageTemplate.Size);
imageResult.Draw(rect, new Gray(bestValue), 2);
//保存结果图像到数组
imageResults[i] = imageResult;
i++;
//显示结果
txtResult.Text += string.Format("匹配方式:{0:G},用时:{1:F05}毫秒,最匹配的点:({2},{3}),最匹配的值:{4}/r/n", tmType, time, bestPoint.X, bestPoint.Y, bestValue);
}
txtResult.Text += string.Format("匹配结束,共用时:{0:F05}毫秒/r/n", totalTime);
//显示结果图像
pbResultSqdiff.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[0]);
pbResultSqdiffNormalized.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[1]);
pbResultCcorr.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[2]);
pbResultCcorrNormalized.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[3]);
pbResultCcoeff.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[4]);
pbResultCcoeffNormalized.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[5]);
//释放资源
imageInput.Dispose();
imageTemplate.Dispose();
imageInput2.Dispose();
imageTemplate2.Dispose();
foreach (Image<Gray, Single> imageResult in imageResults)
imageResult.Dispose();
}

//找到最匹配的点,以及该点的值
private void FindBestMatchPointAndValue(Image<Gray, Single> image, TM_TYPE tmType, out double bestValue, out Point bestPoint)
{
bestValue = 0d;
bestPoint = new Point(0, 0);
double[] minValues, maxValues;
Point[] minLocations, maxLocations;
image.MinMax(out minValues, out maxValues, out minLocations, out maxLocations);
//对于平方差匹配和归一化平方差匹配,最小值表示最好的匹配;其他情况下,最大值表示最好的匹配
if (tmType == TM_TYPE.CV_TM_SQDIFF || tmType == TM_TYPE.CV_TM_SQDIFF_NORMED)
{
bestValue = minValues[0];
bestPoint = minLocations[0];
}
else
{
bestValue = maxValues[0];
bestPoint = maxLocations[0];
}
}

 

显示结果图像
    模板匹配和直方图反向投影生成的结果图像都是32位浮点型单通道图像。如果用C/C++,可以很方便的用OpenCv中的cvShowImage函数来显示;如果用.net,因为EmguCv中将32位浮点图像转换成8位位图的方法有些小问题,我们要自己编写一段转换的代码,然后再显示。

将浮点型图像转换成8位byte图像
   
/// <summary>
/// 将任意浮点型图像转换成Byte图像;
/// 本转换函数对浮点型图像的具体像素值没有要求,自动将值缩放到0~255之间。
/// </summary>
/// <typeparam name="TColor">图像的色彩空间</typeparam>
/// <param name="source">浮点型图像</param>
/// <returns>返回Byte型图像</returns>
public static Image<TColor, Byte> ImageSingleToByte<TColor>(Image<TColor, Single> source)
where TColor : struct, IColor
{
Image<TColor, Byte> dest = new Image<TColor, Byte>(source.Size);
//得到源图像的最小和最大值
double[] minVal, maxVal;
Point[] minLoc, maxLoc;
source.MinMax(out minVal, out maxVal, out minLoc, out maxLoc);
double min = minVal[0];
double max = maxVal[0];
for (int i = 1; i < minVal.Length; i++)
{
min = Math.Min(min, minVal[i]);
max = Math.Max(max, maxVal[i]);
}
//得到缩放比率和偏移量
double scale = 1.0, shift = 0.0;
scale = (max == min) ? 0.0 : 255.0 / (max - min);
shift = (scale == 0) ? min : -min * scale;
//缩放图像,并浮点图像缩放到256级的灰度
CvInvoke.cvConvertScaleAbs(source.Ptr, dest.Ptr, scale, shift);
return dest;
}

/// <summary>
/// 将任意浮点型图像转换成每通道8位的Bitmap;
/// 本转换函数对浮点型图像的具体像素值没有要求,自动将值缩放到0~255之间。
/// </summary>
/// <typeparam name="TColor">图像的色彩空间</typeparam>
/// <param name="source">浮点型图像</param>
/// <returns>返回每通道8位的Bitmap</returns>
public static Bitmap ImageSingleToBitmap<TColor>(Image<TColor, Single> source)
where TColor : struct, IColor
{
Image<TColor, Byte> dest = ImageSingleToByte<TColor>(source);
Bitmap bitmap = dest.Bitmap;
dest.Dispose();
return bitmap;
}

 

 

        左上是输入图像,左中是模板图像,右边是各种匹配方式的结果(相关匹配的结果明显不正确)

 

模板匹配和直方图反向投影的效率
    总的来说,模板匹配和直方图反向投影的效率都不高。在我的机器上,在1136*852大小的输入图像上匹配104*132的大小的模板图像(都是单通道灰度图像),大约需要700毫秒;而直方图反向投影大约需要75000毫秒(1.25分钟)。看来还需要继续学习,寻找更好的处理方法。
    另一方面,通过搜索OpenCv的源代码,发现OpenCv基本上没有使用并行计算。如果学习完之后,还有时间和热情,我准备尝试优化下OpenCv的并行计算;如果.net 4.0正式版推出了,也可以选择在这一方面做点优化。

 

感谢您耐心看完本文,希望对您有所帮助。


 

<think>好的,我现在要解决用户遇到的错误:AttributeError: 'tuple' object has no attribute 'x'。用户提供的代码在使用find_template函数后返回了一个元组,而不是预期的对象,导致访问x属性时报错。 首先,我需要回忆OpenMV的image模块中find_template函数的用法。根据文档,find_template返回的是一个匹配结果的对象,如果没找到则返回None。这个对象应该有x、y、w、h等属性和confidence方法。但用户得到的是元组,说明返回值结构可能不同。 可能的原因有几个。第一,OpenMV的版本更新导致函数返回值变化。较新版本可能返回元组而不是对象。第二,用户使用的代码示例过时,未适配最新API。第三,代码中存在其他错误,导致返回了错误的数据类型。 查阅最新的OpenMV文档,发现find_template函数确实返回一个包含匹配位置和置信度的元组,结构可能是(x, y, w, h, confidence)。如果是这样,用户代码中尝试调用match.x()就会失败,因为元组没有x属性。正确的做法应该是通过索引访问,比如match[0]获取x坐标,match[1]获取y坐标,等等。 因此,错误的原因是代码假设返回的是对象,但实际得到的是元组。解决方法是将访问属性的方式改为使用索引。例如,match.x()改为match[0],match.confidence()改为match[4]。 接下来,检查用户的代码,发现他们使用了match.x(), match.y(), match.w(), match.h()match.confidence()。这些都需要改为元组的索引访问。同时,绘制矩形时使用了match.rect(),这同样需要调整,可能需要用元组中的数据创建矩形。 修改后的代码应该用索引提取x、y、宽高,并构建矩形。例如,img.draw_rectangle(match[0], match[1], match[2], match[3], color=127)。同时,置信度应取match[4]。 总结问题,版本差异导致API变更,用户需要根据当前使用的OpenMV版本来调整代码。如果用户使用的是较新版本,必须按照元组的方式处理返回值。如果用户希望保持旧代码,可能需要降级固件版本,但更建议更新代码以适应新版本。</think>这个错误是由于模板匹配返回的结果类型不匹配导致的。让我们逐步分析问题原因并提供解决方案: $\textbf{错误原因分析}$ 1. $\text{find_template()}$ 函数返回的是一个$\text{元组(tuple)}$而不是对象 2. 旧版OpenMV固件返回的是$\text{MatchResult}$对象,新版可能已修改为返回元组 3. 元组没有$\text{x()}$等对象方法属性,只能通过索引访问 $\textbf{解决方案}$(两种方式任选其一) $\text{方法一:使用元组索引访问}$ ```python # 修改匹配结果处理部分 if match: print("匹配位置:", match[0], match[1]) # x,y print("匹配尺寸:", match[2], "x", match[3]) # w,h print("置信度:", match[4]) # confidence # 绘制矩形参数需要手动构造 img.draw_rectangle(match[0], match[1], match[2], match[3], color=127) ``` $\text{方法二:升级/降级固件版本}$ ```bash # 推荐使用最新稳定版固件(需检查文档确认返回值类型) # 或回退到旧版固件(若需要保持对象访问方式) ``` $\textbf{补充说明}$ 1. $\text{find_template()}$返回的元组结构为:$(x, y, width, height, confidence)$ 2. $\text{confidence}$置信度值范围:$0.0 \leq confidence \leq 1.0$,值越接近1匹配度越高 3. $\text{draw_rectangle()}$的参数顺序为:$(x, y, width, height)$ $\textbf{建议操作步骤}$ 1. 首先尝试$\text{方法一}$修改代码 2. 检查OpenMV IDE的固件版本(帮助 -> 关于) 3. 参考对应版本的$\text{官方文档}$确认API规范 4. 如果使用最新固件,建议保持元组访问方式 5. 如果需要旧版对象访问方式,可回退到$\text{v3.x}$系列固件 $\textbf{修改后的完整代码}$ ```python import sensor, image sensor.reset() sensor.set_pixformat(sensor.GRAYSCALE) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=2000) template = image.Image("/star_template.pgm") while True: img = sensor.snapshot() match = img.find_template(template, 0.7) if match: print("匹配位置:", match[0], match[1]) print("匹配尺寸:", match[2], "x", match[3]) print("置信度:", match[4]) img.draw_rectangle(match[0], match[1], match[2], match[3], color=127) else: print("未检测到模板") ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值