Reconocer objetos con OpenCV

Uno de los aspectos más importantes de la visión artificial es sin duda el reconocimiento de objetos, de patrones, o identificación de figuras y formas. Este reconocimiento puede ir desde ejemplos muy simples (reconocer en una imagen el único objeto de color rojo), hasta posibilidades muy complejas y útiles que aún hoy son prácticamente imposibles (como cámaras de aeropuerto que detecten terroristas automáticamente reconociendo su cara). Este problema plantea un gran reto, pero son infinitas sus posibilidades y aplicaciones. En un futuro se plantea que los robots humanoides tengan un sistema avanzado de reconocimiento de objetos, siendo capaz como los humanos de reconocer a las personas por sus caras, y los demás objetos de su entorno.

Reconocer objetos con OpenCV

En el artículo de hoy voy a explicar cómo reconocer objetos por su color. Estudiaremos el caso más simple, que consiste en un solo objeto del color deseado en un entorno de objetos de colores distintos. Por tanto no tendremos el problema de la segmentación (tener que detectar los distintos objetos del mismo color).

Planteamiento del problema:

Tenemos una cámara en nuestro NXT mirando hacia dos bolas de color, una roja y otra azul, tal como se presenta en la imagen siguiente. En nuestro caso estamos haciendo uso de la cámara de LEGO, pero puede tratarse de cualquier cámara.

Hacemos una foto con la cámara, donde se ven estas dos bolas, y la punta de nuestro robot que será de color verde. Todo esto se encuentra sobre un fondo blanco. La foto resultante quedaría así:

Reconocer objetos con OpenCV: Imagen
Como veis, la foto no tiene mucha calidad, por lo que la tarea de reconocer objetos por colores se complica. Esto nos ayudará a crear un programa mejor, que si nos resulta útil en este caso servirá en la gran mayoría de problemas de este estilo.

El problema consiste en:

1 – Abrir la imagen.
2 – Buscar todos los puntos rojos de la imagen.
3 – Calcular cuál sería el centro de nuestro objeto rojo.
4 – Buscar todos los puntos verdes de la imagen.
5 – Calcular cuál sería el centro de la punta del NXT.
6 – Calcular en que posición relativa y a que distancia se encuentra el objeto del NXT.
7 – Escribir estos datos en un archivo que posteriormente podemos subir al NXT para interpretarlos y actuar en consecuencia.

En resumen el problema consiste en detectar la bola, y saber dónde se encuentra con respecto al NXT. Para llegar a ese punto debemos preguntarnos previamente sobre la forma en la que se almacena la información de la imagen.

¿Qué es una imagen?:

Aunque una imagen es la reproducción de la figura de un objeto por la combinación de los rayos de luz que inciden en el, dentro de un ordenador una imagen no es más que una gran secuencia de ceros y unos que podemos leer y modificar a nuestro antojo. Como ya sabréis, el tamaño de las imágenes se mide en píxeles, que es precisamente la superficie homogénea más pequeña de las que componen una imagen, que se define por su brillo y color. Si cambiamos la resolución de nuestra pantalla veremos la misma imagen (de 600×400 píxeles) más pequeña o más grande.

