【问题标题】:Using opencv to match an image from a group of images for purpose of identification in C++使用 opencv 匹配一组图像中的图像,以便在 C++ 中进行识别
【发布时间】:2013-01-22 11:54:52
【问题描述】:

编辑:我通过这篇文章获得了足够的声誉,可以使用更多链接对其进行编辑,这将帮助我更好地理解我的观点

玩以撒装订的人经常会在小基座上看到重要的物品。

目标是让用户对某个项目能够按下一个按钮感到困惑,然后该按钮将指示他“装箱”该项目(想想 Windows 桌面装箱)。该框为我们提供了感兴趣的区域(实际项目加上一些背景环境)以与整个项目网格进行比较。

理论用户装箱项目

项目的理论网格(没有更多,我只是从 isaac wiki 的绑定中撕下这个)

在项目网格中标识为用户装箱的项目的位置将代表图像上的某个区域,该区域与提供有关项目信息的 isaac wiki 绑定的正确链接相关。

在网格中,项目是从底行算起的第 3 列的第 1 列。我在下面尝试的所有事情中都使用了这两个图像


我的目标是创建一个程序,该程序可以手动裁剪游戏“以撒的结合”中的物品,通过将图像与游戏中物品表的图像进行比较来识别裁剪的物品,然后显示正确的 wiki 页面。

这将是我的第一个“真正的项目”,因为它需要大量的图书馆学习才能完成我想做的事情。有点过头了。

我只是通过谷歌搜索弄乱了一些选项。 (你可以通过搜索方法名称和opencv快速找到我使用的教程。我的帐户由于某种原因被链接发布严重限制)

使用暴力匹配器:

http://docs.opencv.org/doc/tutorials/features2d/feature_description/feature_description.html

#include <stdio.h>
#include <iostream>
#include "opencv2/core/core.hpp"
#include <opencv2/legacy/legacy.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include "opencv2/highgui/highgui.hpp"

using namespace cv;

void readme();

/** @function main */
int main( int argc, char** argv )
{
  if( argc != 3 )
   { return -1; }

  Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
  Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );

  if( !img_1.data || !img_2.data )
   { return -1; }

  //-- Step 1: Detect the keypoints using SURF Detector
  int minHessian = 400;

  SurfFeatureDetector detector( minHessian );

  std::vector<KeyPoint> keypoints_1, keypoints_2;

  detector.detect( img_1, keypoints_1 );
  detector.detect( img_2, keypoints_2 );

  //-- Step 2: Calculate descriptors (feature vectors)
  SurfDescriptorExtractor extractor;

  Mat descriptors_1, descriptors_2;

  extractor.compute( img_1, keypoints_1, descriptors_1 );
  extractor.compute( img_2, keypoints_2, descriptors_2 );

  //-- Step 3: Matching descriptor vectors with a brute force matcher
  BruteForceMatcher< L2<float> > matcher;
  std::vector< DMatch > matches;
  matcher.match( descriptors_1, descriptors_2, matches );

  //-- Draw matches
  Mat img_matches;
  drawMatches( img_1, keypoints_1, img_2, keypoints_2, matches, img_matches );

  //-- Show detected matches
  imshow("Matches", img_matches );

  waitKey(0);

  return 0;
  }

 /** @function readme */
 void readme()
 { std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; }

导致看起来不太有用的东西。使用 flann 得到更清晰但同样不可靠的结果。

http://docs.opencv.org/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.html

#include <stdio.h>
#include <iostream>
#include "opencv2/core/core.hpp"
#include <opencv2/legacy/legacy.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include "opencv2/highgui/highgui.hpp"

using namespace cv;

void readme();

