Sunday, September 26, 2010

Tip Octave

Cuando se carga una imagen en memoria usando imread se almacena como una matriz uint8, lo que significa que cada valor de la matriz es un número positivo de 1 byte, si se quiere trabajar con esos números hay que tener cuidado pues tomarán un valor máximo de 255.


octave:> img = imread("imagen.jpg");
octave:> img(1,1)
octave:> ans = 44
octave:> img(1,1)*1000
octave:> ans=255
octave:> %para evitar esto hay que hacer un casting
octave:> double(img(1,1))*1000
octave:> ans=44000
octave:>


Escribí esto aquí para que no se me olvide, pues me causó un gran dolor de cabeza mientras estaba probando un algoritmo de interpolación para un trabajo de la universidad.

Monday, September 20, 2010

Efecto para ser invisible, para variar en OpenCV

El efecto mostrado en el post anterior es muy fácil de programar:
1.- Sacar una foto al fondo
2.- Reemplazar todos los pixeles color piel por su pixel correspondiente en la imagen tomada en 1.
Para detectar la piel humana en el espacio de colores RGB:

char isSkinPixel(IplImage *img, int x, int y) {
unsigned char R,G,B;
B = img->imageData[y*img->widthStep+x*img->nChannels];
G = img->imageData[y*img->widthStep+x*img->nChannels+1];
R = img->imageData[y*img->widthStep+x*img->nChannels+2];
if( R > 95 && G > 40 && B > 20 && ( max(R,G,B) - min(R,G,B) ) > 15 && abs( (int)(R-G) ) > 15 && R>G && R>B ) {
return 1;
}
return 0; 
}

La condición que se usa para detectar el color piel la ví en un paper, pero no lo pude encontrar para dejarlo aquí.

La detección de piel humana funciona bien sólo si estamos capturando imágenes en un lugar con la luminosidad adecuada, pues si esta muy oscuro o muy iluminado el color de la piel se pierde. Además hay falsos positivos si el fondo no es el adecuado.

Código fuente del programa : invisible (necesita la biblioteca OpenCV)

Tuesday, August 24, 2010

Pastilla para hacerse invisible

Hoy al fin probé la famosa pastilla para hacerse invisible, es impresionante la rapidez con que hace efecto. Os dejo un video, haber si alguien puede explicarme cómo es que funciona esta revolucionaria mezcla de ingredientes exóticos.

Monday, August 2, 2010

Implementación de k-means en OpenCV



k-means o k-medias es un algoritmo de agrupamiento (clustering), lo que significa que es usado para dividir un conjunto de datos en grupos de tal forma que los datos pertenecientes a un grupo tengan propiedades en común. También se dice que este algoritmo es de aprendizaje no supervisado, pues es capaz de clasificar datos sin tener un supervisor que durante la etapa de entrenamiento le indique la clase a la que pertenece cada dato.
Se puede usar este algoritmo para segmentar una imagen por color, clasificando los pixeles que tienen colores parecidos en un mismo grupo.

k-means funciona de la siguiente manera:

  • Elige aleatoriamente k centros (uno por cada grupo)
  • Se etiqueta cada dato como perteneciente al grupo cuyo centro es más cercano
  • Se calcula el centro promedio de cada grupo y se repite el paso anterior.
  • El algoritmo se detiene cuando el promedio de cada centro de grupo no cambia o se alcanza el número máximo de iteraciones.

El centro de cada grupo y las distancias quedan definidos por el problema.
En el caso de las imagenes RGB se define el centro como una tripleta que contiene un valor para cada color:rojo, verde y azul. La distancia que se usará en este caso es la euclídea.
Sólo hay que imaginar los colores como una coordenada en el espacio 3D y la distancia entre dos puntos como el largo de la recta que los une.

El resultado del algoritmo dependerá de los centros iniciales, si estos son escogidos aleatoriamente probablemente se obtendrán resultados diferentes en cada ejecución.

OpenCV trae implementado este algoritmo, sin embargo yo programé mi propia implementación porque el algoritmo es sencillo y me pareció interesante hacerlo.
Un poco de código:

