CUDA 7.5 con OpenCV 3.1 (C++) sobre QT 5 en Linux - Flujo Óptico en Video

CUDA 7.5 con OpenCV 3.1 (C++) sobre QT 5 en Linux - Flujo Óptico en Video

Este método de visión por ordenador ignora por completo y tiene cero interés en la identificación de los objetos observados. Funciona mediante el análisis del movimiento individual de píxeles. Es útil para el seguimiento, análisis en 3D, medición de altitud y medición de la velocidad. Este método tiene la ventaja de que puede trabajar con cámaras de baja resolución, mientras que los algoritmos más simples requieren capacidad de procesamiento mínima.

El flujo óptico es un campo vectorial que muestra la dirección y la magnitud de estos cambios en la intensidad de una imagen a otra, como se muestra aquí:

Empleo del "Flujo Óptico" en la vida real

Seguimiento

El método de flujo óptico de seguimiento combina la sustracción de fondo y el flujo óptico. Al eliminar el fondo, todo lo que hay que hacer es analizar el movimiento de los píxeles en movimiento (si quiere saber más de la sustracción de fondo, por favor haga clic en el siguiente enlace: CUDA 7.5 con OpenCV 3.1 sobre QT 5 en Linux - Remover Fondo en Video).

Análisis de escenas en 3D

Mediante el análisis de movimiento de todos los píxeles, es posible generar mediciones 3D en bruto de la escena observada. Por ejemplo, en la imagen de abajo del tren subterráneo: los píxeles en el extremo izquierdo se mueven rápido, y ambos están convergiendo y desacelerándose hacia el centro de la imagen. Con esta información, la información 3D del tren se puede calcular (incluyendo la velocidad del tren y el ángulo de la vía).

Medición de la velocidad (con una cámara a altitud constante)

Mediante el análisis de la velocidad de píxeles, es posible analizar la velocidad de un objeto. Esta técnica es utilizada a menudo para medir la velocidad de vuelo y dirección de los insectos.

Requisitos

Los requerimientos para poder desarrollar este proyecto son tener instalado y configurado tanto Qt5 como OpenCV 3.1, además de la integración de CUDA. Para ello puedes seguir la guía que publiqué anteriormente: OpenCV 3.1 con CUDA 7.5 sobre QT 5 en Debian 8.

Creación del proyecto

Seguiremos los pasos estándar para la creación de un proyecto en QT, seleccionando crear un nuevo proyecto.

Configuramos el archivo .pro agregando las librerías necesarias para el correcto funcionamiento de OpenCV. Estas librerías son las correspondientes a OpenCV y CUDA:

INCLUDEPATH += /usr/local/include/opencv-3.1.0  
LIBS += `pkg-config opencv --libs --cflags`

En mi caso, el archivo en cuestión FlujoOptico-OpenCV-CUDA.pro queda de esta forma:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = FlujoOptico-OpenCV-CUDA  
TEMPLATE = app

INCLUDEPATH += /usr/local/include/opencv-3.1.0  
LIBS += `pkg-config opencv --libs --cflags`

SOURCES += main.cpp\  
        mainwindow.cpp

HEADERS  += mainwindow.h

FORMS    += mainwindow.ui

Headers

Agregamos los headers de OpenCV (incluyendo las librerías de CUDA para OpenCV) y QFileDialog (para poder obtener el seleccionador de archivos) a nuestro proyecto en el archivo mainwindow.h:

#include <QMainWindow>
#include <QFileDialog>

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/imgproc.hpp>

#include <opencv2/cudawarping.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
#include <opencv2/cudaoptflow.hpp>

Interfaz de usuario (UI)

El diseño se basa en los diseños anteriores. En todo caso, lo básico que debe tener la aplicación son dos "botones": uno configurado como checkable para el "play"/"parar" y otro para seleccionar el archivo, además de los "Radio Buttons" para identificar la entrada de vídeo.

Dejaré el código fuente completo por si presentan dudas.

Código de las funciones principales

Tal como en tutoriales anteriores, se omitieron los namespaces para fines académicos, ya que de esta forma el lector sabrá a qué clase corresponde cada método.

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget* parent)  
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()  
{
    delete ui;
}

