OpenCV 3.1 (C++) sobre Qt 5 en Linux - Detectar Textos y Letras en Video
Este tutorial consiste en detectar los caracteres o textos presentes en una imagen o video (de un archivo o por medio de la webcam), abarcando el paso previo a los sistemas OCR que, a partir de una imagen capturada, son capaces de detectar el texto dentro de una imagen y convertirlo a un formato de texto editable, o aplicaciones más sofisticadas como la recientemente adquirida por Google, Word Lens, la cual está integrada en su aplicación Google Translate y es capaz de traducir texto en tiempo real con solo enfocar la cámara a la imagen a tratar.
Requisitos
Los requerimientos son tener instalado y configurado tanto Qt 5 como OpenCV 3.1. Para ello puedes seguir la guía que publiqué anteriormente: OpenCV 3.1 sobre Qt 5 en Debian
Descripción del proyecto
Detectar texto en tiempo real tanto en video como en imágenes, utilizando las librerías de OpenCV 3.1.
Creación del proyecto
Seleccionamos crear un nuevo proyecto.
Creación de un nuevo proyecto Qt 5

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 DetectarTexto-OpenCV.pro queda de esta forma:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = DetectarTexto-OpenCV
TEMPLATE = app
INCLUDEPATH += /usr/local/include/opencv-3.1.0
LIBS += `pkg-config opencv --libs --cflags` -lopencv_text
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.
#include <QMainWindow>
#include <QFileDialog>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/text.hpp>
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 "iniciar" y "parar" el video, y otro para seleccionar el archivo, además de los Radio Buttons para identificar la entrada de video.
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::Ptr<cv::text::ERFilter> er_filter1 = cv::text::createERFilterNM1(cv::text::loadClassifierNM1(QString("/home/riclab/dev/opencv/opencv_contrib/modules/text/samples/trained_classifierNM1.xml").toStdString().c_str()), 16, 0.00015f, 0.13f, 0.2f, true, 0.1f);
cv::Ptr<cv::text::ERFilter> er_filter2 = cv::text::createERFilterNM2(cv::text::loadClassifierNM2(QString("/home/riclab/dev/opencv/opencv_contrib/modules/text/samples/trained_classifierNM2.xml").toStdString().c_str()), 0.5);
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);
}
}
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
std::vector<cv::Mat> channels;
cv::text::computeNMChannels(frame, channels);
int cn = (int)channels.size();
for (int c = 0; c < cn - 1; c++)
channels.push_back(255 - channels[c]);
std::vector<std::vector<cv::text::ERStat> > regions(channels.size());
for (int c = 0; c < (int)channels.size(); c++) {
er_filter1->run(channels[c], regions[c]);
er_filter2->run(channels[c], regions[c]);
}
std::vector<std::vector<cv::Vec2i> > region_groups;
std::vector<cv::Rect> groups_boxes;
cv::text::erGrouping(frame, channels, regions, region_groups, groups_boxes, cv::text::ERGROUPING_ORIENTATION_HORIZ);
groupsDraw(frame, groups_boxes);
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::groupsDraw(cv::Mat& src, std::vector<cv::Rect>& groups)
{
for (int i = (int)groups.size() - 1; i >= 0; i--) {
if (src.type() == CV_8UC3) {
cv::rectangle(src, groups.at(i).tl(), groups.at(i).br(), cv::Scalar(0, 255, 255), 1, 8);
}
else {
cv::rectangle(src, groups.at(i).tl(), groups.at(i).br(), cv::Scalar(255), 1, 8);
}
}
}
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 - Detectar Textos y Letras en Video