Flujo óptico Gunnar Farneback

El en tutorial anterior estudiamos el flujo óptico, vimos que es y mencionamos algunas de sus aplicaciones, además construimos una pequeña aplicación usando el método creado por Lucas-Kanade, esta vez veremos la función calcOpticalFlowFarneback(...) la cual calcula el flujo óptico mediante el algoritmo propuesto por Gunnar Farneback en el año 2003, este método a diferencia del anterior calcula el flujo para todos los puntos en la imagen.

Para aplicar el algoritmo requerimos la imagen original y la anterior, ambas a escala de grises, este método puede ser más lento que el anterior al tener que calcular el flujo para todos los puntos en comparación con el anterior que utiliza un conjunto de puntos definido, el resultado es un arreglo de dos dimensiones que contiene los vectores que representan el desplazamiento de cada punto.

OpenCV flujo óptico por Gunnar Farneback

La función está definida de la siguiente manera:

void cv::calcOpticalFlowFarneback( 
 InputArray   prev,
 InputArray   next,
 InputOutputArray  flow,
 double  pyr_scale,
 int  levels,
 int  winsize,
 int  iterations,
 int  poly_n,
 double  poly_sigma,
 int  flags 
)

Loa parámetros prev, next, flow, establecen la imagen anterior, la imagen siguiente y el objeto que almacenará el resultado, este último es un arreglo de dos dimensiones que contiene los vectores que representan el flujo para cada punto, visita la documentación para obtener la descripción de los otros parámetros de la función cv::calcOpticalFlowFarneback.

Código de ejemplo, para el calculo de flujo óptico en tiempo real usando la webcam.

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

using namespace cv;
using namespace std;

// Function to compute the optical flow map
void drawOpticalFlow(const Mat& flowImage, const Mat& flowImageGray)
{
    int stepSize = 16;
    Scalar color = Scalar(0, 255, 0);

    // Draw the uniform grid of points on the input image along with the motion vectors
    for (int y = 0; y < flowImageGray.rows; y += stepSize)
    {
        for (int x = 0; x < flowImageGray.cols; x += stepSize)
        {
            // Circles to indicate the uniform grid of points
            circle(flowImageGray, Point(x, y), 1, color, FILLED);

            // Lines to indicate the motion vectors
            const Point2f& pt = flowImage.at<Point2f>(y, x);
            line(flowImageGray, Point(x, y), Point(cvRound(x + pt.x), cvRound(y + pt.y)), color);
        }
    }
}

void main()
{
    String window = "OpticalFlow :: Farneback";
    VideoCapture capture(0);
    Mat prev_gray;

    // crear la ventana
    namedWindow(window);

    // bucle de captura de video
    while (true) {

        Mat frame, gray, flow;

        // capturar el cuadro actual
        capture >> frame;

        // si no hay datos continuar
        if (frame.empty()) continue;

        // escalar a la mitad, para mejorar rendimiento
        resize(frame, frame, Size(), 0.6, 0.6, INTER_LINEAR);

        // convertir a escala de grises
        cvtColor(frame, gray, COLOR_BGR2GRAY);

        if (!prev_gray.empty())
        {
            calcOpticalFlowFarneback(prev_gray, gray, flow, 0.5, 3, 15, 3, 5, 1.1, 0);
            drawOpticalFlow(flow, frame);
        }

        // esperar por 30 ms ha que se presione una tecla
        if(waitKey(30) == 27) break;

        // mostrar la imagen
        imshow(window, frame);

        // intercambiar las imagenes, la actual es ahora la anterior.
        cv::swap(prev_gray, gray);
    }
}

Las partes más relevantes de nuestro código:

Mat frame;
capture >> frame;

Capturamos la imagen de la cámara.

resize(frame, frame, Size(), 0.6, 0.6, INTER_LINEAR);
cvtColor(frame, gray, COLOR_BGR2GRAY);

Primero reducimos el tamaño de la imagen capturada a un 60%, esto simplemente para aumentar el rendimiento, no es estrictamente necesario, luego cambiamos la imagen a escala de grises.

calcOpticalFlowFarneback(prev_gray, gray, flow, 0.5, 3, 15, 3, 5, 1.1, 0);
drawOpticalFlow(flow, frame);

Calculamos el flujo óptico y dibujamos el resultado mediante la función drawOpticalFlow(...), esta la hemos creado nosotros y se define de esta forma:

void drawOpticalFlow(const Mat& flowImage, const Mat& flowImageGray)
{
    int stepSize = 16;
    Scalar color = Scalar(0, 255, 0);

    // Draw the uniform grid of points on the input image along with the motion vectors
    for (int y = 0; y < flowImageGray.rows; y += stepSize)
    {
        for (int x = 0; x < flowImageGray.cols; x += stepSize)
        {
            // Circles to indicate the uniform grid of points
            circle(flowImageGray, Point(x, y), 1, color, FILLED);

            // Lines to indicate the motion vectors
            const Point2f& pt = flowImage.at<Point2f>(y, x);
            line(flowImageGray, Point(x, y), Point(cvRound(x + pt.x), cvRound(y + pt.y)), color);
        }
    }
}

Lo que debemos hacer es recorrer la imagen devuelta por el algoritmo, marcaremos los puntos en una rejilla de 16 pixeles, cada punto de la rejilla lo dibujamos usando la función circle(...) a parte también dibujamos una línea para representar el vector del flujo, para esto usamos la función line(...).

Flujo óptico Gunnar Farneback

Si te resulta un poco lento y deseas acelerar el proceso puedes probar una la T-API para aumentar el rendimiento mediante OpenCL, si tienes el hardware que lo soporte, para un ejemplo puedes ver Tutorial OpenCV + OpenCL.

De momento es todo, nos vemos en la siguiente publicación.

Descargar código: flujo-óptico-gf.zip

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Histogramas OpenCV Python

Python Binance API