example - Usar opencv para hacer coincidir una imagen de un grupo de imágenes con el fin de identificación en C++
opencv python template matching (2)
Me encontré con tu pregunta al tratar de resolver mi problema de coincidencia de plantillas, y ahora estoy de vuelta para compartir lo que creo que podría ser tu mejor apuesta según mi propia experiencia. Probablemente ya hace tiempo que abandonó esto, pero algún día alguien más podría estar en zapatos similares.
Ninguno de los elementos que compartió es un rectángulo sólido, y dado que la coincidencia de plantillas en opencv no puede funcionar con una máscara , siempre estará comparando su imagen de referencia con lo que debo asumir es al menos varios fondos diferentes (sin mencionar los elementos que se encuentran en ubicaciones variadas en diferentes fondos, haciendo que la plantilla coincida aún peor).
Siempre estará comparando los píxeles de fondo y confundiendo su coincidencia, a menos que pueda recolectar un recorte de cada una de las situaciones donde se puede encontrar la imagen de referencia. Si las calcomanías de sangre / etc. introducen aún más variabilidad en los fondos alrededor de los artículos también, la coincidencia de plantillas probablemente no obtendrá grandes resultados.
Entonces, las dos cosas que probaría si fuera usted dependen de algunos detalles:
- Si es posible, recorte una plantilla de referencia de cada situación donde se encuentre el artículo (este no será un buen momento), luego compare el área especificada por el usuario con cada plantilla de cada elemento . Obtenga el mejor resultado de estas comparaciones y, si tiene suerte, tendrá una coincidencia correcta.
- Las capturas de pantalla de ejemplo que compartió no tienen ninguna línea oscura / negra en el fondo, por lo que se destacan los contornos de todos los elementos. Si esto es consistente durante todo el juego , puede encontrar los bordes dentro del área especificada por el usuario y detectar los contornos exteriores . Antes de tiempo, habría procesado los contornos exteriores de cada elemento de referencia y habría almacenado esos contornos. Luego puede comparar su (s) contorno (es) en el recorte del usuario con cada contorno de su base de datos, tomando la mejor coincidencia como respuesta.
Estoy seguro de que cualquiera de estos podría funcionar para usted, dependiendo de si el juego está bien representado por sus capturas de pantalla.
Nota: La coincidencia de contorno será mucho, mucho más rápida que la coincidencia de plantilla. Lo suficientemente rápido como para ejecutarse en tiempo real y anular la necesidad de que el usuario recorta algo, tal vez.
EDITAR: He adquirido suficiente reputación a través de esta publicación para poder editarla con más enlaces, lo que me ayudará a entender mejor mi punto de vista.
La gente que juega el atascamiento de Isaac a menudo se encuentra con elementos importantes en pequeños pedestales.
El objetivo es que el usuario se sienta confundido acerca de qué elemento puede presionar un botón, lo que le indicará que "guarde" el artículo (piense en el escritorio de Windows). El recuadro nos brinda la región de interés (el elemento real más algún ambiente de fondo) para comparar con lo que será una grilla completa de ítems.
Usuario en caja teórico
Cuadrícula teórica de elementos (no hay muchos más, simplemente arranqué esto de la encuadernación de la wiki de isaac)
La ubicación en la grilla de elementos identificados como el ítem que el usuario encuadraba representaría un área determinada en la imagen que se correlaciona con un enlace apropiado al enlace de la wiki de isaac que proporciona información sobre el ítem.
En la grilla, el ítem es la 1ra columna 3ra desde la fila inferior. Uso estas dos imágenes en todas las cosas que intenté a continuación
Mi objetivo es crear un programa que pueda tomar un recorte manual de un elemento del juego "El enlace de Isaac", identificar el elemento recortado mediante la comparación de la imagen con una imagen de una tabla de elementos en el juego, y luego mostrar el elemento adecuado página wiki.
Este sería mi primer "proyecto real" en el sentido de que requiere una gran cantidad de aprendizaje de la biblioteca para obtener lo que quiero hacer. Ha sido un poco abrumador
Me he metido con algunas opciones solo de buscar en Google. (Puede encontrar rápidamente los tutoriales que utilicé buscando el nombre del método y opencv. Por alguna razón, mi cuenta está muy restringida por la publicación de enlaces)
usando 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; }
resultados en cosas que no son tan útiles. Resultados más limpios pero igualmente poco confiables utilizando 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; }
templatematching ha sido mi mejor método hasta ahora. de los 6 métodos que van desde obtener solo 0-4 identificaciones correctas.
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
de los 6 fallan, pasan, fallan, pasan, pasan, pasan
Sin embargo, este fue el mejor resultado de caso. El siguiente artículo que intenté fue
y resultó en fallar, fallar, fallar, fallar, fallar, fallar
De un artículo a otro, todos estos métodos tienen algunos que funcionan bien y otros que lo hacen terriblemente
Entonces preguntaré: ¿es mi mejor apuesta la combinación de plantillas o hay un método que no estoy considerando que será mi santo grial?
¿Cómo puedo obtener un USUARIO para crear el recorte manualmente? La documentación de Opencv sobre esto es realmente mala y los ejemplos que encuentro en línea son extremadamente viejos cpp o C.
Gracias por cualquier ayuda. Esta aventura ha sido una experiencia interesante hasta ahora. Tuve que quitar todos los enlaces que mostrarían mejor cómo todo ha ido funcionando, pero el sitio dice que estoy publicando más de 10 enlaces, incluso cuando no lo estoy.
algunos ejemplos más de artículos a lo largo del juego:
el rock es un objeto raro y uno de los pocos que pueden estar "en cualquier lugar" en la pantalla. Los elementos como la roca son la razón por la que el recorte del artículo por parte del usuario es la mejor manera de aislar el artículo; de lo contrario, sus posiciones solo se encuentran en un par de lugares específicos.
Un artículo después de una pelea de jefe, muchas cosas en todas partes y transparencia en el medio. Me imagino que este es uno de los más difíciles de trabajar correctamente
Habitación rara. fondo simple. sin transparencia del artículo
Aquí están las dos tablas en las que se encuentran todos los elementos del juego. Haré una imagen eventualmente, pero por el momento fueron tomadas directamente de la wiki de isaac.
Un detalle importante aquí es que tienes una imagen pura de cada elemento de tu mesa. Conoces el color del fondo y puedes separar el elemento del resto de la imagen. Por ejemplo, además de la matriz, que representa la imagen en sí misma, puede almacenar una matriz de 1-sy 0-s del mismo tamaño, donde corresponden al área de la imagen y ceros - al fondo. Llamemos a esta matriz "máscara" e imagen pura del ítem - "patrón".
Hay 2 formas de comparar imágenes: hacer coincidir la imagen con el patrón y el patrón de coincidencia con la imagen. Lo que ha descrito es una imagen coincidente con el patrón: tiene alguna imagen recortada y desea encontrar un patrón similar. En cambio, piense en buscar patrón en la imagen .
Primero definamos función match()
que toma patrón, máscara e imagen del mismo tamaño y verifica si el área en el patrón debajo de la máscara es exactamente la misma que en la imagen (pseudocódigo):
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
Pero los tamaños del patrón y la imagen recortada pueden diferir. La solución estándar para esto (utilizada, por ejemplo, en el clasificador de cascada) es usar ventana deslizante : simplemente mueva la "ventana" del patrón a través de la imagen y verifique si el patrón coincide con la región seleccionada. Esto es más o menos como funciona la detección de imágenes en OpenCV.
Por supuesto, esta solución no es muy robusta: recortar, redimensionar o cualquier otra transformación de imagen puede cambiar algunos píxeles, y en este caso el método match()
siempre devolverá false. Para superar esto, en lugar de la respuesta booleana, puede usar la distancia entre la imagen y el patrón . En este caso, function match()
debería devolver algún valor de similitud, por ejemplo, entre 0 y 1, donde 1 significa "exactamente lo mismo", mientras que 0 significa "completamente diferente". A continuación, establezca el umbral de similitud (por ejemplo, la imagen debe ser al menos un 85% similar al patrón), o simplemente seleccione el patrón con el mayor valor de similitud.
Dado que los elementos en el juego son imágenes artificiales y la variación en ellos es muy pequeña, este enfoque debería ser suficiente. Sin embargo, para casos más complicados, necesitará otras características aparte de los píxeles debajo de la máscara. Como ya sugerí en mi comentario, los métodos como Eigenfaces, el clasificador en cascada que usa características similares a Haar o incluso los Modelos de Apariencia Activa pueden ser más eficientes para estas tareas. En cuanto a SURF, hasta donde yo sé, es más adecuado para tareas con diferentes ángulos y tamaños de objetos, pero no para diferentes fondos y cosas por el estilo.