Reconocimiento de colores con Pixy


En efecto, si no tenemos demasiado tiempo  para experimentar con algoritmos de reconocimiento de imágenes con opencv o similares ,por ejemplo para integrar  la visión artificial en un robot que siga una linea, hay un producto llamado   Pixy2    que puede hacer todo eso y mas. Además, esta segunda version,  es más rápida, más pequeña y más potente que el Pixy original, agregando algoritmos de seguimiento / seguimiento de línea, así como otras características  (de  hecho la velocidad de procesamiento  en tiempo real   se ha mejorado en 60 fotogramas por segundo)

Como  puede  suponer  Pixy2  incorpora una cámara  y  una fuente de luz , y con todo  este hw puede detectar líneas, intersecciones y pequeños códigos de barras, diseñados para robots que siguen líneas.

Precisamente  por la utilidad de integrarlo en pequeños robots ,se han agregado algoritmos de seguimiento a la detección de objetos basada en color. Ademas tampoco descuidan su conexión con otras placas pues proporcionan bibliotecas mejoradas y simplificadas para Arduino, Raspberry Pi.

Veamos a continuación   mas detalles sobre esta interesante  placa

 

Hablamos de una placa  bastante pequeña(de unos 8x7cm) ,  tanto que incluso se puede conectar varios Pixy2 a un microcontrolador pues  Pixy2 es más pequeño, más rápido y más potente que el Pixy original. 

Este sistema de visión inteligente plug-and-play para Arduino-compatibles, Raspberry Pi u otros microcontroladores / sistemas de computadora.

 Al igual que su predecesor, Pixy2 puede aprender a detectar objetos que le enseñe, simplemente presionando un botón. Además, Pixy2  implementa nuevos algoritmos que detectan y rastrean las líneas para su uso con robots de seguimiento de línea (  incluso con  los nuevos algoritmos también pueden detectar intersecciones y «señales de tráfico».)

 

Aunque la nueva cámara puede funcionar hasta 60 fps, se  ejecuta aproximadamente la mitad de esa velocidad y existen múltiples opciones para el ajuste fino del reconocimiento de objetos, todo  ello  para  mejorar la precisión   el reconocimiento de imágenes   ya que la cámara no esta diseñada para la grabación de imágenes pero si  pasará el centro X, Y, y el ancho, la altura de un objeto, y lo hace muy bien.

La nueva version  Pixy2  lleva  modo de seguimiento de línea y fuente de luz LED integrada simplificando  su programación recibiendo solo los objetos que le interesan.

Se puede utilizar el controlador que desee pues incluye bibliotecas de software para Arduino, Raspberry Pi y BeagleBone Black.

Si lo conectamos a  un pc, podemos instalar la utilidad de configuración (se ejecuta en Windows, MacOS y Linux) asi como el sw  Pixy2 CMUCam5, un sensor de imagen para su microcontrolador que puede enseñarle qué buscar.También es una gran mejora con respecto a las versiones anteriores de Pixy CMUCam, que agrega una mayor flexibilidad cuando se trata de cambios de iluminación y exposición. También puede recordar siete firmas de colores diferentes, encuentre cientos de objetos en a visión del robot es fácil: presione el botón para enseñarle a Pixy2 un objeto

Por ello el   Pixy2   es un sensor de imagen con un potente procesador que puede programar para enviar solo la información que está buscando para que su microcontrolador no se vea abrumado por los datos  ya que Pixy2 exporta su información en una variedad de formas útiles (UART serie, SPI, I2C, salida digital o salida analógica) para que su microcontrolador o microcomputadora pueda comunicarse fácilmente mientras realiza otras tareas.

Las coordenadas  enviadas  desde   Pixy2  pueden decirle a su robot qué hacer, como girar a la izquierda, girar a la derecha, disminuir la velocidad, etc. Y Pixy2 hace todo esto a 60 cuadros por segundo, para que su robot también pueda ser rápido. Pixy2 utiliza el tono y la saturación como su principal medio de detección de imágenes, en lugar del RGB normal. Esto significa que la iluminación o la exposición no afectarán la detección de Pixy2 de un elemento, lo cual es un problema frustrante con muchos sensores de imagen. .

 

 

