En este tutorial explicaremos como detectar rostros mediante OpenCV 3.1 en C++ utilizando QT5 en Linux, ademas 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 mas rápido que un sistema basado en pıxel, ademas estas características tienen la ventaja de ser invariantes a la iluminación y a la escala, ademas 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 linea, c) características de centro

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 TextoEnVideo-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 header 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" con uno configurado como checkable para el "play", "parar" y otro para seleccionar el archivo, ademas de los "Radio Buttons" para identificar la entrada de vídeo.
Dejare 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 que 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;
}
/**
* Metodo para obtener la direccion 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);
}
/**
* Metodo para procesar el video frame a frame si ckecked==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 multidimencional
    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" esta 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 precione "parar video"
    {
        cap >> frame; // obtiene un nuevo frame del video o camara
        if (frame.empty())
            break; // detiene el bucle si elframe esta vacio
        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 redimencionar
        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í: