web-dev-qa-db-ja.com

C ++での識別を目的として、opencvを使用して画像グループの画像を照合する

編集:私はこの投稿を通じて十分な評判を得て、より多くのリンクで編集できるようになりました。これは私の主張をよりよく理解するのに役立ちます

アイザックのバインディングをしている人々は、小さな台座で重要なアイテムに出くわすことがよくあります。 

目標は、アイテムがボタンを押すことができるものについてユーザーを混乱させ、ボタンを押してアイテムを「ボックス化」するように指示することです(Windowsデスクトップボックス化を考えてください)。ボックスは、関心領域(実際のアイテムといくつかの背景環境)を示し、アイテムのグリッド全体と比較します。

理論上のユーザーボックスアイテム enter image description here

アイテムの理論上のグリッド(これ以上はありませんが、isaac wikiのバインディングからこれを取り除いただけです) enter image description here

ユーザーがボックス化したアイテムとして識別されたアイテムのグリッド内の場所は、アイテムに関する情報を提供するisaacwikiのバインディングへの適切なリンクに関連する画像上の特定の領域を表します。

グリッドでは、アイテムは下の行から3番目の1列目です。私はこれらの2つの画像を以下で試したすべてのことに使用します


私の目標は、ゲーム「The Binding of Isaac」からアイテムを手動で切り抜いて、画像をゲーム内のアイテムのテーブルの画像と比較して切り抜いたアイテムを識別し、適切なものを表示できるプログラムを作成することですウィキページ。

これは、私がやりたいことを成し遂げるために膨大な量の図書館学習を必要とするという意味で、私の最初の「実際のプロジェクト」になるでしょう。少し圧倒されました。

私はただグーグルで回るだけでいくつかのオプションを台無しにしました。 (メソッドの名前とopencvを検索すると、使用したチュートリアルをすばやく見つけることができます。私のアカウントは、何らかの理由でリンクの投稿が厳しく制限されています)

bruteforcematcherの使用:

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; }

enter image description here

あまり役に立たない見た目のものになります。フランを使用すると、よりクリーンですが、同様に信頼性の低い結果になります。

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; }

enter image description here

テンプレートマッチングは、これまでのところ私の最善の方法です。 6つの方法のうち、0〜4個の正しいIDのみを取得する範囲です。

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#

6つの失敗、合格、失敗、合格、合格、合格のうち

しかし、これは一種の最良の結果でした。私が試した次のアイテムは

enter image description here そして失敗、失敗、失敗、失敗、失敗、失敗に終わった

アイテムからアイテムへ、これらのすべての方法には、うまく機能するものとひどく機能するものがあります

だから私は尋ねます:テンプレートマッチングは私の最善の策ですか、それとも私が考えていない方法が私の聖杯になるとは思いませんか?

ユーザーに手動でクロップを作成させるにはどうすればよいですか?これに関するOpencvのドキュメントは本当に悪く、オンラインで見つけた例は非常に古いcppまたはストレートCです。

助けてくれてありがとう。このベンチャーはこれまでのところ興味深い経験でした。私はすべてがどのように機能しているかをよりよく描写するためにすべてのリンクを取り除く必要がありました、しかしサイトは私がそうでないときでさえ私が10以上のリンクを投稿していると言っています。


ゲーム全体のアイテムのいくつかの例:

岩は珍しいアイテムであり、画面の「どこにでも」配置できる数少ないアイテムの1つです。岩のようなアイテムは、ユーザーによるアイテムのトリミングがアイテムを分離するための最良の方法である理由です。そうでない場合、それらの位置はいくつかの特定の場所にのみあります。

enter image description here

enter image description here

ボス戦後のアイテム、いたるところにたくさんのものがあり、真ん中に透明感があります。これは正しく機能するのが難しいものの1つだと思います

enter image description here

enter image description here

レアルーム。シンプルな背景。アイテムの透明性はありません。

enter image description here

enter image description here

これがゲーム内のすべてのアイテムの2つのテーブルです。最終的には1つの画像にしますが、今のところ、それらはisaacwikiから直接取得されています。

enter image description here

enter image description here

23
2c2c

私自身のテンプレートマッチングの問題を理解しようとしているときにあなたの質問に出くわしました、そして今、私は私自身の経験に基づいてあなたの最善の策であると思うものを共有するために戻ってきました。あなたはおそらく長い間これを放棄してきましたが、誰か他の人がいつか同じような靴を履いているかもしれません。

共有したアイテムはどれも実線の長方形ではありません。opencvでのテンプレートマッチングはマスクでは機能しません参照画像を、少なくともいくつかの異なる背景であると想定するものと常に比較します。 (さまざまな背景のさまざまな場所にあるアイテムは言うまでもなく、テンプレートの一致はさらに悪化します)。
常に背景ピクセルを比較しますおよび一致を混乱させる参照画像が見つかるすべての状況の切り抜きを収集できない限り。血のデカールなどがアイテムの周囲の背景にもさらに変動をもたらす場合、テンプレートマッチングはおそらく素晴らしい結果を得ることができません。

だから私があなただったら私が試みる2つのことはいくつかの詳細に依存しています:

  1. 可能であれば、アイテムが見つかったすべての状況の参照テンプレートを切り取り(これは良い時期ではありません)、次にユーザー指定の領域をすべてのアイテムのすべてのテンプレートと比較します。これらの比較から最良の結果を得ると、運が良ければ、正しい一致が得られます。
  2. 共有したスクリーンショットの例では、背景に暗い/黒い線がないため、すべてのアイテムの輪郭が目立ちます。 これがゲーム全体で一貫している場合、ユーザー指定の領域内のエッジを見つけることができ、外部の輪郭を検出する。事前に、各参照アイテムの外部輪郭を処理し、それらの輪郭を保存しておく必要があります。次に、ユーザーのクロップ内の輪郭をデータベース内の各輪郭と比較し、最適な一致を回答として使用できます。

あなたのスクリーンショットでゲームがうまく表現されているかどうかに応じて、どちらもあなたのために働くことができると私は確信しています。

注:輪郭マッチングははるかに高速になりますテンプレートマッチングよりも。リアルタイムで実行するのに十分な速度であり、おそらくユーザーが何かをトリミングする必要がありません。

ここで重要な詳細の1つは、すべてのアイテムの純粋なイメージがありますテーブルにあるということです。あなたは背景の色を知っていて、写真の残りの部分からアイテムを切り離すことができます。たとえば、画像自体を表す行列に加えて、同じサイズの1と0の行列を格納できます。ここで、1は画像領域に対応し、0は背景に対応します。このマトリックスを「マスク」と呼び、アイテムの純粋なイメージを「パターン」と呼びましょう。

画像を比較するには、画像とパターンを一致させる方法と、パターンを画像と一致させる方法の2つがあります。あなたが説明したのは、画像をパターンと一致させることです-あなたはいくつかのトリミングされた画像を持っていて、同様のパターンを見つけたいと思っています。代わりに、画像のパターンの検索について考えてください。

まず、同じサイズのパターン、マスク、画像を取得し、マスクの下のパターンの領域が画像(擬似コード)とまったく同じであるかどうかを確認する関数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
ffriend