天天看點

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

導讀

本文将介紹使用OpenCV實作多角度模闆比對的詳細步驟 + 代碼。(公衆号:OpenCV與AI深度學習)

背景介紹

    熟悉OpenCV的朋友肯定都知道OpenCV自帶的模闆比對matchTemplate方法是不支援旋轉的,也就是說當目标和模闆有角度差異時比對常常會失敗,可能目标隻是輕微的旋轉,比對分數就會下降很多,導緻比對精度下降甚至比對出錯。另一個方法是matchShape(形狀比對),比對時需要輪廓分明才容易比對成功,但無法的到比對角度,也不友善使用。本文介紹基于matchTemplate + 旋轉 + 金字塔下采樣實作多角度的模闆比對,傳回比對結果(斜矩形、角度、方向)。

實作效果

如上面視訊所示,本方法可以對不同角度的元件做比對并标注元件方向。

實作思路

【1】如何适應目标的角度變化?我們可以将模闆旋轉,從0~360°依次比對找到最佳的比對位置;

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

【2】如何提高比對速度?使用金字塔下采樣,将模闆和待比對圖均縮小後比對;加大比對搜尋角度的步長,比如從每1°比對一次改為每5°比對一次等。

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

實作步驟:

【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掩碼有什麼用?看下面連結文章介紹:

​​實戰 | OpenCV帶掩碼(mask)的模闆比對使用技巧與示範(附源碼)​​

② 旋轉時不放大目标圖像尺寸,剔除黑邊剩餘部分做mask來比對。

【2】圖像金字塔下采樣。什麼是圖像金字塔?什麼是上下采樣?直接百度。

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

下采樣的目的前面已介紹,減小圖像分辨率提高圖像比對速度,代碼如下:

//對模闆圖像和待檢測圖像分别進行圖像金字塔下采樣
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在目标圖中比對,記錄最佳比對分數,以及對應的角度。

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)
實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)
實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

模闆比對詳細使用說明:

​​https://docs.opencv.org/4.x/df/dfb/group__imgproc__object.html#ga586ebfb0a7fb604b35a23d85391329be​​

旋轉比對代碼:

TemplateMatchModes matchMode = TM_CCOEFF_NORMED;
  switch (nccMethod)
  {
  case 0:
    matchMode = TM_SQDIFF;
    break;
  case 1:
    matchMode = TM_SQDIFF_NORMED;
    break;
  case 2:
    matchMode = TM_CCORR;
    break;
  case 3:
    matchMode = TM_CCORR_NORMED;
    break;
  case 4:
    matchMode = TM_CCOEFF;
    break;
  case 5:
    matchMode = TM_CCOEFF_NORMED;
    break;
  }


  //在沒有旋轉的情況下進行第一次比對
  double minVal, maxVal;
  Point minLoc, maxLoc;
  matchTemplate(src, model, result, matchMode);
  minMaxLoc(result,  &minVal, &maxVal, &minLoc, &maxLoc);


  Point location = maxLoc;
  double temp = maxVal;
  double angle = 0;


  Mat newImg;


  //以最佳比對點左右十倍角度步長進行循環比對,直到角度步長小于參數角度步長
  if (nccMethod == 0 || nccMethod == 1)
  {
    do
    {
      for (int i = 0; i <= (int)range / step; i++)
      {
        newImg = ImageRotate(model, start + step * i);
        
        matchTemplate(src, newImg, result, matchMode);
        double minval, maxval;
        Point minloc, maxloc;
        minMaxLoc(result, &minval, &maxval, &minloc, &maxloc);
        if (maxval < temp)
        {
          location = maxloc;
          temp = maxval;
          angle = start + step * i;
        }
      }
      range = step * 2;
      start = angle - step;
      step = step / 10;
    } while (step > angleStep);
    return ResultPoint(location.x * pow(2, numLevels) + modelImage.cols / 2, location.y * pow(2, numLevels) + modelImage.rows / 2, -angle, temp);
  }
  else
  {
    do
    {
      for (int i = 0; i <= (int)range / step; i++)
      {
        newImg = ImageRotate(model, start + step * i);
        imshow("rotate", newImg);
        imshow("src-pyrDown", src);
        waitKey();
        matchTemplate(src, newImg, result, matchMode);
        double minval, maxval;
        Point minloc, maxloc;
        minMaxLoc(result, &minval, &maxval, &minloc, &maxloc);
        if (maxval > temp)
        {
          location = maxloc;
          temp = maxval;
          angle = start + step * i;
        }
      }
      range = step * 2;
      start = angle - step;
      step = step / 10;
    } while (step > angleStep);
    if (temp > thresScore)
    {
      return ResultPoint(location.x * pow(2, numLevels), location.y * pow(2, numLevels), -angle, temp);
    }
  }
  return ResultPoint(-1, -1, 0, 0);      

【4】标注比對結果。根據模闆圖大小、比對結果角度計算出比對後的矩形四個角點,根據角點關系即可繪制方向:

//擷取旋轉後矩形對應的端點坐标
vector<Point> GetRotatePoints(Mat img, Rect inRect, double angle)
{
  Rect rect = inRect;
  vector<Point>pts;
  Point2f center = Point2f(img.cols / 2, img.rows / 2);
  Mat M = getRotationMatrix2D(center, angle, 1.0);
  //cout << M << endl;


  Mat ptMat = Mat::ones(3, 4, CV_32FC1);
  ptMat.at<float>(0, 0) = 0;
  ptMat.at<float>(0, 1) = (float)rect.width - 1;
  ptMat.at<float>(0, 2) = (float)rect.width - 1;
  ptMat.at<float>(0, 3) = 0;
  ptMat.at<float>(1, 0) = 0;
  ptMat.at<float>(1, 1) = 0;
  ptMat.at<float>(1, 2) = (float)rect.height - 1;
  ptMat.at<float>(1, 3) = (float)rect.height - 1;


  M.convertTo(M, CV_32F);


  Mat result = M * ptMat;
  //cout << result << endl;
  pts.push_back(Point((int)result.at<float>(0, 0), (int)result.at<float>(1, 0)));
  pts.push_back(Point((int)result.at<float>(0, 1), (int)result.at<float>(1, 1)));
  pts.push_back(Point((int)result.at<float>(0, 2), (int)result.at<float>(1, 2)));
  pts.push_back(Point((int)result.at<float>(0, 3), (int)result.at<float>(1, 3)));
  return pts;
}      

【5】舉例示範。模闆圖從下圖中截取并儲存template.png:

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

測試圖像12張,來自Halcon例程圖檔,路徑如下:

C:\Users\Public\Documents\MVTec\HALCON-20.11-Steady\examples\images\modules

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

比對結果:

實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)
實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)
實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)
實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)
實戰 | OpenCV實作多角度模闆比對(詳細步驟 + 代碼)

後記

    可以添加比對分數門檻值和NMS實作多目标比對,後續還會介紹其他比對方法的實作,敬請期待。

繼續閱讀