Vamos  a ver un ejemplo publicado en instructables.com  de un interesante  robot seguidor que implementa  esta tecnologia  creado  por chaabani houssem 

Necesitaremos  al menos los  siguientes componentes:

  • 1 Arduino mega (o en su defecto un Arduno uno o Arduino nano … )
  • Pixy2  
  • 2 motores  paso  a paso
  • 2 ruedas y sus engranajes 
  • Un l293d
  • Una batería de  9v  (o   cualquiera que pueda reciclar 

 

Podemos  enseñar cualquier objeto a pixy a través de este enlace:

http: //cmucam.org/projects/cmucam5/wiki/Teach_Pixy …

Para controlar  los dos motores usaremos un famoso controlador , el CI   l293d

Este circuito integrado     de 16 pines ofrece los siguintes carasterciticas: 

  • Capacidad del canal: 600 mA de corriente de salida / canal.
  • Sistema de activación.
  • Pico de salida de corriente: 1,2 A / canal (no repetitivo).
  • Protección contra sobretemperatura.
  • Entrada lógica «0» a 1,5 V (alta inmunidad al ruido).

Conectemos  los pines del L293D   a un  Arduino   de la siguiente manera :

  • Pin  1 al pin Arduino 2.
  • Pin 2 al pin 3 de Arduino.
  • Entrada 3 al pin 4 de Arduino.
  • Entrada 4 al pin Aduino 5.

Con estas conexiones ya  se puede probar el robot como verificar el avance, probar el giro a la izquierda o  el giro a la derecha y la parada.

Si todo va  bien ahora ya puede agregar pixy, pero para que  funcione el robot  antes tendrá que  programar Arduino  para  que  en función de la  salida de  Pixy2   de las  ordenes oportunas a los motores

Como  ejemplo de código  vamos a ver  una demostracion de como podemos  controlar lso dos motores usando  unicamente la informacion apoportada por Pixy2  

 

//definición de  puertos  que se usaran

int mg2 = 4;

int mg1 = 5;

int md1 = 3;

int md2 = 2;

int enag= 6;

//librerias  externas

#include <Wire.h>

#include <Pixy.h>

//llamada a las primitivas de Pixy

Pixy pixy;




//definición de puertos 

void setup()
{
pinMode(enag, OUTPUT);

pinMode(md1, OUTPUT);

pinMode(md2, OUTPUT);

pinMode(mg1, OUTPUT);

pinMode(mg2, OUTPUT);

//sacamos  por consola de Arduino mensajes de depuración

Serial.begin(9600);

Serial.print("Arrancando...\n");

pixy.init();

}







//comienzo del programa  principal

void loop()
{

static int i = 0;

int j;

uint16_t blocks;

char buf[32];


blocks = pixy.getBlocks();

if (blocks)

{
i++;

// Hacer esto cada 50 cuadros porque si se  supero colapsaria Arduino


if (i%50==0)

{
sprintf(buf, "Detected %d:\n", blocks);
Serial.print(buf);


//bucle for que recorre todos lox bloques enviados por Pixy
for (j=0;  j<blocks;    j++) 

{

sprintf(buf, " block %d: ", j);

Serial.print(buf);  //sacamos las salida por consola

pixy.blocks[j].print();

}


}

if((pixy.blocks[0].x>120)&&(pixy.blocks[0].x<190))
{
go();  //avanzar
}

if((pixy.blocks[0].x<120) && (pixy.blocks[0].x>10))
{

gauche();//girar a derecha

}

if(pixy.blocks[0].x>190)
{

droite();} //girar  a izda
}

else
{

o9if();//hacia atras

}
}






//giro a la derecha
void droite()
{
analogWrite(enag,210);
analogWrite(mg1,0);
analogWrite(mg2,0);
analogWrite(md1,255);
analogWrite(md2,0);
delay(30);
}




//giro a al izda

void gauche()
{
analogWrite(enag,210);
analogWrite(mg1,255);
analogWrite(mg2,0);
analogWrite(md1,0);
analogWrite(md2,0);
delay(30);
}




//hacia atras
void o9if()

{
analogWrite(mg1,0);
analogWrite(mg2,0);
analogWrite(md2,0);
analogWrite(md1,0);
delay(30);
}

//avanzar

void go ()
{
analogWrite(enag,210);
analogWrite(mg1,255);
analogWrite(mg2,0);

analogWrite(md1,255);
analogWrite(md2,0);
delay(30);
}

 

Y   ahora en un vídeo podemos ver el robot   funcionando:

 

En general,   Pixy2   es un excelente sistemas de visión inteligente introductorio  pues  podemos dar seguimiento de objetos a un  robot de manera mucho más fácil que usar un pc de una sola placa con OpenCV (más requisitos de menor potencia) pues se conecta fácilmente a un Arduino Nano o Uno (entre otros sistemas)  gracias  a los ejemplos de código que proporcionan que  ayudan a ponerlo en marcha rápidamente .Ademas es  muy fácil configurarlo utilizando las instrucciones en su sitio web pues hay ejemplos integrados en su software.

 

Interaccionar con FireBase desde Arduino


Como  podemos ver en este blog en numerosas  entradas que hablamos de dispositivos de IoT, es  relativamente sencillo construir nuestros  propios dispositivos de IoT con algunos sensores y microcontroladores  como Arduino, NodeMCU, Raspberry  Pi, etcétera , lo cual le permitirán automatizar su hogar apoyándose en estos dispositivos como por ejemplo usando el servicio de Cayenne .

De hecho ,como ejemplo de lo  sencillo  y económico  que puede ser  la construcción de dispositivos   IoT desde un punto de vista más empírico , lo ideal es usar un o NodeMCU ESP-12E para   acceder a Firebase  

 

 NodeMCU ESP-12E  es muy barato (unos 6€)   ,  y al tener  wifi incorporado para conectarse a internet,  ya tenemos los elementos suficientes  para conectarnos a  bases de datos avanzada  en l anube como puede ser Firevase y   gracias a un hardware tan eficiente  (y por supuesto los servicios cloud de Firebase).

Precisamente  FirebaseArduino (abstracción completa de la API REST de Firebase expuesta a través de las llamadas de C ++ de una manera amigable con el cableado.)   es una biblioteca muy útil usada   para simplificar la conexión a la base de datos Firebase desde cualquier cliente Arduino .Esta biblioteca  como podemos imaginar  se encarga de todo el análisis de Json y puede realizar transacciones en tipos C / Arduino puros.

En un post anterior «Primeros pasos con NodeMCU y Firebase»  ya vimos un sencillo ejemplo de como ambos componentes pueden funcionar, Veamos a  a continuación  que podemos hacer c muchas mas cosas con esta famosa librería

 

class FirebaseArduino

Esta es la clase principal para que los clientes de Arduino interactúen con Firebase. Como es habitual con arduino  para referenciarla  y poderla usar necesitamos  introducir  esta libreria con un include  al principio del programa , como por ejemplo

 #include <FirebaseArduino.h>»

Esta implementación está diseñada para seguir las mejores prácticas de Arduino y favorecer la simplicidad sobre todo lo demás. Para casos de uso más complicados y más control, podemos usar la clase Firebase en Firebase.h.

Esta libreria debe ser llamada primero con void begin ( const String y host , const String y auth = «» )  , lo cual inicializa el cliente con el host y las credenciales de base de fuego dados.

Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);  //intentamos conectarnos a la base de datos Firebase con nuestras credenciales

