Extracción de puntos característicos (keypoint)

Los puntos característicos de una imagen, también llamados keypoints en inglés, son aquellas puntos que son fácilmente diferenciables en una imagen, para extraer dichos puntos OpenCV cuenta con diversas clases que implementan los algoritmos más conocidos que han sido desarrollados para este propósito, los mismos se encentran en el módulo cv::features2d algunas de ellas son AKAZE, BRISK, ORB, etc., otros algoritmos que requieren licencia los puedes encontrar en el módulo opencv_contrib, más específicamente cv::xfeatures2d podemos usar SURF, FREAK, SIFT, entre otros.

detección de puntos de interés

Para facilitar el uso de estos algoritmos se utiliza la clase abstracta cv::Features2D la cual sirve de base para todos los algoritmos antes mencionados.

clase Feature2D OpenCV

Vemos nuestro primer código de ejemplo:

#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\features2d.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv )
{
    Mat image = imread("../opencv-keypoints/lena.jpg", IMREAD_GRAYSCALE);

    if (image.empty())
    {
        printf("No image data.");
        getchar();

        return -1;
    }

    Ptr<Feature2D> detect = BRISK::create();

    vector<KeyPoint> kp;
    detect->detect(image, kp);

    Mat result;
    drawKeypoints(image, kp, result, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

    imshow("OpenCV :: " + detect->getDefaultName(), result);
    waitKey(0);

    return 0;
}

Obtenemos un puntero a un objeto de la clase BRISK la cual implemente el algoritmo del mismo nombre, lo hacemos a través del método:

Ptr<Feature2D> detect = BRISK::create();

Luego usamos el método detect(...) para obtener el listado de los KeyPoint de nuestra imagen de entrada, esta clase guarda toda la información correspondiente a nuestro punto característico, coordenadas del punto, ángulo, etc.

vector<KeyPoint> kp;
detect->detect(image, kp);

Finalmente para visualizar los puntos encontrados usaremos la función cv::drawKeypoints(...) en la cual indicamos la imagen de entrada, el conjunto de puntos que deseamos dibujar, la imagen de salida, el color a utilizar (-1 indica colores aleatorios) y el modo como deseamos dibujar los puntos puedes probar las opciones de DrawMatchesFlags.

Mat result;
drawKeypoints(image, kp, result, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

El resultado:

OpenCV ORB

Otro ejemplo, ahora con la clase AKAZE.

OpenCV AKAZE

Puedes probar todas loas otras clases disponibles y ver los resultados.

Pareo de Keypoints

Una vez tenemos los puntos característicos de nuestra imagen podemos intentar localizar estos puntos en una segunda imagen, con ello podremos por ejemplo: combinar imágenes, realizar seguimiento y detección de una figura plana conocida, hacer aplicaciones de realidad aumentada, entre otras cosas.

Lo primero que necesitamos es obtener un descriptor para cada punto que hemos obtenido, este descriptor nos permite definir de manera sencilla un keypoint, luego de obtener los correspondientes descriptores solo los comparamos para obtener los más parecidos entre sí.

#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\features2d.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv )
{
    Mat imageA = imread("../opencv-keypoints/box.png", IMREAD_GRAYSCALE);
    Mat imageB = imread("../opencv-keypoints/box_in_scene.png", IMREAD_GRAYSCALE);

    if (imageA.empty() || imageB.empty())
    {
        printf("No image data.");
        getchar();

        return -1;
    }

    Ptr<Feature2D> detect = BRISK::create();

    vector<KeyPoint> kpA, kpB;
    Mat descA, descB;

    detect->detectAndCompute(imageA, noArray(), kpA, descA);
    detect->detectAndCompute(imageB, noArray(), kpB, descB);

    vector<DMatch> matches;

    Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING, true);
    matcher->match(descA, descB, matches);

    sort(matches.begin(), matches.end());
    matches.erase(matches.begin() + 35, matches.end());

    Scalar color = Scalar::all(-1);
    int flags = DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS;

    Mat result;
    drawMatches(imageA, kpA, imageB, kpB, matches, result, color, color, vector<char>(), flags);

    imshow("OpenCV :: Match Keypoints", result);
    waitKey(0);

    return 0;
}

Usaremos el método detectAndCompute(...) para detectar los puntos y generar los descriptores para cada uno de ellos, también puedes usar detect(...) para ubicar los puntos y luego compute(...) para calcular los respectivos descriptores.

Ptr<Feature2D> detect = BRISK::create();

vector<KeyPoint> kpA, kpB;
Mat descA, descB;

detect->detectAndCompute(imageA, noArray(), kpA, descA);
detect->detectAndCompute(imageB, noArray(), kpB, descB);

Nótese que calculamos los puntos característicos y descriptores para ambas imágenes, los segundos son almacenados en un objeto cv::Mat.

Luego utilizamos BFMatcher (BruteForceMatcher) para comparar los descriptores de cada punto y obtener el pareo para cada uno de ellos, la clase base para los comparadores es DescriptorMatcher, al utilizar el método match(...) obtenemos una lista de DMatch.

vector<DMatch> matches;

Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING, true);
matcher->match(descA, descB, matches);

Antes de visualizar el pareo vamos a tratar de filtrar aquellos que son correctos, para esto ordenamos en base al campo DMatch.distance de DMatch, el cual indica el nivel de similitud que existe entre un punto y su correspondiente, de modo que entre menor sea este valor mayor probabilidad de que sea bueno, por ello ordenamos y luego nos quedamos con los primeros 35 elementos.

sort(matches.begin(), matches.end());
matches.erase(matches.begin() + 35, matches.end());

Ahora ya podemos visualizar el resultado de una mejor manera, para esta terea usamos el método cv::drawMatches(...) le indicamos la imagen inicial y sus puntos y luego la segundo con sus puntos, seguimos con el pareo y luego la imagen de salida, los últimos parámetros con para configurar la salida, colores y modo de dibujo.

Scalar color = Scalar::all(-1);
int flags = DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS;

Mat result;
drawMatches(imageA, kpA, imageB, kpB, matches, result, color, color, vector<char>(), flags);

Con esto tenemos el siguiente resultado:

OpenCV BFMatch

Con esto terminamos por hoy, en próximos tutoriales veremos algunas aplicaciones de lo aprendido, puede ser detección y seguimiento de objetos planos y realidad aumentada.

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Histogramas OpenCV Python

Conectar SQL Server con Java