Detección de objetos planos

Seguimos aplicando los conocimientos adquiridos en tutoriales anteriores, esta vez aprenderemos a detectar un objeto plano conocido que se encuentre dentro de una imagen, para hacer esto requerimos extraer los puntos característicos de la imagen de referencia (el objeto que deseamos localizar) para luego ubicar estos puntos en la imagen de destino, luego usando homografía podemos obtener la posición de dicho objeto.

Detección de objeto plano

Lo primero que requerimos luego de crear nuestro proyecto es encontrar los key point de nuestra imagen de referencia como lo hicimos el los tutoriales previos, por supuesto también requerimos los de nuestra imagen en donde deseamos detectar nuestro objeto.

Ptr<Feature2D> detector = BRISK::create();
Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING, true);

// cargar la imagen de referencia, aquel objeto plano que deseamos localizar.
Mat img_object = imread("image/img.jpg", IMREAD_GRAYSCALE);
Mat desc_object;

// cargar la imagen de la escena en donde buscaremos el objeto plano.
Mat img_scene = imread("image/scene.jpg", IMREAD_GRAYSCALE);
Mat desc_scene;

// detectar y extraer los puntos de interes y sus respectivos descriptores.
vector<KeyPoint> kp_object;
detector->detectAndCompute(img_object, noArray(), kp_object, desc_object);

// detectar y extraer los puntos de interes y sus respectivos descriptores.
vector<KeyPoint> kp_scene;
detector->detectAndCompute(img_scene, noArray(), kp_scene, desc_scene);

Todo lo referente a este código lo estudiamos en el tutorial: Detección y extracción de puntos clave, lo siguiente que requerimos realizar es el pareo de puntos, es decir, intentar ubicar los puntos de la imagen A en la imagen B, este tema también lo hemos visto anteriormente, el código es el siguiente:

// hacer el pareo de los key points.
vector<DMatch> matches;
matcher->match(desc_object, desc_scene, matches);

// filtrar los mejores key points.
sort(matches.begin(), matches.end());
matches.erase(matches.begin() + 30, matches.end());

Recordemos que podemos usar la función cv::drawMatches(...) para mostrar el pareo de puntos.

Detectar y describir los puntos clave

Antes de proceder con el siguiente paso será necesario obtener las coordenadas (x, y) de cada uno de los puntos, para ello recorremos el objeto matches el cual es una lista de objetos DMatch para cada uno de ellos podemos consultar el campo queryIdx y trainIdx para obtener el índice que corresponde al KeyPoint de la imagen de referencia y la imagen de escena respectivamente, luego obtenemos en campo pt que guarda la información que necesitamos.

vector<Point2f> pts_object, pts_scene;
vector<DMatch> good_match;

// guardar los puntos correspondientes a cada uno de los key points.
for (auto& m : matches)
{
    pts_object.push_back(kp_object[m.queryIdx].pt);
    pts_scene.push_back(kp_scene[m.trainIdx].pt);

    good_match.push_back(m);
}

Ahora podemos calcular la matriz de homografía usando los puntos previamente obtenidos, para esta tarea usamos la función OpenCV llamada cv::findHomography(...), los primeros dos parámetros corresponden a los puntos de obtenidos en el paso anterior y el último indica el método a utilizar.

Con la matriz de homografía H podemos usar cv::perspectiveTransform(...) para transformar las coordenadas del la imagen de referencia, para nuestro caso estas coordenadas corresponden a cada una de las esquinas de la imagen, las almacenamos en el objeto obj_corners, las coordenadas transformadas se guardan en scene_corners.

// buscar la matriz de homografia que representa la tranformación entre los puntos del objeto y la escena.
Mat H = findHomography(pts_object, pts_scene, CV_RANSAC);
if (!H.empty()) {

    // obtener las coordenadas de la imagen de referencia, corresponde a las 4 esquinas de la imagen.
    std::vector<Point2f> obj_corners(4);
    obj_corners[0] = Point2f(0, 0);
    obj_corners[1] = Point2f((float)img_object.cols, 0);
    obj_corners[2] = Point2f((float)img_object.cols, (float)img_object.rows);
    obj_corners[3] = Point2f(0, (float)img_object.rows);

    // aplicar la tranformación a las corredenadas previas para obtener la posición de cada punto en la escena.
    std::vector<Point2f> scene_corners(4);
    perspectiveTransform(obj_corners, scene_corners, H);

    Mat img_scene_dst;
    cvtColor(img_scene, img_scene_dst, CV_GRAY2BGR);

    // Dibujamos las coordenadas del objeto plano en la escena.
    line(img_scene_dst, scene_corners[0], scene_corners[1], Scalar(0, 255, 0), 3, LINE_AA);
    line(img_scene_dst, scene_corners[1], scene_corners[2], Scalar(0, 255, 0), 3, LINE_AA);
    line(img_scene_dst, scene_corners[2], scene_corners[3], Scalar(0, 255, 0), 3, LINE_AA);
    line(img_scene_dst, scene_corners[3], scene_corners[0], Scalar(0, 255, 0), 3, LINE_AA);

    imshow("Detected Object", img_scene_dst);
}

Con esto ya tenemos las coordenadas de nuestro objeto buscado, solo resta dibujarlas sobre la imagen para que podamos visualizarlas en nuestra aplicación, para ello las dibujamos con la función cv::line(...), si deseas mas información puedes ver: Funciones de dibujo OpenCV, las líneas que vamos a dibujar son para unir y marcar las 4 esquinas del objeto.

Hecho esto el resultado sería el siguiente:

Identificar un objeto plano con OpenCV

Podemos utilizar esta técnica cuando deseemos ubicar objetos planos como: libros, tarjetas, señales de transito, entre otros, aunque de momento trabajamos con una imagen estática esto puede fácilmente ser aplicado a un video en tiempo real para, por ejemplo, estimar la posición del libro y dibujar una figura 3D sobre él, de este modo podríamos crear aplicaciones de realidad aumentada.

Comentarios

  1. Buenas noches, muy buen tutorial.

    Una duda, yo quiero hacer la detección de objetos(realidad aumentada) de un cuadro que está dibujado o impreso en una hoja de papel pero con la camara de frente a la hoja, ejemplo quiero ver una sopa de letras, este tuto me serviria? o me recomiendas otro más adecuado para ese tipo de casos??. gracias de antemano

    ResponderEliminar
    Respuestas
    1. Este tipo de detector funciona bien si conoces de antemano el objeto plano que deseas localizar, si tu objeto varía en color o forma quizás debas aplicar otro tipo de detección, por color o forma, por ejemplo.

      Eliminar
    2. En mi caso seria el ejemplo del recuadro de la sopa de letras captada directamente desde la hoja de papel, el problema es como dices, en este caso los colores que a veces presentan este tipo de juegos, muchas gracias por la respuesta.

      Eliminar

Publicar un comentario

Temas relacionados

Entradas populares de este blog

tkinter Grid

Controles y Contenedores JavaFX 8 - I

Conectar SQL Server con Java

tkinter Canvas