Los parámetros son :

  • host : es decir el  host de base de datos de base de datos de Fierbase , normalmente X.firebaseio.com.
  • auth : credenciales  para la db  que pueden ser  una palabra  secreta o token.

 

Lo más sencillo  precisamente para pasar las credenciales de Firebase   a esta clase es usando  variables que  definiremos al principio del programa:

  • FIREBASE_HOST “xxxxxxxxxxxxxxx.firebaseio.com”
  • FIREBASE_AUTH “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”

 

Veamos algunas funciones que podemos usar con esta clase:  pushInt,pushFloat, pushBool,pushString,push,setInt, setFloat,setBool,setString,set ,getInt, getFloat,getBool,getString,get,   las relacionadas con Firebaseobject(get,readevent)  .   asi como remove,stream, available,read ,success, failed,error

 

 

String pushInt ( const String & path , int value )

Anexa el valor entero al nodo en la ruta.Equivalente al POST de la API REST. Debe comprobar  success()  después de llamar. Devuelve la clave única del nuevo nodo hijo.

Los parámetros que usa:

  • path : La ruta del nodo padre.
  • value : valor entero que desea agregar al nodo.

 

String pushFloat(const String &path, float value)

Esta función anexa el valor flotante al nodo en la ruta.  Es equivalente al POST de la API REST. Debe comprobar   success()   después de llamar. Devuelve la clave única del nuevo nodo hijo.