//calculo de centroides y matriz de clasificacion
for(it=0; it < iterations;it++) {
for(i=0;i<size;i++) {
red[i] = 0;
green[i] = 0;
blue[i] = 0;
count[i] = 0;
}
for(j=0; j < image->height; j++) {
for(i=0; i < image->width; i++) {
tcolor = getColor(image,i,j);
dist = distance(centroid[0], tcolor );
label = 0;
for(k=1; k<size; k++) {
temp = distance(centroid[k], tcolor );
if( temp < dist) {
dist = temp;
label = k;
}
}
++count[label];
red[label] += tcolor.r;
green[label] += tcolor.g;
blue[label] += tcolor.b;
if(it == (iterations-1)) {
labeled[i][j] = (uchar)label;
}
}
}
//actualizar centroides
for(i=0;i<size;i++) {
if(count[i]==0) continue; //no actualizar centroide si no se hay colores que promediar
centroid[i].r = (uchar)(red[i]/count[i]);
centroid[i].g = (uchar)(green[i]/count[i]);
centroid[i].b = (uchar)(blue[i]/count[i]);
}

}
//dejar con color de centroide mas cercano a cada pixel de la imagen a mostrar
for(i=0; i < image->width; i++) {
for(j=0; j < image->height; j++) {
image2->imageData[j*image->widthStep + i*image->nChannels + 2] = centroid[labeled[i][j]].r;
image2->imageData[j*image->widthStep + i*image->nChannels + 1] = centroid[labeled[i][j]].g;
image2->imageData[j*image->widthStep + i*image->nChannels ] = centroid[labeled[i][j]].b;
}
}

centroid[] : contiene un color por cada clase
size: cantidad de clases
labeled[][]: almacena los resultados de la clasificación, un número de 0 a (size-1) que relaciona cada pixel con su clase.
red[],green[],blue[],cout[]: variables usadas para calcular el color promedio

En el código anterior, dados los centroides de cada clase se procede a identificar cada pixel con la clase cuyo color se encuentre más cercano a su color.
Posteriormente se actualizan los centroides de cada clase, para ello se promedian los colores de los pixeles que fueron etiquetados como pertenecientes a la clase.
El proceso se repite hasta completar la cantidad de iteraciones especificada en la variable iterations.
Código fuente completo: kmeans-2010.tar.gz

Resultados al aplicar a la imagen superior izquierda el algoritmo con 5 clases y 90 iteraciones:

Tuesday, May 18, 2010

Filtro de Sobel en OpenCV

Una forma más elaborada de detectar bordes es el filtro de Sobel, dado que usa una mayor cantidad de píxeles de la vecindad para calcular el borde.

#include <opencv/highgui.h>
#include <opencv/cv.h>

void sobel(IplImage *img) {
int i,j,indice;
uchar *pixels = (uchar*)(img->imageData);
uchar *new_pixels = (uchar*)malloc(img->widthStep*img->height*img->nChannels*sizeof(uchar));

uchar sup;
uchar sup_izq;
uchar sup_der;
uchar inf;
uchar inf_izq;
uchar inf_der;
uchar izq;
uchar der;
int A;
int B;
double SUM;
//el origen esta en esquina superior izquierda del pantalla
for( i=1 ; i < (img->width-1) ; i++) {
for( j=1 ; j < (img->height-1) ; j++) {
indice = j*img->widthStep + i*img->nChannels;
sup = pixels[indice - img->widthStep]; //pixel superior al actual
inf = pixels[indice + img->widthStep]; //pixel inferior al actual
izq = pixels[indice - img->nChannels]; //pixel izq al actual
der = pixels[indice + img->nChannels]; //pixel der al actual
sup_izq = pixels[indice - img->nChannels - img->widthStep];
sup_der = pixels[indice + img->nChannels - img->widthStep];
inf_izq = pixels[indice - img->nChannels + img->widthStep];
inf_der = pixels[indice + img->nChannels + img->widthStep];
A = sup_izq + 2*sup + sup_der - inf_izq - 2*inf - inf_der;
B = sup_izq + 2*izq + inf_izq - sup_der - 2*der - inf_der;
SUM = A*A + B*B;
new_pixels[indice] = 255-(uchar)sqrt(SUM);

}
}
memcpy(pixels, new_pixels, img->widthStep*img->height*img->nChannels*sizeof(uchar));
free(new_pixels);

}