/** @function main */
int main( int argc, char** argv )
{
  if( argc != 3 )
  { readme(); return -1; }

  Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
  Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );

  if( !img_1.data || !img_2.data )
  { std::cout<< " --(!) Error reading images " << std::endl; return -1; }

  //-- Step 1: Detect the keypoints using SURF Detector
  int minHessian = 400;

  SurfFeatureDetector detector( minHessian );

  std::vector<KeyPoint> keypoints_1, keypoints_2;

  detector.detect( img_1, keypoints_1 );
  detector.detect( img_2, keypoints_2 );

  //-- Step 2: Calculate descriptors (feature vectors)
  SurfDescriptorExtractor extractor;

  Mat descriptors_1, descriptors_2;

  extractor.compute( img_1, keypoints_1, descriptors_1 );
  extractor.compute( img_2, keypoints_2, descriptors_2 );

  //-- Step 3: Matching descriptor vectors using FLANN matcher
  FlannBasedMatcher matcher;
  std::vector< DMatch > matches;
  matcher.match( descriptors_1, descriptors_2, matches );

  double max_dist = 0; double min_dist = 100;

  //-- Quick calculation of max and min distances between keypoints
  for( int i = 0; i < descriptors_1.rows; i++ )
  { double dist = matches[i].distance;
    if( dist < min_dist ) min_dist = dist;
    if( dist > max_dist ) max_dist = dist;
  }

  printf("-- Max dist : %f \n", max_dist );
  printf("-- Min dist : %f \n", min_dist );

  //-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist )
  //-- PS.- radiusMatch can also be used here.
  std::vector< DMatch > good_matches;

  for( int i = 0; i < descriptors_1.rows; i++ )
  { if( matches[i].distance < 2*min_dist )
    { good_matches.push_back( matches[i]); }
  }

  //-- Draw only "good" matches
  Mat img_matches;
  drawMatches( img_1, keypoints_1, img_2, keypoints_2,
               good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
               vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );

  //-- Show detected matches
  imshow( "Good Matches", img_matches );

  for( int i = 0; i < good_matches.size(); i++ )
  { printf( "-- Good Match [%d] Keypoint 1: %d  -- Keypoint 2: %d  \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); }

  waitKey(0);

  return 0;
 }

 /** @function readme */
 void readme()
 { std::cout << " Usage: ./SURF_FlannMatcher <img1> <img2>" << std::endl; }

到目前为止,模板匹配是我最好的方法。在这 6 种方法中,它的范围从仅获得 0-4 个正确标识。

http://docs.opencv.org/doc/tutorials/imgproc/histograms/template_matching/template_matching.html

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/// Global Variables
Mat img; Mat templ; Mat result;
char* image_window = "Source Image";
char* result_window = "Result window";

int match_method;
int max_Trackbar = 5;

/// Function Headers
void MatchingMethod( int, void* );

/** @function main */
int main( int argc, char** argv )
{
  /// Load image and template
  img = imread( argv[1], 1 );
  templ = imread( argv[2], 1 );

  /// Create windows
  namedWindow( image_window, CV_WINDOW_AUTOSIZE );
  namedWindow( result_window, CV_WINDOW_AUTOSIZE );

  /// Create Trackbar
  char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
  createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );

  MatchingMethod( 0, 0 );

  waitKey(0);
  return 0;
}

/**
 * @function MatchingMethod
 * @brief Trackbar callback
 */
void MatchingMethod( int, void* )
{
  /// Source image to display
  Mat img_display;
  img.copyTo( img_display );

  /// Create the result matrix
  int result_cols =  img.cols - templ.cols + 1;
  int result_rows = img.rows - templ.rows + 1;

  result.create( result_cols, result_rows, CV_32FC1 );

  /// Do the Matching and Normalize
  matchTemplate( img, templ, result, match_method );
  normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );

  /// Localizing the best match with minMaxLoc
  double minVal; double maxVal; Point minLoc; Point maxLoc;
  Point matchLoc;

  minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );

  /// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better
  if( match_method  == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
    { matchLoc = minLoc; }
  else
    { matchLoc = maxLoc; }

  /// Show me what you got
  rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
  rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );

  imshow( image_window, img_display );
  imshow( result_window, result );

  return;
}

http://imgur.com/pIRBPQM,h0wkqer,1JG0QY0,haLJzRF,CmrlTeL,DZuW73V#3

6 个 失败,通过,失败,通过,通过,通过

不过,这是一个最好的案例结果。我尝试的下一个项目是

并导致失败,失败,失败,失败,失败,失败

从一个项目到另一个项目,所有这些方法都有一些效果很好,一些效果很差

所以我会问:模板匹配是我最好的选择,还是有一种我没有考虑过的方法会成为我的圣杯?

如何让用户手动创建裁剪? Opencv 在这方面的文档真的很糟糕,我在网上找到的例子是非常老的 cpp 或纯 C。

感谢您的帮助。到目前为止,这次冒险是一次有趣的经历。我不得不删除所有链接,这样可以更好地描述一切是如何进行的,但该网站说我发布了超过 10 个链接,即使我没有发布。


游戏中的更多物品示例:

岩石是一种稀有物品,也是少数可以在屏幕上“随处”出现的物品之一。像石头这样的物品是用户裁剪物品是隔离物品的最佳方式的原因,否则它们的位置仅在几个特定的​​地方。

boss战后的物品,到处都是东西,中间是透明的。我想这是最难正确工作的一种

稀有房间。简单的背景。没有项目透明度。

这是游戏中所有物品的两张表。我最终会将它们制作成一张图片,但现在它们是直接取自 isaac wiki。

【问题讨论】:

  • 所以我理解工作流程是这样的:您和用户有一些大图像,用户裁剪该图像的区域并将其发送给您;之后,您想将此区域与大图像中的所有项目进行比较,并找到最匹配的项目。这是正确的吗?到目前为止,这听起来更像是分类,而不是匹配使用 SURF 检测到的特征。快速浏览一下 Eigenfaces 和 Haar 分类器等方法。此外,更多输入示例(包括裁剪区域和要搜索的大图像)和预期结果将有所帮助。
  • 我编辑了原始帖子,以便更清楚地了解我想要做什么(最初我没有足够的声誉来使用图像/链接)。我将研究您建议的那些事情,尽管乍一看它们似乎比我正在使用的内容更令人困惑。输入不会在不同项目之间发生显着变化,网格本身看起来就像现在一样,来自裁剪的输入图像将只是一个不同的项目,具有不同数量的背景,具体取决于用户的准确性和不同的背景本身,具体取决于用户所处的级别。我会尽快添加一些。