Los parámetros que usa:

  • path : La ruta del nodo padre.
  • value : valor flotante que desea agregar al nodo.

String pushBool(const String &path, bool value)

Esta función anexa el valor booleano al nodo en la ruta.  Es equivalente al POST de la API REST. Debe comprobar el resultado de salida con  success()  después de llamar a a funcion . Devuelve la clave única del nuevo nodo hijo.

Los parámetros que usa:

  • path : La ruta del nodo padre.
  • value : valor Booleano que desea agregar al nodo.

String pushString(const String &pathconst String &value)

Esta función anexa el valor de cadena al nodo en la ruta.Es equivalente al POST de la API REST. Debe comprobar el resultado de salida con  success()  después de llamar a a funcion. Devuelve la clave única del nuevo nodo hijo.

Los parámetros que usa:

  • path : La ruta del nodo padre.
  • value : valor de la  cadena que desea agregar al nodo.

 

String push(const String &pathconst JsonVariant &value)

Esta función anexa los datos JSON al nodo en la ruta.Equivalente al POST de la API REST.   Devuelve la clave única del nuevo nodo hijo.

Parámetros

  • path : La ruta del nodo padre.
  • value : los datos JSON que desea agregar al nodo.

 

void setInt(const String &path, int value)

Escribe el valor entero en el nodo ubicado en la ruta equivalente al PUT de la API REST. Debe comprobar el resultado de salida con  success()  después de llamar a a función

Parámetros que usa

  • path : la ruta dentro de su base de datos al nodo que desea actualizar.
  • value : valor entero que desea escribir.

void setFloat(const String &path, float value

Escribe un  valor en coma flotante en el nodo ubicado en la ruta equivalente al PUT de la API REST.Debe comprobar el resultado de salida con  success()  después de llamar a a función

Parámetros necesarios:

  • path : la ruta dentro de su base de datos al nodo que desea actualizar.
  • value : el valor flotante que desea escribir.

void setBool(const String &path, bool value)

Escribe el valor booleano  en el nodo ubicado en la ruta equivalente al PUT de la API REST.  Debe comprobar el resultado de salida con  success()  después de llamar a a función  

Parámetros que usa

  • path : la ruta dentro de su base de datos al nodo que desea actualizar.
  • value :  valor booleano  que desea escribir.

void setString(const String &pathconst String &value)

Escribe el valor de la cadena en el nodo ubicado en la ruta equivalente a la PUT de la API REST. Debe comprobar el resultado de salida con  success()  después de llamar a a función

Parámetros que requiere:

  • path : la ruta dentro de su base de datos al nodo que desea actualizar.
  • value : valor de la cadena que desea escribir.

 

void set(const String &pathconst JsonVariant &value)

Escribe los datos JSON en el nodo ubicado en la ruta.  Equivalente al PUT de la API REST.  Debe comprobar el resultado de salida con  success()  después de llamar a a función

Parámetros necesarios:

  • path : la ruta dentro de su base de datos al nodo que desea actualizar.
  • value : datos JSON que desea escribir.

 

int getInt(const String &path)

Obtiene el valor entero ubicado en la ruta. Debe comprobar el resultado de salida con  success()  después de llamar a a función   Devuelve el valor entero ubicado en esa ruta. Solo será poblado si  success()   es verdadero. Requiere un único  parámetro path : la ruta al nodo que desea recuperar.

 

float getFloat(const String &path)

Obtiene el valor flotante ubicado en la ruta. Debe comprobar el resultado de salida con  success()  después de llamar a a función. Devuelve  un valor flotante ubicado en ese camino. Solo será poblado si un success()  ) es verdadero.

Requiere un único  parámetropath : la ruta al nodo que desea recuperar.

 

String getString(const String &path)

Obtiene el valor de cadena ubicado en la ruta.Debe comprobar el resultado de salida con  success()  después de llamar a a función.   Devuelve el valor de cadena ubicado en esa ruta. Solo será poblado si el   success()   es verdadero.

