OpenCV 3.1 (C++) sobre QT 5 en Linux - Detectar Rostros

OpenCV 3.1 (C++) sobre QT 5 en Linux - Detectar Rostros

En este tutorial explicaremos cómo detectar rostros mediante OpenCV 3.1 en C++ utilizando QT5 en Linux, además de explicar a grandes rasgos el funcionamiento del algoritmo.

Para llevar a cabo la detección de rostros se ha utilizado el algoritmo de Viola&Jones, que viene junto con OpenCV, y que nos permite detectar distintos tipos de objetos en imágenes.

En este caso utilizamos el clasificador haarcascade_frontalface_alt.xml que nos permite encontrar rostros que estén en la imagen de manera frontal.

Algoritmo

Este procedimiento de detección de objetos clasifica las imágenes basándose en el valor de las características simples. Hay muchas motivaciones para usar las características en lugar de los píxeles directamente. La principal es que los sistemas basados en características operan mucho más rápido que un sistema basado en píxeles, además estas características tienen la ventaja de ser invariantes a la iluminación y a la escala, además de ser muy robustas frente al ruido de la imagen.

El algoritmo busca en la imagen combinaciones tales como los siguientes patrones:

patrones haarcascade: a) características de borde, b) características de línea, c) características de centro

El algoritmo realiza un barrido tal como el que se muestra a continuación:

barrido en imagen

En cada iteración el algoritmo buscará en la imagen la combinación de los bloques que, si se juntan, se aproximan a un rostro:

haarcascade en acción

Creación del proyecto

Seleccionamos 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.

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

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

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = DetectarRostros-OpenCV  
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 y QFileDialog (para poder obtener el seleccionador de archivos) a nuestro proyecto en el archivo "mainwindow.h".

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QFileDialog>

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/objdetect.hpp>

#include <iostream>
#include <stdio.h>

namespace Ui {  
class MainWindow;  
}

class MainWindow : public QMainWindow  
{
    Q_OBJECT

public:  
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    void SeleccionarVideo();
    void ProcesarVideo(bool checked);

private slots:  
    void on_toolButton_clicked();

    void on_actionAbrir_Video_triggered();

    void on_play_toggled(bool checked);

private:  
    Ui::MainWindow *ui;
    cv::VideoCapture cap;
};

#endif // MAINWINDOW_H

Interfaz de usuario (UI)

El diseño siempre es a gusto del usuario, 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.

QT OpenCV Video Texto

Código de las funciones principales

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::CascadeClassifier face_cascade;
    face_cascade.load("/home/riclab/dev/opencv/opencv-3.1.0/data/haarcascades/haarcascade_frontalface_alt.xml");
    cv::Mat frame; // Frame como array multidimensional
    
    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);
        }
    }
    
    std::vector<cv::Rect> rostros;
    cv::Mat frame_gray;
    
    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
            
        cv::cvtColor(frame, frame_gray, cv::COLOR_BGR2GRAY);
        cv::equalizeHist(frame_gray, frame_gray);
        face_cascade.detectMultiScale(frame_gray, rostros, 1.1, 2, cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
        
        for (size_t i = 0; i < rostros.size(); i++) {
            cv::Rect rostro_i = rostros[i];
            cv::rectangle(frame, rostro_i, CV_RGB(0, 255, 0), 3);
            int pos_x = std::max(rostro_i.tl().x - 10, 0);
            int pos_y = std::max(rostro_i.tl().y - 10, 0);
            cv::putText(frame, "Rostro detectado", cv::Point(pos_x, pos_y), CV_FONT_HERSHEY_DUPLEX, 0.8, CV_RGB(0, 255, 0), 1.5);
        }
        
        cv::namedWindow("Reproductor", cv::WINDOW_KEEPRATIO); // Creamos una ventana la cual permita redimensionar
        cv::imshow("Reproductor", frame); // 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);
}

Cada método debe estar previamente definido en el/los headers. Para mayor detalle puede descargar el código fuente disponible aquí:

Descargar código en GitHub - QT OpenCV (C++) - Detectar-Rostros