• 多角度的模板匹配


    多角度的模板匹配

    背景介绍

    熟悉OpenCV的朋友肯定都知道OpenCV自带的模板匹配matchTemplate方法是不支持旋转的,也就是说当目标和模板有角度差异时匹配常常会失败,可能目标只是轻微的旋转,匹配分数就会下降很多,导致匹配精度下降甚至匹配出错。另一个方法是matchShape(形状匹配),匹配时需要轮廓分明才容易匹配成功,但无法的到匹配角度,也不方便使用。本文介绍基于matchTemplate + 旋转 + 金字塔下采样实现多角度的模板匹配,返回匹配结果(斜矩形的端点、角度、匹配得分)。

    实现思路

    【1】如何适应目标的角度变化?

    • 我们可以将模板旋转,从0~360°依次匹配找到最佳的匹配位置

    【2】如何提高匹配速度?

    • 使用金字塔下采样,将模板和待匹配图均缩小后匹配;加大匹配搜寻角度的步长,比如从每1°匹配一次改为每5°匹配一次等。

    实现步骤:

    【1】旋转模板图像。

    旋转图像本身比较简单,下面是代码:

    //旋转图像
    Mat ImageRotate(Mat image, double angle)
    {
      Mat newImg;
      Point2f pt = Point2f((float)image.cols / 2, (float)image.rows / 2);
      Mat M = getRotationMatrix2D(pt, angle, 1.0);
      warpAffine(image, newImg, M, image.size());
      return newImg;
    }
    

    但需要注意,很多时候按照上面方法旋转时,会丢失模板信息产生黑边,这里提供两种方法供大家参考尝试:

    ① 旋转时放大目标图像尺寸,保证模板图像上信息不丢失,然后模板匹配时使用mask;

    ② 旋转时不放大目标图像尺寸,剔除黑边剩余部分做mask来匹配。

    【2】图像金字塔下采样。

    什么是图像金字塔?什么是上下采样?

    下采样的目的前面已介绍,减小图像分辨率提高图像匹配速度,代码如下:

    //对模板图像和待检测图像分别进行图像金字塔下采样
    for (int i = 0; i < numLevels; i++)
    {
      pyrDown(src, src, Size(src.cols / 2, src.rows / 2));
      pyrDown(model, model, Size(model.cols / 2, model.rows / 2));
    }
    

    【3】0~360°各度匹配。

    旋转模板图像,依次调用matchTemplate在目标图中匹配,记录最佳匹配分数,以及对应的角度。

    【4】计算匹配结果。

    根据模板图大小、匹配结果角度计算出匹配后的矩形四个角点,根据角点关系即可绘制方向:

    【5】举例演示。

    模板图从下图中截取并保存template.png:

    测试图像12张,来自Halcon例程图片,路径如下:

    ...\MVTec\HALCON-20.11-Steady\examples\images\modules

    匹配结果:

    相关代码

    /*
    旋转模板匹配函数(通过图像金字塔、增大旋转步长来提升匹配速度)
    Mat src:原图像
    Mat model:模板图
    double startAngle:旋转的最小角
    double endAngle:旋转的最大角
    double firstStep:角度旋转时的最大步长
    double secondStep:角度旋转时的最小步长
    int numLevels = 0:图像金字塔缩放次数
    */
    MatchResult rotateMatch(Mat src, Mat model, double startAngle, double endAngle, double firstStep, double secondStep, int numLevels = 0) {
      //对模板图像和待检测图像分别进行图像金字塔下采样
      for (int i = 0; i < numLevels; i++)  {
        pyrDown(src, src, Size(src.cols / 2, src.rows / 2));
        pyrDown(model, model, Size(model.cols / 2, model.rows / 2));
      }
    
      Mat rotatedImg, result;
      double score = -1;
      Point location;
      double angle;
    
      bool isSecond = false;
      while (true) {
        for (double curAngle = startAngle; curAngle <= endAngle; curAngle += firstStep) {
          rotatedImg = ImageRotate(model, curAngle);
          //imshow("rotated", rotatedImg);
          //imshow("src-pyrDown", src);
          //waitKey();
    
          matchTemplate(src, rotatedImg, result, TM_CCOEFF_NORMED);
          double minval, maxval;
          Point minloc, maxloc;
          minMaxLoc(result, &minval, &maxval, &minloc, &maxloc);
          if (maxval > score)
          {
            location = maxloc;
            score = maxval;
            angle = curAngle;
          }
        }
    
        if (isSecond) break;
    
        startAngle = angle - firstStep;
        endAngle = angle + firstStep;
    
        if ((endAngle - startAngle) / 5 > secondStep) {
          firstStep = (endAngle - startAngle) / 5;
        } else {
          firstStep = secondStep;
          isSecond = true;
        }
      }
    
      Point finalPoint = Point(location.x * pow(2, numLevels), location.y * pow(2, numLevels));
      vector points = GetRotatePoints(Size(model.cols * pow(2, numLevels), model.rows * pow(2, numLevels)), angle);
    
      for (int j = 0; j < points.size(); j++)
      {
        points[j].x += finalPoint.x;
        points[j].y += finalPoint.y;
      }
    
      return MatchResult(points, angle, score);
    }
    
    int main() {
      //读取所有图像
      vector imgs;
      string imageName;
      string path = "D:\\zMaterials\\algorithm\\Algo\\Data\\modules\\";
      ifstream fin(path + "modules.seq");
      while (getline(fin, imageName))
      {
        Mat img = imread(path + imageName + ".png");
        imgs.push_back(img);
      }
    
      Mat templateImg = imread(path + "template.png");
    
      int i = 0;
      for (Mat img: imgs)
      {
        i += 1;
        MatchResult matchResult = rotateMatch(img, templateImg, 0, 360, 30, 1, 0);
        vector points = matchResult.points;
        cout << i << "- 角度:" << matchResult.angle << endl;
        cout << i << "- 得分:" << matchResult.score << endl;
    
        line(img, points[0], points[1], Scalar(255, 0, 0), 2);
        line(img, points[1], points[2], Scalar(255, 0, 0), 2);
        line(img, points[2], points[3], Scalar(255, 0, 0), 2);
        line(img, points[3], points[0], Scalar(255, 0, 0), 2);
    
        Point pt1 = Point((points[0].x + points[1].x) / 2, (points[0].y + points[1].y) / 2);
        Point pt2 = Point((points[2].x + points[3].x) / 2, (points[2].y + points[3].y) / 2);
        arrowedLine(img, pt2, pt1, Scalar(0, 0, 255), 2);
        
        imshow("img_" + to_string(i), img);
        waitKey(0);
      }
    
      return 0;
    }
    
    

    参考:https://blog.csdn.net/qq_42722197/article/details/131447920

  • 相关阅读:
    Java真的不难(五十一)SpringBoot使用EasyExcel实现导出
    【计算机组成】实模式/保护模式下地址分段(基段地址+偏移地址)的原因
    es分布式全文搜索引擎介绍、下载和安装、索引操作、文档操作
    【面试系列】Java面试知识篇(六)
    PWM实验
    Geoserver中TileLayers中切割离线瓦片预览时地图模糊不清解决方法2
    全景环视开启「第二曲线」
    AI智能分析视频监控系统如何助力智慧民宿规范化、安全最大化?
    【PAT (Advanced Level) Practice】1011 World Cup Betting (20 分)python题解
    Android案例手册 - 实现一个华容道拼图游戏
  • 原文地址:https://www.cnblogs.com/nbk-zyc/p/17938810