CUDA 7.5 con OpenCV 3.1 (C++) sobre QT 5 en Linux - Remover Fondo en Video

CUDA 7.5 con OpenCV 3.1 (C++) sobre QT 5 en Linux - Remover Fondo en Video

En el siguiente tutorial removeremos el fondo estático de un vídeo, mostrando solo el movimiento de los objetos.

Introducción

La remoción de fondo es el método de eliminación de píxeles que no se mueven, al centrarse sólo en los objetos que lo hacen. A grandes rasgos, el método funciona así:

  1. Capturar dos frames (fotogramas).
  2. Comparar los colores de los píxeles en cada fotograma.
  3. Si los colores son los mismos, reemplazar con otro.
  4. Mantener el nuevo píxel.

Aquí está un ejemplo de un tipo que se mueve con un fondo estático. Algunos píxeles no parecen cambiar cuando se mueve, lo que resulta en un error:

El problema con este método es que si el objeto deja de moverse, entonces se vuelve "indetectable". Si mi mano se mueve, pero mi cuerpo no lo hace, todo lo que ves es una mano en movimiento. También existe la posibilidad de que, a pesar de que algo se mueve, no todos los píxeles individuales cambien de color debido a que el objeto es de un color uniforme. Para corregir esto, este algoritmo se debe combinar con otros algoritmos tales como la detección de bordes.

Hay otra forma de sustracción de fondo llamada blue-screening (o green-screening, o chroma-key). Lo que se hace es físicamente reemplazar el fondo por un color sólido; sin embargo, en este caso no veremos dicho método.

Requisitos

Los requerimientos son tener instalado y configurado Qt5, OpenCV 3.1 con 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.

Creación de un nuevo proyecto QT5

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 <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

#include <opencv2/opencv.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/core.hpp>
#include <opencv2/cudabgsegm.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 "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::Ptr<cv::cuda::BackgroundSubtractorMOG2> MOG2_cuda = cv::cuda::createBackgroundSubtractorMOG2();
    cv::Mat Mascara_Mog;
    cv::cuda::GpuMat Mascara_Mog_cuda;
    cv::Mat frame; // Frame como array multidimensional
    cv::cuda::GpuMat frame_cuda;
    
    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);
        }
    }
    
    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
            
        frame_cuda.upload(frame);
        MOG2_cuda->apply(frame_cuda, Mascara_Mog_cuda, -1);
        Mascara_Mog_cuda.download(Mascara_Mog);
        
        cv::namedWindow("Reproductor", cv::WINDOW_KEEPRATIO); // Creamos una ventana que permita redimensionar
        cv::imshow("Reproductor", Mascara_Mog); // Se muestran los frames
        
        char key = (char)cv::waitKey(20); // Espera 20ms por la tecla ESC
        if (key == 27)
            break; // Detiene el bucle
    }
}

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 Sacar Fondo de video