Flujo Óptico Lucas-Kanade con OpenCV
El flujo óptico es una técnica de análisis de imágenes que permite determinar el movimiento de un objeto dentro de una secuencia de imágenes, se puede aplicar en: detección de movimiento, seguimiento de objetos, compresión de video, estabilización de videos, etc., la biblioteca OpenCV implementa varios métodos para calcular el flujo óptico, en este tutorial veremos el algoritmo propuesto por Lucas-Kanade.
Método de Lucas Kanade con OpenCV
Este es implementado por la función cv::calcOpticalFlowPyrLK()
la misma trabaja con imágenes a escala de grises, debemos pasarle la imagen actual y la anterior, además es necesario proporcionarle una serie de puntos para los cuales se intentará calcular el flujo, la función devuelve las nuevas posiciones de dichos puntos junto con un código para cada que indica si ha sido posible detectar el flujo, los valores son 1 en caso de éxito y 0 de lo contrario.
Nuestra aplicación demostrativa utiliza la webcam para la captura de la secuencia de video en tiempo real, al iniciar el programa primero debes presionar una tecla cualquiera que no sea ESC, esto inicia la captura de los puntos de interés, luego mueves el objeto para ver el flujo.
#include <iostream>
#include <vector>
#include <deque>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
void main()
{
String window = "OpticalFlow :: Lucas-Kanade";
VideoCapture capture(0);
deque<vector<Point2f>> records;
vector<Point2f> points[2];
Mat prev_gray;
Scalar COLOR(255, 255, 0);
// crear la ventana
namedWindow(window);
// bucle de captura de video
while (true) {
// capturar el cuadro actual
Mat frame, gray;
capture >> frame;
// si no hay datos continuar
if (frame.empty()) continue;
// convertir a escala de grises
cvtColor(frame, gray, COLOR_BGR2GRAY);
// verificar si hay puntos anteriores
if (!points[0].empty())
{
vector<uchar> status;
// aseguarse de que la imagen anterior no esta vacia
if (prev_gray.empty()) {
gray.copyTo(prev_gray);
}
// calcular flujo optico con el metodo de Lucas-Kanade
calcOpticalFlowPyrLK(prev_gray, gray, points[0], points[1], status, cv::noArray());
// guardar los puntos obtenidos
records.push_front(points[1]);
// guardar solo un maximo de 10 conjuntos de puntos anteriores
if (records.size() >= 10) {
records.pop_back();
}
// dibujar los datos
for (size_t i = 0; i < points[1].size(); i++)
{
if (!status[i]) continue;
// dibujar el conjunto de lineas guardo previamente,
// estos representan el movimiento de los puntos.
for (size_t j = 0; j < records.size() - 1; j++)
line(frame, records[j][i], records[j + 1][i], COLOR, 1, LINE_AA);
// dibujar los puntos de interes
circle(frame, points[1][i], 3, COLOR, FILLED, LINE_AA);
}
}
// esperar por 30 ms ha que se presione una tecla
char key = (char)waitKey(30);
// si la tecla es ESC salir, cualquier otra captura los puntos caracteristicos.
if (key == 27) break;
else if (key != -1)
{
// obtener los puntos de interes
goodFeaturesToTrack(gray, points[1], 100, 0.01, 10);
// limpiar los puntos previos
records.clear();
}
// mostrar la imagen
imshow(window, frame);
// intercambiar los puntos, los actuales pasan a ser los anteriores.
std::swap(points[1], points[0]);
// intercambiar las imagenes, la actual es ahora la anterior.
cv::swap(prev_gray, gray);
}
}
Lo primero a tener en cuenta es que requerimos los puntos de interés, podemos crearlos manualmente usando el ratón, es decir, haciendo clic en cada punto y guardando dicha posición para luego proporcionársela al algoritmo, en este ejemplo usaremos la función goodFeatureToTrack(...)
para obtener dichos puntos, puedes ver como se utiliza la misma en detección de esquinas.
// esperar por 30 ms ha que se presione una tecla
char key = (char)waitKey(30);
// si la tecla es ESC salir, cualquier otra captura los puntos caracteristicos.
if (key == 27) break;
else if (key != -1)
{
// obtener los puntos de interes
goodFeaturesToTrack(gray, points[1], 100, 0.01, 10);
}
Los parámetros son: primero la imagen en donde ubicaremos los puntos, luego la variable en donde se almacenarán los mismos, seguido de la cantidad máxima de puntos a obtener, sigue el nivel de calidad y la distancia mínima entre dichos puntos.
Una vez tenemos los puntos, podemos aplicar el algoritmo de Lucas-Kanade, la variable points nos servirá para guardar los puntos obtenidos en el paso anterior, y la nueva ubicación de los mismos proporcionada por el siguiente método:
vector<Point2f> points[2];
vector<uchar> status;
// calcular flujo optico con el metodo de Lucas-Kanade
calcOpticalFlowPyrLK(prev_gray, gray, points[0], points[1], status, cv::noArray());
A esta función le pasamos las imágenes previas y la actual, en escala de grises, luego los puntos anteriores y el lugar donde guardar los nuevos, le sigue la variable en donde se indica si ha sido posible encontrar el flujo del punto, podemos configurar más parámetros pero en esta ocasión usaremos sus valores por defecto, para más detalles ver la documentación.
Para dibujar el flujo solo recorremos los puntos almacenados y dibujamos:
for (size_t i = 0; i < points[1].size(); i++)
{
if (!status[i]) continue;
// dibujar el conjunto de lineas guardo previamente,
// estos representan el movimiento de los puntos.
for (size_t j = 0; j < records.size() - 1; j++)
line(frame, records[j][i], records[j + 1][i], COLOR, 1, LINE_AA);
// dibujar los puntos de interes
circle(frame, points[1][i], 3, COLOR, FILLED, LINE_AA);
}
Finalmente mostramos la imagen, además almacenamos la imagen actual para utilizarla en la siguiente pasada, hacemos lo mismo con los puntos.
// mostrar la imagen
imshow(window, frame);
// intercambiar los puntos, los actuales pasan a ser los anteriores.
std::swap(points[1], points[0]);
// intercambiar las imagenes, la actual es ahora la anterior.
cv::swap(prev_gray, gray);
En siguientes tutoriales veremos otros métodos que podemos aplicar con OpenCV, luego estudiaremos algunas de las aplicaciones del flujo óptico, quizás estabilización de video.
Descargar proyecto: flujo-optico-lk.zip
Comentarios
Publicar un comentario