/**
* Método para obtener la dirección del video
* @brief MainWindow::SeleccionarVideo
*/
void MainWindow::SeleccionarVideo()  
{
    // Declara la variable con la ruta del archivo
    QString archivo = QFileDialog::getOpenFileName(this, tr("Abrir Video"),
        "",
        tr("Videos (*.avi *.mp4 *.mov)"));
    // Agrega la ruta del archivo
    ui->labelVideo->setText(archivo);
    ui->radioVideo->setChecked(true);
}

/**
* Método para procesar el video frame a frame si checked==true
* @brief MainWindow::ProcesarVideo
* @param checked
*/
void MainWindow::ProcesarVideo(bool checked)  
{
    cv::destroyAllWindows(); // Para cerrar todas las ventanas
    cv::Mat frame, next, prvs, flujo_xy; // Frame como array multidimensional
    int s = 1;
    cv::cuda::GpuMat prvs_cuda, next_cuda, flujo_xy_cuda;
    cv::cuda::GpuMat prvs_cuda_o, next_cuda_o;
    cv::cuda::GpuMat prvs_cuda_c, next_cuda_c;
    
    if (!checked) { // Si !checked detiene el video, si no, lo procesa
        ui->play->setText("Iniciar video");
        cap.release();
    }
    else {
        ui->play->setText("Parar video");
        if (ui->radioVideo->isChecked()) { // Si el "radio button" está seleccionado ejecuta el video, si no, la webcam
            cap.open(ui->labelVideo->text().toStdString().c_str());
        }
        else {
            cap.open(0);
        }
        cap >> frame;
        prvs_cuda_o.upload(frame);
        cv::cuda::resize(prvs_cuda_o, prvs_cuda_c, cv::Size(frame.size().width / s, frame.size().height / s));
        cv::cuda::cvtColor(prvs_cuda_c, prvs_cuda, CV_BGR2GRAY);
    }
    
    // Dense optical flow
    cv::Ptr<cv::cuda::FarnebackOpticalFlow> fbOF = cv::cuda::FarnebackOpticalFlow::create();
    
    while (checked) // Bucle hasta que se presione "parar video"
    {
        cap >> frame; // Obtiene un nuevo frame del video o cámara
        if (frame.empty())
            break; // Detiene el bucle si el frame está vacío
        
        // CUDA upload, resize, color convert
        next_cuda_o.upload(frame);
        cv::cuda::resize(next_cuda_o, next_cuda_c, cv::Size(frame.size().width / s, frame.size().height / s));
        cv::cuda::cvtColor(next_cuda_c, next_cuda, CV_BGR2GRAY);
        fbOF->calc(prvs_cuda, next_cuda, flujo_xy_cuda);
        
        // Copiar el vector para dibujar el flujo
        cv::Mat cflujo;
        cv::resize(frame, cflujo, cv::Size(frame.size().width / s, frame.size().height / s));
        flujo_xy_cuda.download(flujo_xy);
        mostrarFlujo(flujo_xy, cflujo, 10, CV_RGB(0, 255, 0));
        
        cv::namedWindow("Reproductor", cv::WINDOW_KEEPRATIO); // Creamos una ventana que permita redimensionar
        cv::imshow("Reproductor", cflujo); // Se muestran los frames
        
        next_cuda.download(next);
        prvs_cuda.download(prvs);
        prvs_cuda = next_cuda.clone();
        
        char key = (char)cv::waitKey(20); // Espera 20ms por la tecla ESC
        if (key == 27)
            break; // Detiene el bucle
    }
}

void MainWindow::mostrarFlujo(const cv::Mat& flujo_xy, cv::Mat& cflujomap, int step, const cv::Scalar& color)  
{
    for (int y = 0; y < cflujomap.rows; y += step)
        for (int x = 0; x < cflujomap.cols; x += step) {
            cv::Point2f fxy;
            fxy.x = cvRound(flujo_xy.at<cv::Vec2f>(y, x)[0] + x);
            fxy.y = cvRound(flujo_xy.at<cv::Vec2f>(y, x)[1] + y);
            cv::line(cflujomap, cv::Point(x, y), cv::Point(fxy.x, fxy.y), color);
            cv::circle(cflujomap, cv::Point(fxy.x, fxy.y), 1, color, -1);
        }
}

void MainWindow::on_toolButton_clicked()  
{
    SeleccionarVideo();
}

void MainWindow::on_actionAbrir_Video_triggered()  
{
    SeleccionarVideo();
}

void MainWindow::on_play_toggled(bool checked)  
{
    ProcesarVideo(checked);
}

Código fuente

Descargar código en GitHub - CUDA QT OpenCV Texto en video