Подтвердить что ты не робот

Использование opencv для сопоставления изображения из группы изображений с целью идентификации в С++

EDIT: я получил достаточную репутацию благодаря этому сообщению, чтобы иметь возможность редактировать его с помощью дополнительных ссылок, что поможет мне лучше понять

Люди, играющие в привязку isaac, часто сталкиваются с важными предметами на маленьких пьедесталах.

Цель состоит в том, чтобы пользователь путался о том, что элемент может нажимать на кнопку, которая затем предложит ему "вставить" элемент (подумайте о боксе на рабочем столе Windows). Коробка дает нам интересующую область (фактический элемент плюс некоторая фоновая среда) для сравнения с тем, что будет всей сеткой элементов.

Теоретический пользовательский бокс enter image description here

Теоретическая сетка элементов (там не так много, я просто разорвал это из привязки isaac wiki) enter image description here

Расположение в сетке элементов, идентифицированных как элемент, который пользовательский бокс представлял бы определенную область на изображении, которая коррелирует с соответствующей ссылкой на привязку wiki wiki, дающей информацию об элементе.

В сетке элемент - 1-й столбец 3-й из нижней строки. Я использую эти два изображения во всех вещах, которые я пробовал ниже.


Моя цель - создать программу, которая может взять ручную обрезку предмета из игры "Связывание Исаака", идентифицировать обрезанный предмет, находя сравнение изображения с изображением таблицы предметов в игре, затем отобразите соответствующую страницу вики.

Это будет мой первый "реальный проект" в том смысле, что он требует огромного количества обучения в библиотеке, чтобы получить то, что я хочу сделать. Это было немного подавляющим.

Я испортил несколько вариантов, просто из-за googling. (вы можете быстро найти учебники, которые я использовал при поиске имени метода и 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

приводит к не очень полезному виду. Более чистые, но одинаково ненадежные результаты с использованием 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; }

enter image description here

templatematching был моим лучшим методом. из 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 потерпеть неудачу, пройти, не в состоянии, проходят, проходят, проходят

Однако это был лучший результат. Следующий пункт, который я пробовал, был

enter image description here и привели к сбою, сбою, сбоям, сбоям, сбоям, сбоям

От элемента к элементу все эти методы имеют некоторые из них, которые хорошо работают, а некоторые, которые действительно ужасно

Итак, я спрошу: templatematching мой лучший выбор или есть метод, который я не рассматриваю, что будет моим святым Граалем?

Как я могу получить ПОЛЬЗОВАТЕЛЯ для создания обрезки вручную? Документация Opencv на этом очень плохой, и примеры, которые я нахожу в Интернете, - это очень старые cpp или прямые C.

Спасибо за любую помощь. До сих пор это предприятие было интересным. Мне пришлось снять все ссылки, которые лучше отображали бы, как все работает, но сайт говорит, что я публикую более 10 ссылок, даже если это не так.


еще несколько примеров предметов на протяжении всей игры:

камень - редкий предмет и один из немногих, который может быть "в любом месте" на экране. такие предметы, как камень, являются причиной того, что обрезка предмета пользователем является наилучшим способом изолирования предмета, в противном случае их позиции находятся только в нескольких конкретных местах.

enter image description here

enter image description here

Предмет после боя босса, множество вещей повсюду и прозрачность посередине. Я бы предположил, что это один из самых трудных для правильной работы.

enter image description here

enter image description here

Редкая комната. простой фон. нет прозрачности элемента.

enter image description here

<Т411 >

вот две таблицы, все элементы в игре:.. Я сделаю им одно изображение в конце концов, но пока они были непосредственно взяты из вики файла isaac.

enter image description here

enter image description here

4b9b3361

Ответ 1

Одна важная деталь здесь состоит в том, что у вас есть чистый образ каждого элемента в вашей таблице. Вы знаете цвет фона и можете отсоединить предмет от остальной части изображения. Например, помимо матрицы, представляющей само изображение, вы можете хранить матрицу из 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% аналогично шаблону), либо просто выберите шаблон с наибольшим значением сходства.

Поскольку элементы в игре являются искусственными изображениями, а вариации в них очень малы, такого подхода должно быть достаточно. Однако для более сложных случаев вам понадобятся другие функции, а не просто пиксели под маской. Как я уже сказал в своем комментарии, такие методы, как Eigenfaces, каскадный классификатор с использованием Haar-подобных функций или даже моделей Active Appearance, могут быть более эффективными для этих задач. Что касается SURF, насколько я знаю, он лучше подходит для задач с различным углом и размером объекта, но не для разных фонов и всех подобных вещей.

Ответ 2

Я столкнулся с вашим вопросом, пытаясь выяснить мою собственную проблему с сопоставлением шаблонов, и теперь я вернусь к тому, чтобы поделиться тем, что, по моему мнению, может быть лучшим выбором на основе моего собственного опыта. Вы, наверное, давно отказались от этого, но однажды кто-то может быть в подобной обуви.

Ни один из разделяемых элементов не является сплошным прямоугольником, и поскольку сопоставление шаблонов в opencv не может работать с маской, вы всегда будете сравнивать ваш ссылочный образ с тем, что я должен считать, по крайней мере, несколько разных фонов (не говоря уже о предметах, которые находятся в разных местах на разных фонах, что делает шаблон еще хуже). Это всегда будет сравнение фоновых пикселов и путая ваш матч, если вы не можете собрать урожай каждой ситуации, в которой можно найти эталонное изображение. Если наклейки крови/и т.д. Вносят еще большую изменчивость в фоны вокруг предметов, то сопоставление шаблонов, вероятно, не принесет больших результатов.

Итак, две вещи, которые я постараюсь, если бы я был вами, зависят от некоторых деталей:

  • Если возможно, обведите шаблон ссылки в любой ситуации, когда элемент найден (это не будет подходящее время), а затем сравните указанную пользователем область с каждым шаблоном каждого элемента. Возьмите лучший результат из этих сравнений, и вы, если повезет, получите правильное совпадение.
  • Пример снимков экрана, которые вы использовали, не имеет темных/черных линий на фоне, поэтому выделяются контуры всех элементов. Если это согласовано на протяжении всей игры, вы можете найти края в пределах указанной пользователем области и определить внешние контуры. Перед временем вы обработали внешние контуры каждого ссылочного элемента и сохранили эти контуры. Затем вы можете сравнить свой контур в пользовательской обрезке с каждым контуром в своей базе данных, взяв наилучшее соответствие в качестве ответа.

Я уверен, что любой из них может работать на вас, в зависимости от того, хорошо ли ваша игра представлена ​​вашими снимками экрана.

Примечание: Согласование контуров будет намного быстрее, чем, чем сопоставление шаблонов. Достаточно быстрый, чтобы работать в реальном времени и отрицать необходимость для пользователя обрезать что-либо, возможно.