zoukankan      html  css  js  c++  java
  • 模板匹配(Match Template)

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

    模板匹配的工作方式
        模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。
        假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
      (1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
      (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}) ", 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} ", tmType, time, bestPoint.X, bestPoint.Y, bestValue);
    }
    txtResult.Text += string.Format("匹配结束,共用时:{0:F05}毫秒 ", 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];
    }
    }

    复制代码
     //模板匹配
            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})
    ", 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}
    ", tmType, time, bestPoint.X, bestPoint.Y, bestValue);
                }
                txtResult.Text += string.Format("匹配结束,共用时:{0:F05}毫秒
    ", 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位位图的方法有些小问题,我们要自己编写一段转换的代码,然后再显示。

    复制代码
    /// <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;
    }

    复制代码
    /// <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正式版推出了,也可以选择在这一方面做点优化。

     转:https://www.cnblogs.com/xrwang/archive/2010/02/05/MatchTemplate.html

  • 相关阅读:
    mysql查看执行sql语句的记录日志
    Java 装箱和拆箱
    Oracle导入的常见语句
    static与非static的区别
    nginx 常用命令
    linux sed 替换文件中的字符串
    linux 创建文件并写好内容
    Xshell连接docker centos 7
    按任意键开始、结束
    低配docker命令
  • 原文地址:https://www.cnblogs.com/soundcode/p/13952612.html
Copyright © 2011-2022 走看看