int main() {
CvCapture *cap = cvCreateCameraCapture(0);
IplImage *img;
IplImage *imgGray;
int notCreated=1;
while(1) {
img = cvQueryFrame(cap);
cvNamedWindow("hahaha",CV_WINDOW_AUTOSIZE);
if(notCreated) {
imgGray = cvCreateImage( cvSize(img->width,img->height), IPL_DEPTH_8U, 1 );
notCreated = 0;
}
cvCvtColor(img, imgGray, CV_RGB2GRAY);
sobel(imgGray);
cvShowImage("hahaha",imgGray);
char key = cvWaitKey(40);
if(key == 27)
break;
}
cvReleaseImage(&img);
cvReleaseImage(&imgGray);
cvReleaseCapture(&cap);
cvDestroyWindow("hahaha");
return 0;
}

Sunday, April 18, 2010

Efecto fotocopia en tiempo real

He estado jugando un poco con la librería de visión artificial de Intel, OpenCV. Programé el mismo efecto fotocopia que postie tiempo atrás, pero esta vez aplicandolo al video capturado por la webcam en tiempo real.
Esto no es difícil de lograr, sin embargo es interesante ver los resultados.

Aca el código:


#include <opencv/highgui.h>
#include <opencv/cv.h>

void fotocopia(IplImage *img, int difMax) {
int i,j,difh,difw,indice;
for( i=0 ; i < (img->width-1) ; i++) {
for( j=0 ; j < (img->height-1) ; j++) {
indice = j*img->widthStep + i*img->nChannels;
/*diferencia brillo en pixeles de una misma columna
widthStep es el tamaño de una fila en bytes,
ya que la imagen esta en un arreglo unidimensional.
Al sumar widthStep estamos pasando a la siguiente fila
*/
difh = ( (uchar*)(img->imageData) )[indice] - ( (uchar*)(img->imageData) )[indice+img->widthStep];
if( difh < 0) {
difh = difh * -1;
}

//diferencia brillo en pixeles de una misma fila

difw = ( (uchar*)(img->imageData) )[indice] - ( (uchar*)(img->imageData) )[indice+1];
if( difw < 0) {
difw = difw * -1;
}

//si hay mucha diferencia, es un contorno, hay que pintar el pixel negro
if ( (difh + difw) > difMax) {
( (uchar*)(img->imageData) )[indice] = 0 ;
} else {
( (uchar*)(img->imageData) )[indice] = 255 ;
}
}
}
}

int main() {
CvCapture *cap = cvCreateCameraCapture(0);
IplImage *img;
IplImage *imgGray;
cvNamedWindow("hahaha",CV_WINDOW_AUTOSIZE);
img = cvQueryFrame(cap);
imgGray = cvCreateImage( cvSize(img->width,img->height), IPL_DEPTH_8U, 1 );
while(1) {
img = cvQueryFrame(cap);
cvCvtColor(img, imgGray, CV_RGB2GRAY);
fotocopia(imgGray,5);
cvShowImage("hahaha",imgGray);
char key = cvWaitKey(40);
if(key == 27)
break;
}
cvReleaseCapture(&cap);
cvReleaseImage(&img);
cvReleaseImage(&imgGray);
cvDestroyWindow("hahaha");
}

Para compilar:

gcc `pkg-config --cflags opencv --libs opencv` fotocopia.c -o fotocopia

Aca un video de cómo se queda el efecto:

Thursday, April 1, 2010

tarjeta de video Intel GMA X4500HD Ubuntu

Problema:

Cuando abría alguna aplicación que hiciera uso de OpenGL esta veía con manchas negras o se pegaba y al ejecutar dmesg me aparecía lo siguiente:

[ 210.430986] [drm:i915_gem_object_pin_and_relocate] *ERROR* Relocation beyond target object bounds: obj f08cb9c0 target 23 delta 4096 size 4096.
[ 210.430992] [drm:i915_gem_execbuffer] *ERROR* Failed to pin buffers -22
[ 210.445817] [drm:i915_gem_object_pin_and_relocate] *ERROR* Relocation beyond target object bounds: obj f0a39180 target 23 delta 4096 size 4096.
[ 210.445827] [drm:i915_gem_execbuffer] *ERROR* Failed to pin buffers -22

....

Usando Ubuntu 9.10 the Karmic Koala con el paquete xserver-xorg-video-intel versión:
2:2.9.1~git-0ubuntu0tormod

Solución :

apt-get install libgl1-mesa-dri
Con lo cual se actualizaron los siguientes paquetes:
libdrm-intel1 a la versión 2.4.15~git-0ubuntu0tormod~jaunty
libgl1-mesa-dri a la versión 7.7.0~git20091114.57f40b18-0ubuntu0sarvatt~jaunty

La solución la encontré aquí.