Cada píxel está además definido por su color o brillo. Las imágenes en blanco y negro son las más básicas, pudiendo tomar sus píxeles valores entre 0 (completamente negro) y 255 (completamente blanco). Sin embargo las imágenes de color contienen tres canales de colores distintos (los famosos RGB (R : rojo, G : verde, B : Azul). Cada canal puede tomar un valor entre 0 y 255, por ejemplo un R = 255, G = 0, y B = 0 será un píxel completamente rojo. Si los tres valores son 255 el píxel será blanco, y si los tres valen 0 el píxel será negro. Existe además el sistema HSB (Hue, Saturation, Brightness (Matiz, Saturación, Luminosidad)), con el que también se puede crear cualquier color, y que tiene transformación directa a su representación en el sistema RGB:

Reconocer objetos con OpenCV: Colores
Por tanto, ¿cuántos valores hay en una imagen?: el cálculo es sencillo: altura x anchura x canales. Para recorrer una imagen en OpenCV se va de izquierda a derecha, y de arriba a abajo. Teniendo en cuenta además que los canales están ordenados en BGR (azul, verde y rojo), si quisiésemos acceder al valor rojo del píxel número 40 de la fila 24 tendríamos que acceder al dato: anchura_fila*23 + numero_canales*39 + 2, donde anchura_fila será la anchura de la imagen multiplicada por el número de canales. Está es la única forma de recorrer imágenes, y se implementa con bucles anidados como veremos posteriormente.

¿Cómo detectar un objeto de determinado color?:

Para detectar un objeto de determinado color necesitaremos detectar todos los píxeles que lo componen. Para ello iremos buscando en todos los píxeles de la imagen, calculando si son o no del color deseado. ¿Como sabemos si por ejemplo es rojo?. En primer lugar tenemos que ver qué datos característicos tiene dicho color. En este caso es sencillo, su dato más característico será que el canal con mayor valor será el del rojo. También deberíamos tener en cuenta que los valores de los demás canales no deberían ser muy altos, por lo menos no lo suficiente como para acercarse al valor del canal del rojo, ya que estos valores podrían corresponder a colores como el morado o el naranja.

Con un color que no sea de los tres básicos la cosa se complica un poco; en este caso lo mejor es abrir la imagen con un editor de imágenes (el Paint por ejemplo), y mirar que valores RGB tienen los píxeles del color deseado. Así podremos sacar las características que nos determinarán dicho color.

Una vez tengamos todos los píxeles del objeto podemos hacer varias cosas: podríamos cambiar de color los píxeles y mostrar la imagen resultante por pantalla para ver si ha detectado bien el objeto:

Reconocer objetos con OpenCV: Pelotas 2
Podemos además llevar un contador de cuantos píxeles llevamos, y una suma de sus posiciones, lo que nos serviría para calcular lo que sería el punto medio de nuestro objeto. Estás coordenadas del punto medio serán bastante precisas si contamos con una gran cantidad de píxeles del objeto, y nos será de gran utilidad para emplazar dicho objeto con respecto a nuestro robot NXT.

¿Cómo saber la posición del objeto respecto a nuestro robot?:

Teniendo el punto medio del objeto calculado anteriormente el proceso es sencillo. Ponemos a nuestro robot algo de color distintivo en la punta, como el ladrillo verde que hemos puesto al nuestro. Calculamos el punto medio de dicha punta verde, tal y como lo hemos hecho para la bola roja. Una vez tenemos las coordenadas sabemos si el objeto se encuentra a nuestra izquierda o derecha, y arriba o abajo de nuestro robot. Además podemos hallar la distancia al objeto (en píxeles) mediante la famosa formula:

Reconocer objetos con OpenCV: Distancia
Sabiendo toda está teoría ya nos podemos poner a programar.

Detección de un objeto rojo y cálculo de distancia respecto al robot:

Vamos a programar y resolver este problema con OpenCV, os recomiendo que os leáis previamente este artículo para saber como crear un proyecto de OpenCV y el manejo básico de una imagen. Iré poniendo el programa por partes y explicándo las líneas, pero recordad que en el programa tiene que ir todo seguido:

Includes:

#include

using namespace std;

Inicio del programa principal y definición de variables:

int main(int argc, char *argv[])
{
IplImage* img = 0;
int altura,anchura,anchura_fila,canales;
uchar *data;
int i,j;

Cargado de la imagen y recolección de los datos de la imagen:

img=cvLoadImage(argv[1]);
if(!img){
printf("No se ha podido cargar la imagen: %sn",argv[1]);
exit(0);
}

altura = img->height;
anchura = img->width;
anchura_fila = img->widthStep;
canales = img->nChannels;
data = (uchar *)img->imageData;
printf(“Procesando una imagen de %dx%d píxeles con %d canalesn”,
altura, anchura, canales);

7 a 10 – En OpenCV podemos acceder fácilmente a las características de la imagen. Gracias a estos datos podremos recorrer la imagen fácilmente.

11 – Cogemos los datos de la imagen y los metemos en un enorme array, que será el que recorreremos para acceder a los valores de los píxeles.

Creación de variables para el cálculo de los puntos medios, y creación de una ventana:

cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE);
cvMoveWindow("mainWin", 100, 100);

int x_cont = 0;
int y_cont = 0;

int x_total = 0;
int y_total = 0;

int x_v_cont = 0;
int y_v_cont = 0;

int x_v_total = 0;
int y_v_total = 0;

Recorrido de la imagen buscando los puntos de color verdes (punta Robot) y rojos (pelota):

for(i=0;i<altura;i++) for(j=0;j<anchura;j++) {
//verde oscuro
if ((data[i*anchura_fila+j*canales + 1] > 80) &&
!((data[i*anchura_fila+j*canales + 0] > data[i*anchura_fila+j*canales + 1]/2) ||
(data[i*anchura_fila+j*canales + 2] > data[i*anchura_fila+j*canales + 1]/2))){
printf("Punto verde en %d, %d, valor: %dn",j,i, data[i*anchura_fila+j*canales+2]);
data[i*anchura_fila+j*canales + 0]=255;
data[i*anchura_fila+j*canales + 1]=255;
data[i*anchura_fila+j*canales + 2]=255;
y_v_total = y_v_total + i;
x_v_total = x_v_total + j;
y_v_cont++;
x_v_cont++;
}

//rojo
if ((data[i*anchura_fila+j*canales + 2] > 80) &&
!((data[i*anchura_fila+j*canales + 0] > data[i*anchura_fila+j*canales + 2]/2) ||
(data[i*anchura_fila+j*canales + 1] > data[i*anchura_fila+j*canales + 2]/2))){
printf(“Punto rojo en %d, %d, valor: %dn”,j,i, data[i*anchura_fila+j*canales+2]);
data[i*anchura_fila+j*canales + 2]=0;
y_total = y_total + i;
x_total = x_total + j;
y_cont++;
x_cont++;
}
}

1 – El doble bucle anidado del que hablamos anteriormente, necesario para recorrer la imagen (de izquierda a derecha, y de arriba a abajo).

3 – Accedemos a cada color tal y como comenté en la sección ¿Qué es una imagen?:. En este caso el verde que está en el medio será el + 1. Con este valor umbral de 80 tenemos suficiente verde, por debajo sería ya demasiado oscuro, y por encima perderíamos píxeles verdes.

4 y 5 – Que los valores de azul y rojo sean claramente inferiores a los de verde es importante; con que sus valores no lleguen a la mitad del del verde es suficiente. La expresión booleana completa significa: Si el valor del canal verde es superior a 80, y el de los canales rojo y azul no supera ninguno de ellos la mitad de el del verde, entonces..

7 a 9 – Pintamos los píxeles de blanco, para que cuando mostremos la imagen veamos cuales ha detectado.

10 a 13 – Guardamos los datos para el posterior cálculo del punto medio.

17 a 26 – Repetimos el proceso con los píxeles rojos.

Cálculo del punto medio de los dos objetos, y la distancia entre ellos:

int x_medio = x_total / x_cont;
int y_medio = y_total / y_cont;

printf(“Punto medio rojo en %d, %d, valor: %dn”,x_medio, y_medio, data[i*anchura_fila+j*canales+2]);

int x_v_medio = x_v_total / x_v_cont;
int y_v_medio = y_v_total / y_v_cont;

printf(“Punto medio verde en %d, %d, valor: %dn”,x_v_medio, y_v_medio, data[i*anchura_fila+j*canales+1]);

int distancia = sqrt((x_medio – x_v_medio)^2 + (y_medio – y_v_medio)^2);

printf(“Distancia a la pelota: %d”, distancia);

1, 2, 6 y 7 – Cálculo de los puntos medios.

11 – Cálculo de la distancia entre dos puntos.

Escritura de la distancia en un archivo, mostrar la imagen y fin del programa:

ofstream fsalida("fichero.txt", ofstream::out);
fsalida << "Distancia: " << distancia;
fsalida.close();

cvShowImage(“mainWin”, img );

cvWaitKey(0);
cvReleaseImage(&img );
return 0;
}

1 a 3 – Así se abre, se escribe, y se cierra un fichero de texto.

Como siempre, os invitamos a que probéis el código y comentéis en el foro los problemas que os surjan.

2 thoughts on “Reconocer objetos con OpenCV

  1. uy! muchas gracias justo lo que estaba buscando 🙂