标签: c++ image opencv matching


【解决方案1】:

这里的一个重要细节是您对表格中的每个项目都有纯图像。您知道背景的颜色,并且可以将项目与图片的其余部分分离。例如,除了表示图像本身的矩阵之外,您还可以存储相同大小的 1-s 和 0-s 矩阵,其中 1 对应于图像区域,0 对应于背景。让我们称这个矩阵为“掩码”和项目的纯图像 - “模式”。

图像比较有两种方式:图像与图案匹配和图案与图像匹配。您所描述的是将图像与模式匹配 - 您有一些裁剪的图像并希望找到类似的模式。相反,请考虑图像上的搜索模式

让我们首先定义函数match(),它接受相同大小的图案、遮罩和图像,并检查遮罩下图案上的区域是否与图像中的区域完全相同(伪代码):

def match(pattern, mask, image):
    for x = 0 to pattern.width:
        for y = 0 to pattern.height: 
           if mask[x, y] == 1 and              # if in pattern this pixel is not part of background
              pattern[x, y] != image[x, y]:    # and pixels on pattern and image differ
               return False  
    return True

但图案和裁剪图像的大小可能会有所不同。对此的标准解决方案(例如,在级联分类器中使用)是使用 滑动窗口 - 只需在图像中移动模式“窗口”并检查模式是否与所选区域匹配。这就是 OpenCV 中图像检测的工作原理。

当然,这个解决方案不是很健壮 - 裁剪、调整大小或任何其他图像转换可能会改变一些像素,在这种情况下,方法 match() 将始终返回 false。为了克服这个问题,您可以使用图像和图案之间的距离,而不是布尔答案。在这种情况下,函数match() 应该返回一些相似性值,例如,介于 0 和 1 之间,其中 1 代表“完全相同”,而 0 代表“完全不同”。然后你要么设置相似度的阈值(例如,图像应该与图案至少有 85% 相似),要么只选择相似度最高的图案。

由于游戏中的物品是人造图像,并且它们的变化非常小,因此这种方法应该足够了。但是,对于更复杂的情况,您将需要其他功能,而不仅仅是掩码下的像素。正如我在评论中已经建议的那样,特征脸、使用类似 Haar 特征的级联分类器甚至主动外观模型等方法对于这些任务可能更有效。至于 SURF,据我所知,它更适合具有不同角度和对象大小的任务,但不适用于不同的背景和所有此类事情。

【讨论】:

    【解决方案2】:

    我在试图找出我自己的模板匹配问题时遇到了您的问题,现在我回来分享根据我自己的经验我认为可能是您最好的选择。您可能早就放弃了这一点,但嘿,有一天其他人可能会穿上类似的鞋子。

    您分享的所有项目都不是实心矩形,并且由于 opencv 中的模板匹配无法使用蒙版,您将始终将您的参考图像与我必须假设的图像进行比较几个不同的背景(更不用说在不同背景的不同位置找到的项目,使模板匹配更差)。
    它总是比较背景像素混淆您的匹配,除非您可以收集可以找到参考图像的每一种情况。如果血液/等的贴花也会在物品周围的背景中引入更多的可变性,那么模板匹配可能不会得到很好的结果。

    所以如果我是你,我会尝试的两件事取决于一些细节:

    1. 如果可能,请在找到该项目的每种情况下裁剪一个参考模板(这不是一个好时机),然后将用户指定的区域与每个项目的每个模板进行比较。从这些比较中获取最佳结果,如果幸运的话,您将获得正确的匹配。
    2. 您分享的示例屏幕截图在背景上没有任何暗/黑线,因此所有项目的轮廓都突出。 如果这在整个游戏中保持一致,您可以在用户指定的区域内找到边缘并检测外部轮廓。您将提前处理每个参考项目的外部轮廓并存储这些轮廓。然后,您可以将用户裁剪中的轮廓与数据库中的每个轮廓进行比较,将最佳匹配作为答案。

    我相信其中任何一个都适合你,这取决于你的屏幕截图是否很好地代表了游戏。

    注意:轮廓匹配会比模板匹配快很多很多。速度足够快,可以实时运行,并且可能不需要用户裁剪任何内容。

    【讨论】:

    • 本月发布的新版本游戏激起了我重新尝试的可能。然而,游戏中的一大变化是从 Flash 引擎转向 C++ 引擎。我最初只是试图通过视觉解决这个问题,因为分析 Flash 游戏的内存对我来说不起作用。总而言之,我喜欢看到这个,我可能会根据事情的进展来应用它
    猜你喜欢
    • 2013-03-13
    • 1970-01-01
    • 2016-03-21
    • 2019-10-29
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多