martes, 3 de junio de 2008
jueves, 7 de febrero de 2008
Filtro Blob
El algoritmo de blobs se aplicará sobre una imagen ya binarizada con anterioridad.
El fin de la aplicación del algoritmo es buscar el blob que se ajuste más a nuestras necesidades: un blob con una forma determinada, que siga un parámetro de comportamiento determinado, etc, gracias al análisis de los blobs podemos obtener información del entorno y del objetivo al que Rabotron deberá seguir, así como su posible comportamiento en el futuro.
Vamos a pasar a la explicación del algoritmo:
El algoritmo es realmente sencillo, simplemente clasifica, como ya hemos dicho anteriormente, un píxel con respecto a sus vecinos creando los blobs (o grupos de objetos encontrados), por lo tanto recorremos la imagen píxel a píxel calculando un código de ponderación de cada píxel con respecto a sus vecinos y aplicando la función correspondiente según el resultado obtenido en dicho código.
Lo primero que haremos será crearnos una matriz cuadrada del tamaño de la imagen, para almacenar en ella el valor del blob al que pertenece cada pixel, por ejemplo (si la imagen fuese de 10x10 píxeles):
1 | 1 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
1 | 1 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
1 | 1 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
1 | 1 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
1 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 | 3 |
2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 |
2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 |
2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 |
2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
La ponderación aplicada a los vecinos será con una mascará de 2x2:
pixel * 1
pixel * 2
pixel * 4
Pixel examinado * 8
code = Pixel(x-1, y-1) + 2*Pixel(x, y-1) + 4*src.Pixel(x-1, y) + 8*Pixel(x, y)
Al utilizar una matriz de 2x2, los píxeles situados al borde de la imagen binarizada dejan elementos de la matriz fuera de la imagen. Para evitar esto, se rodea la imagen original con un borde de un píxel negro.
A la derecha de cada cuadro se indica el tipo de operación que se aplicará cuando se encuentre una situación con el código indicado (a la izquierda):
- Operaciones de tipo I: indican operaciones donde se explora un punto negro, que se incorpora a los puntos del fondo de la imagen (no forma parte de ningún blob).
- Operaciones de tipo II: indican operaciones donde se explora un punto blanco, y alrededor no hay puntos blancos con quienes conectarlo, con lo que se iniciará en él un nuevo blob.
- Operaciones de tipo III: indican operaciones donde se explora un punto blanco, teniendo puntos blancos en alguna casilla de alrededor (P2, P3 o P4), con lo que se añadirá el nuevo punto blanco al blob que se hubiera formado previamente con estos.
- Operaciones de tipo IV: indica un caso particular de las operaciones de tipo III, donde el nuevo punto blanco detectado conecta dos blobs que antes se consideraban distintos. Lo que se hace es unir los dos blobs en uno solo, añadiendo a este nuevo blob el nuevo punto explorado.
El código java correspondiente a la clasificación del píxel examinado a un blob:
if(code<8)
regiones[x][y] = 0;
else if (code == 8 || code == 9) {
blob = new Blob();
blob.lista_x.add(x);
blob.lista_y.add(y);
lista_blobs.add(blob);
regiones[x][y] = lista_blobs.size();
}else if (code == 10 || code == 11 || code == 15) {
blob = (Blob) lista_blobs.get(regiones[x][y - 1] - 1);
blob.lista_x.add(x);
blob.lista_y.add(y);
regiones[x][y] = regiones[x][y - 1];
}else if (code == 12 || code == 13) {
blob = (Blob) lista_blobs.get(regiones[x - 1][y] - 1);
blob.lista_x.add(x);
blob.lista_y.add(y);
regiones[x][y] = regiones[x - 1][y];
} else if (code == 14) {
if (regiones[x][y - 1] != regiones[x - 1][y])
fusiona(regiones[x][y - 1], regiones[x - 1][y], regiones, lista_blobs);
blob = (Blob) lista_blobs.get(regiones[x][y - 1] - 1);
blob.lista_x.add(x);
blob.lista_y.add(y);
regiones[x][y] = regiones[x][y - 1];
}
Este proceso finalmente nos devuelve un listado de blobs de los que podemos extraer variar propiedades, entre la que destacaremos el uso del centroide.
Nos hemos creado una clase Blob, donde se almacenan los datos de cada blob en marticular, concretamente para cada blob tenemos almacenado:
- Lista de coordenada X de cada píxel perteneciente al blob --> lista_x
- Lista de coordenada Y de cada píxel perteneciente al blob --> lista_y
- Coordenada X del centroide del blob -->centro_x
- Coordenada Y del centroide del blob --> centro_y
- Variable boolena que indica si es un blob válido (si posee un número mínimo de píxeles) --> valido
La función fusiona(blob1, blob2, regiones, lista_de_blobs) junta dos bloques de píxeles pertenecientes a dos blob distintos en un mismo blob.
private void fusiona(int blob1, int blob2, int[][]regiones, ArrayList lista_blobs) {
Blob b1 = (Blob) lista_blobs.get(blob1 - 1);
Blob b2 = (Blob) lista_blobs.get(blob2 - 1);
int l2_length = b2.lista_x.size();
int[] l2x_array = b2.lista_x.toArray();
int[] l2y_array = b2.lista_y.toArray();
for (int i = 0; i <>
regiones[l2x_array[i]][l2y_array[i]] = blob1;
b1.lista_x.append(b2.lista_x);
b2.lista_x.free();
b1.lista_y.append(b2.lista_y);
b2.lista_y.free();
}
Como ejemplo de detección de blob vamos a poner una impresión de pantalla en la que pintamos (de verde) el blob mas grande detectado con el mismo color que existe justo en el punto central de la imagen.
Para este ejemplo de uso del algoritmo de detección de blob usamos como criterio para binarizar la imagen el color detectado en el punto central de la imagen, podemos observar como en la imagen binarizada aparecen varias zonas blancas distantes, gracias a la separación en blobs podemos ignorar las zonas que no nos interesan y quedarnos con el blob mas grande de la imagen (pintado de verde), que casualmente coincide con el blob central de la imagen en este caso.
Prueba 1:
Prueba 2:
martes, 5 de febrero de 2008
Filtro K-means (K-Medias)
La función del algoritmo kmeans es clusterizar la imagen en K grupos distintos, entiéndase por cluster: agrupación de elementos similares. El proceso de Clusterización (Clustering) consiste en la división de los datos en grupos de objetos similares (en nuestro caso particular esa similitud se hará entre los píxeles de la imagen). Para calcular la similitud entre píxeles se suele utilizar diferentes algoritmos de distancia: distancia euclídea, de Manhatan, de Mahalanobis, etc.
El nombre de kmeans (kmedias) viene porque representa cada uno de los clusters por la media (o media ponderada) de sus puntos, es decir, por su centroide. La representación mediante centroides tiene la ventaja de que tiene un significado gráfico y estadístico inmediato .
En el filtro kmeans aplicado en nuestro proyecto robótico agruparemos cada sector de la imagen en k cluster, para ello usaremos la imagen en color RGB y creamos tantos cluster distintos como le indiquemos al algoritmo (si indicamos solo un cluster todos los píxeles pertenecerán al mismo sector, con dos cluster el algoritmo agrupará los píxeles en dos grupos distintos, y así sucesivamente). Mediante este algoritmo perdemos gran cantidad de detalles en la imagen, pero simplificaremos el procesamiento de los misma.
Para comenzar el algoritmo inicializa los valores de las clases de manera que estos queden repartidos uniformente.
public void buscaValoresColor(int clases, int[]valoresR, int[]valoresG, int[]valoresB) {
int[] vectorR= new int [tamano/3];
int[] vectorG= new int [tamano/3];
int[] vectorB= new int [tamano/3];
int aux=1;
//guardamos los pixeles en distintos arrays de color
for (int k = 0; k < tamano/3; k++) {
aux= k*3;
vectorR[k] = imageRGB[aux];
vectorG[k] = imageRGB[aux+1];
vectorB[k] = imageRGB[aux+2];
}
//ordenamos de menor a mayor, solo ordenamos el array rojo, los otros, se ordenarán a partir del rojo
Arrays.sort (vectorR);
Arrays.sort (vectorG);
Arrays.sort (vectorB);
int longi = vectorR.length;
int sep = longi/clases;
int j=0;
int i=0;
//clasificación provisional de cluster
while (i <>
valoresR[j] = vectorR[i]; valoresG[j] = vectorG[i]; valoresB[j] = vectorB[i];
i = i + sep;
j++;
}
}
Una vez hecho esto nos guardamos los valores de los píxeles originales de la imagen, estos valores no se modificarán nunca en el algoritmo.
for (int k = 0; k < tamano/3; k++) {
aux= k*3;
valorPixelR[k] = imageRGB[aux];
valorPixelG[k] = imageRGB[aux+1];
valorPixelB[k] = imageRGB[aux+2];
}
Una vez hecho esto, comenzamos con el algoritmo de distancia mínima (algoritmo de distancia mínima euclídea) para saber a que clase pertenece cada píxel, haremos tantas iteraciones como sean necesarias para que cada píxel acabe agrupado en su clase mas cercana, la finalización de este proceso se hará cuando no haya variación entre un conjunto de clusteres y los obtenidos en la siguiente iteración.
do {
//examinamos cada pixel de la imagen y asignamos un cluster dependiendo de la distancia minima (euclidea)
for (int i=0; i
min_dif=1000;
for (int j=0;j
dif1=(double)Math.abs(valorPixelR[i] - valoresR[j]);
dif2=(double)Math.abs(valorPixelG[i] - valoresG[j]);
dif3=(double)Math.abs(valorPixelB[i] - valoresB[j]);
sumaPotencias=Math.pow(dif1, 2) + Math.pow(dif2, 2) + Math.pow(dif3, 2);
diferencia = (int)Math.sqrt(sumaPotencias);
if (diferencia <>
min_dif = diferencia;
min_clase = j;
}
}
cluster[i] = min_clase;
}
//guardamos valores de la ultima iteracion
int elems[] = new int[cjtos];
for (int i=0; i
valorAntR[i]=valoresR[i]; valorAntG[i]=valoresG[i]; valorAntB[i]=valoresB[i];
valoresR[i]=0; valoresG[i]=0; valoresB[i]=0;
elems[i]=0;
}
for (int j=0; j
valoresR[cluster[j]] += valorPixelR[j];
valoresG[cluster[j]] += valorPixelG[j];
valoresB[cluster[j]] += valorPixelB[j];
elems[cluster[j]]++;
}
//recalculamos el centroide de cada cluster
for (int j=0; j
if (elems[j]==0) {//it avoids that no class has 0 elements
Random alea = new Random(); //it chooses random coordinates
int x=alea.nextInt(w) ;
int y=alea.nextInt(h);
valoresR[j] = imageRGB[(y*w)+x];
valoresG[j] = imageRGB[(y*w)+x+1];
valoresB[j] = imageRGB[(y*w)+x+2];
elems[j]++;
}
else {
valoresR[j] /= elems[j];
valoresG[j] /= elems[j];
valoresB[j] /= elems[j];
}
//comprobamos si hubo cambios en esta iteracion
salir = true;
for (int i=0; i
if (valoresR[i]!=valorAntR[i]){
salir=false;
break;
}
}
} while (!salir);
Finalmente copiamos los valores calculados en una nueva imagen, en la cual al mostrarla por pantalla podemos ver los distintos cluster formados por el algoritmo.
Algoritmo kmeans con 2 cluster (K=2):
Algoritmo kmeans con 3 cluster (K=3):
Algoritmo kmeans con 4 cluster (K=4):
Algoritmo kmeans con 6 cluster (K=6):
Algoritmo kmeans con 8 cluster (K=8):
Algoritmo kmeans con 10 cluster (K=10):
Algoritmo kmeans con 20 cluster (K=20):
lunes, 28 de enero de 2008
Player + placa+ pwm +puente en h + motor
En el vídeo se puede ver la prueba que se hizo referida a la velocidad del motor. En el vídeo se puede ver la fuente de alimentación de 12 voltios que alimenta la placa gestora de los motores, luego se ve la placa de motores con sus componentes (pic16f876, max232, 74hct04, 74hct08, condensadores, resistencias, conectores ...). Esta placa conecta con un bus de 6 pines a otra placa donde se puede ver el puente en h, este es el encargado de proporcionar la fuerza correspondiente al motor. En el vídeo también se ve el portátil con el eclipse Ide arrancado y nuestro programa funcionando. También se ven dos consolas, una con el servidor de player corriendo, y otra con la lectura del puerto serie /dev/ttyUSB0 con las señales que devuelve el pic.
El programa es un bucle que empieza mandando señales de funcionamiento del motor hacia delante a una velocidad máxima, el bucle va decrementando esta velocidad en porciones de 1/10 (un décimo) hasta que llegue a parar.
El vídeo no se aprecia muy bien la velocidad a la que gira el motor, casi se puede apreciar más con el sonido que hace este mismo.
Esta prueba nos sirve para poder sacar algunas conclusiones que voy a exponer:
-La integración es un hecho, los funcionamientos de dirección (adelante / atrás) son correctos y la implementación del pwm(modulación ancho de pulso / pulse width modulation) satisfactoria.
- El calor es ahora mismo el enemigo publico numero uno. Los cables de potencia (12v) son finos lo que provoca irradiación por lo tanto cierto malfuncionamiento de la placa. Esto lo solucionaremos soldando cables de mayor grosor.
- Los tips usados, los TIP125 solo soportan 4 amperios, los motores consumen de 2 a 3. Lo que hace que se calienten en exceso, en 20 o 30 segundos pueden llegar a hechar humo. Esto lo podemos solucionar con disipadores y algún ventilador. Aunque hemos decidido comprar unos superiores, los TIP135 que soportan 12 Amperios (suficiente creo) combinados con los TIP130, si no fuera suficiente le pondremos disipadores. Estos TIPS están pedidos, pero tardarán 1 semana en llegar (no había en ninguna tienda de alicante).
- Tenemos algunos problemas con los voltajes finales. A máxima potencia debería de dar 12 voltios y solo da 10,30 y "8 y algo" dependiendo la dirección. Además los voltajes no son estables, lo que pensamos que puede ser por la temperatura de los cables y los tips. A pesar de ello voy a desmontar el puente en H y hacerle una limpieza en profundidad por si hay algún pequeño cortocircuito.
Por lo demás estamos relativamente contentos por el funcionamiento. No es correcto del todo, pero el trasfondo es positivo.
martes, 22 de enero de 2008
Actualizacion de diagrama
Aquí tenéis la nueva versión del diagrama:
Aquí os ponga también el diseño que he creado para soldar en una placa virgen:
Bueno ahora queda soldar los componentes y hacer las pruebas. Algo ya tengo soldado pero estoy teniendo algunos problemas de inductancia que explicaré en el próximo post.
jueves, 17 de enero de 2008
Integracion de PWM y Puente en H
Como dijimos en el post anterior (http://celtico-celtico.blogspot.com/2008/01/consideraciones-del-puente-en-h.html) el puente en H tiene 5 entradas: 2 entradas para los voltajes positivo y negativo de la bateria (fuerza con la que se mueven los motores) y 3 entradas para el manejo lógico del puente en H en sí (dos para las direcciones y una para tierra).
Un ejemplo de puente en H se muestra a continuación:
No es exactamente el que usamos nosotros pero nos sirve para la explicación. Las entradas de avance y retroceso son las que indican la dirección en la que van a girar los motores, vamos a llamarlas de una forma más técnica InputA e InputB. Con la siguiente tabla podemos ver el funcionamiento del motor dependiendo de los valores de las entradas:
input | output
A | B | A | B
----------------
0 0 | libre --> Paro: Motores sin corriente
1 0 | 1 0 --> Giro hacia un sentido
0 1 | 0 1 --> Giro hacia otro sentido
1 1 | 1 1 --> Paro: Bloqueo de motores (intentar evitarlo)
Lo que nos queda entonces es poder aplicar el pwm al puente en H. En otros integrados ya existentes (L293, L298...) ya tienen la entrada de habilitar donde podemos poner el pwm. En el que creamos nosotros por contra no dispone de ella, por lo que deberemos aplicar la señal pwm sobre las entradas de control del puente en H dirtectamente.
La movida es que necesitaríamos 4 señales de pwm (2 motores y 2 entradas por motor) y el integrado (PIC16F876) solo dispone de 2 (por lo menos por hardware). Podríamos crear 2 señales pwm manejadas por software, pero creo que no sería la mejor solución, primero por la gestión de recursos del propio chip y segundo por la necesidad de dejar entradas libres del pic para otros usos.
Entonces fué el momento de sacar mis conocimientos Informática básica, fundamentos de los computadores y fundamentos técnicos de los computadores para acordarme de aquellas puertas lógicas tan chulas que me acompañaron en la carrera. La solución al problema es dar una señal pwm a cada motor. Cada entrada del motor tendrá una puerta and (hablamos de puertas lógicas) que mezcle la señal de pwm con la señal de habilitación.
Vamos a ir por partes y lo primero que quiero indicar para el que no sepa mucha lógica booleana es que hace una puerta and. Aquí está la tabla de verdad:
input | output
A | B | A
----------------
0 0 | 0
1 0 | 0
0 1 | 0
1 1 | 1
Por lo que si ponemos una puerta and a cada una de las entradas del puente en H con las señales de pwm y de "habilitación" conseguimos sacar ó 0 ó la señal de pwm:
Podemos ver el comportamiento de las entradas, donde x significa el valor del pwm, h entrada de habilitación y P entrada pwm:
input | outputCabe destacar que la otra entrada del puente en H es inversa. Por lo que si una está habilitada la otra no. Por lo que siempre correrá por el puente los siguiente:
H | P | A
----------------
0 x | 0
1 x | x
pwm | direccion | entrada A | entrada B
-----------------------------------------
x | adelante | x | 0
x | atras | 0 | x
Las puertas and utilizadas son las del chip 74HCT08
Creo que esto nos va a servir para el movimiento de nuestros motores. Pero esto es la teoría, ahora hay que empezar a montar esto a ver si funciona. En cuanto tenga algo espero poder subir fotos o algún vídeo.
jueves, 10 de enero de 2008
Consideraciones del puente en H
En un principio queríamos usar uno ya hecho. Por lo que en los esquemas aparece el integrado L293 o el L293b (con diodos) que es un doble puente en H, por lo que podriamos tener una bonita, a la misma vez que barata solución para el proyecto.
L293:
Pero no todo es tan bonito. Aguanta hasta 36V y nosotros vamos a manejar 12V. Pero solo puede aguantar 1A de consumo. No sabemos exactamente cuanto va a consumir los motores pero preveemos que rondará el 1,2A. Por lo que este integrado no nos sirve.
Hemos visto la posibilidad de usar un integrado de mayores prestaciones, como puede ser el L298.
L298:
Al igual que el L293 se trata de un puente en H aunque se presenta en otro formato:
Este integrado soporta ya 2A (amperios) por canal (cada motor), por lo que en un principio podrían moverse los motores de nuestro robot.
Es el momento entonces de introducir un nuevo concepto: Consumo de parada.
Cuando un motor nos dice en la especificación que consume X amperios significa que lo hace en vacío, es decir, sin tener que soportar ninguna carga. Conforme el motor tiene que soportar mas carga este consumo se va haciendo cada vez mas y mas grande. Llegando hasta el punto de que el motor no sea capaz de mover el peso. En este punto estamos en parada. El consumo aquí sube dramáticamente hasta hasta puntos insospechados.
Para hacernos una idea, si el robot se tropieza con una pared y las ruedas no "patinan" (momento de parada) los motores podrían pasar de 1,2 A hasta 8A. También es verdad es que este caso tan extremo no se va a dar (o eso espero), pero tenemos que tener en cuenta que un puente en H que solo soporte 2A parece muy poco.
Tenemos que ir a algo mas "consistente". En integrados no he encontrado nada mas fuerte, por lo que hemos decidido construirlo nosotros mismos.
Vamos a usar un esquema encontrado por Internet que maneja pequeños motores dc de 100W, 5 Amperios o 40 Voltios, que creemos que es suficiente:
Por lo que vamos a intentar implementar este esquema para el manejo de los motores, ya que creemos que es el mas idóneo.
lunes, 7 de enero de 2008
Posible configuración de los motores
Lo que hay que tener en cuenta antes de comprarlos es de las dos limitaciones que tenemos: la velocidad y la potencia.
La velocidad ya comentamos en posts anteriores que dependería del radio de la rueda y de las rev por minuto (rpm) del motor. En una primera estimación concluí que con una rueda de 10 cm de diámetro necesitaríamos un motor de unas 280 vueltas por minuto.
La velocidad se ve todo muy matemático. Pero el problema es cuando queremos ver que potencia debe de tener los motores. Ya comentamos que hay que llevar unos 7 a 9 kg. Pero para saber que potencia debemos de manejarnos por la experiencia.
Buscando por Internet encontré una pagina que ya habían hecho esta experiencia y los resultados fueron:
Con dos motores de 19,61 N-cm se comprobó que se podía transportar hasta 3,5 Kg. Por lo que con dos motores de 26 N-cm se podía transportar un total de 9,8 kg.
Carga = 52 N-cm * 3,5 Kg / 19,61 N-cm = 9,8 Kg
Una opción sería comprar motores y ponerle yo una reductora. Cosa que veo muy complicada debido a que no se donde comprar reductoras, y otra opción es comprar motores con reductoras incorporadas.
De la segunda opción: motores con reductora, he creado una pequeña configuración de motores de una tienda de Sevilla llamada superrobotica.com.
Los motores S330142 tienen las siguientes características:
Tensión Nominal | 12V |
Velocidad Nominal a 12V | 253 rpm |
Consumo sin carga 12V | 157 mA |
Consumo eje frenado 12V | 3800 mA |
Relación engranajes | 30:1 |
Fuerza de parada | 10 kg·cm |
Peso | 202 g |
Diámetro Máximo | 37 mm |
Diámetro Eje | 6 mm |
Rosca fijación | 4 x M3 |
Los 10 kg.cm de parada supera claramente a los 2,65 necesarios para mover 9 kilos. Las revoluciones son un poco justas, por lo que lo compensamos con ruedas de 13 cm de diámetro:
Diametro rueda | cm*vuelta | RPM | cm/minuto | cm/hora | m/hora | Km/h |
13 | 40,8407045 | 253 | 10332,6982 | 619961,894 | 6199,61894 | 6,19961894 |
Con lo que conseguiríamos una velocidad de poco mas de 6 km/h, suficiente para seguir a un peatón que no corra mucho.