Requiere un único  parámetropath : la ruta al nodo que desea recuperar.

 

bool getBool(const String &path)

Obtiene el valor booleano ubicado en la ruta.Debe comprobar el resultado de salida con  success()  después de llamar a a función. Devuelve el  valor booleano ubicado en esa ruta. Solo será poblado si el éxito () es verdadero. Requiere un único  parámetro  path : la ruta al nodo que desea recuperar.

Es muy usado para  activar o desactivar un  nivel  lógico  en los pines binarios  en la placa ,por ejemplo para activar una salida a nivel alto  o bajo 

Ejemplo

      bool isLedOn = Firebase.getBool(“led”); // recuperamos el valor del objeto led de la sesión firebase

 

 

FirebaseObjectget(const String &path)

Obtiene el valor del objeto json ubicado en la ruta.Debe comprobar el resultado de salida con  success()  después de llamar a a función. Devuelve el valor FirebaseObject ubicado en esa ruta. Solo será poblado si el éxito () es verdadero.Requiere un único  parámetro path : la ruta al nodo que desea recuperar.

FirebaseObjectreadEvent()

Lee el siguiente evento en una stream ( secuencia).Esto solo tiene sentido una vez que se ha llamado a  stream() 

A la salida FirebaseObject tendrá [«type»] que describe el tipo de evento, [«path»] que describe la ruta efectuada y [«data»] que se actualizaron.

 

 

 

void remove(const String &path)

Elimina el nodo, y posiblemente el árbol completo, ubicado en la ruta.Debe comprobar el resultado de salida con  success()  después de llamar a a funcion.Requiere un único  parámetro path : la ruta al nodo que desea eliminar, incluidos todos sus hijos.

void stream(const String &path)

Inicia la transmisión de los cambios realizados en el nodo ubicado en la ruta, incluido cualquiera de sus elementos secundarios.

Debe comprobar el resultado de salida con  success()  después de llamar a a funcion Esto cambia el estado de este objeto. Una vez que se llama a esto, puede comenzar a monitorear available () y llamar a readEvent () para obtener nuevos eventos.

Requiere un único  parámetro path : la ruta dentro de su db al nodo que desea monitorear.

bool available()

Comprueba si hay nuevos eventos disponibles.Esto solo tiene sentido una vez que se ha llamado a  stream()  .Devuelve si un nuevo evento está listo.

bool success ( ) 

Devuelve   si el último comando fue exitoso.

bool failed ( ) 

Devuelve si el último comando falló.

 

const String &error()

 Devuelve el  mensaje de error del último comando si  failed() es verdadero.

 

 

 

 

 

class FirebaseObject

 

Representa el valor almacenado en Firebase , puede ser un valor singular (nodo de tipo leaf) o una estructura de árbol.

Las  funciones publicas  definidas para esa clase son las siguientes:

 

FirebaseObject ( const char * data ) 

Construir a partir de json.  Requiere un único  parámetro data : cadena formateada JSON.

 

bool getBool ( const String & path = «» ) const

Devuelve el valor como un booleano. .Requiere un único  parámetro optional : ruta en el objeto JSON.

 

int getInt ( const String & path = «» ) const

 Devuelve el  resultado como un entero.Requiere un único  parámetro optional : ruta en el objeto JSON.

 

float getFloat ( const String & path = «» ) const

Devuelve el valor como un flotador..Requiere un único  parámetro optional : ruta en el objeto JSON.

 

String getString ( const String & path = «» ) const

Devuelve el valor como una cadena.Requiere un único  parámetro optional : ruta en el objeto JSON.

 

JsonVariant getJsonVariant ( const String & path = «» ) const

Devuelve el valor como JsonVariant.Requiere un único  parámetro optional : ruta en el objeto JSON.

bool success ( ) const

Devuelve si hubo un error en la descodificación o el acceso al objeto JSON.bool 

 

failed ( ) const

Devuelve si  hubo un error en la descodificación o el acceso al objeto JSON.const 

 

String & error ( ) const

 Devuelve un mensaje de error si  failed()  es verdadero.

 

 

Mas informacion en  https://firebase-arduino.readthedocs.io/en/latest/