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:

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

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:

Creación del proyecto
Seleccionamos crear un nuevo proyecto.

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.

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