Clasificador-Maching Learning
Autores: Jaime Rueda Carpintero, Cristian Fernando Calva Troya, Luis Ovejero Martín y Valery Isabel Cortez Fernández.
1. Introducción
El proyecto consiste en un clasificador de herramientas, capaz de clasificar hasta 4 tipos de dichas herramientas: tuercas, tornillos, arandelas y mariposas. La clasificación de los distintos tipos de herramientas será llevada a cabo por una inteligencia artificial realizada por nosotros. El sistema consta de una cinta que será la encargada de llevar la herramienta hasta la cámara de la placa de la inteligencia artificial y hasta el brazo clasificador, que en función del tipo que sea se clasificará en una caja u otra. El brazo hará el mismo movimiento siempre, pero las cajas serán las que variarán su posición en función del tipo clasificado.
2. Material utilizado
- Arduino Uno: placa principal encargada de mover la cinta, comunicarse con la placa del Arduino Nano 33, mover el brazo y las cajas.
- Servomotores: encargados de mover el brazo. Un servomotor se encarga de mover la base y otro de girar la caja del brazo.
- Joystick: en función del movimiento de izquierda, derecha, arriba o abajo será clasificado como una tuerca, tornillo, arandela o mariposa.
- Servomotor Hitec HS-433: servo con una mayor fuerza que los servomotores del brazo. Encargado de mover la cinta.
- Motor paso a paso: mueve la bandeja de las cajas de las herramientas clasificadas.
- Kit Maching Learning: consta de la placa Arduino Nano 33 y su cámara OV7675. La cámara captará una imagen de la herramienta por clasificar y le enviará la foto a la placa, que será capaz de clasificarla y mandar la información gracias a un modelo de inteligencia artificial ya entrenado.
Nota*: Pese a que hayamos utilizado el sensor ultrasónico y el electroimán en nuestros prototipos, finalmente no hemos llegado a incluirlos en la versión final debido a problemas que se especificarán más adelante.
3. Montaje del proyecto en Tinkercad
Para el desarrollo del circuito en la plataforma de tinkercad vamos a encontrar algunas pequeñas limitaciones, estas van a hacer que el circuito sea levemente distinto respecto al que finalmente realizamos en el proyecto. En primer lugar, no está la placa del controlador del motor paso a paso así que hemos tenido que crear un pequeño circuito para suplirla. El otro cambio es que al no tener el joystick como componente dentro de la plataforma no hemos podido añadirlo.
En el circuito del proyecto final el joystick servirá para indicar que objeto ha reconocido el Arduino nano 33 y poder comunicárselo al Arduino uno para que lo clasifique.
4. Inteligencia Artificial
Para el desarrollo de la IA de nuestro proyecto, hemos intentado implementarla de dos formas diferentes: a partir de la librería de TensorFlow, codificando toda la IA, y a partir de la plataforma web de EdgeImpulse. La implementación de la IA ha resultado muy difícil debido a la falta de documentación que existe, problemas con versiones, problemas con la memoria de la placa…
El primer modelo de IA que creamos fue a partir de la librería de TensorFlow. Se utiliza Python y antes de desarrollar nuestro modelo tuvimos que desarrollar una dataset (conjunto de datos que el modelo va a tomar para entrenar y reconocer patrones entre esos datos). Dicha dataset la realizamos a través de la página de EdgeImpulse con la cámara del Arduino Nano 33.
La dataset consta de alrededor de 1200 fotos donde se van a dividir entre fotos de entrenamiento y fotos de testeo. Las fotos de entrenamiento serán utilizadas por el modelo para reconocer patrones del tipo de clases que les hemos especificado a las imágenes. Una vez entrenado el modelo, les pasaremos unas imágenes donde nosotros sabremos a que clase pertenece dicha imagen y el modelo, gracias al entrenamiento anterior, hará una predicción de la clase.
Una vez realizada la dataset, es la hora de realizar nuestro modelo en Python. Aquí se nos muestra un ejemplo de clasificación de imágenes que nos ha resultado muy útil:
https://www.tensorflow.org/tutorials/images/classification?hl=es-419
El primer problema que nos surgió fue a la hora descargar todas las librerías de TensorFlow. La librería que más nos dieron problemas fue Keras. En versiones más recientes de Python daba problemas a la hora de instalar la librería. La versión que mejor nos funcionó fue la 3.7 de Python.
Esta es la configuración para los directorios donde está el dataset con las imágenes. El nombre de las clases lo recogeremos en función del nombre de las carpetas de la dataset ya que la dataset está organizada así:
Hemos definido el conjunto de datos que vamos a usar para entrenar y validar. Lo óptimo es tener un 20% de validación y 80% de entrenamiento. El train_ds cogerá las últimas imágenes de su correspondiente porcentaje y el val_ds las primeras del 20%. Mapeamos las variables para tener un par imagen-etiqueta. Una vez que hemos divido las imágenes en lotes, dándoles un tamaño y diversas operaciones, ahora pasamos a entrenar al modelo.
Esta variable al final es una capa más en nuestra red neuronal que se encargará de transformar las imágenes de entrada. Esta función en concreto rota las imágenes para tener más variedad.
Tuvimos varias redes neuronales diferentes, pero esta es la red neuronal más eficiente que pudimos hacer. Lo primero que hacemos en nuestra red neuronal es reescalar las fotos de entrenamiento a un tamaño de 64 x 64 para que el modelo sea más liviano y pasarlo a una escala de grises. Aplicamos la función del procesamiento de datos que he comentado antes y ya comenzamos a aplicar las neuronas:
- Conv2D: capa de convolución. Aplica diversas operaciones en las imágenes de entrada para una mayor eficiencia.
- MaxPooling2D: reduce la resolución de la salida de las capas convolucionales.
- Dropout: capa que evita los sobreajustes.
- Flatten: transforma la imagen de la salida anterior en un array.
- Dense: obtiene la probabilidad de cada clase mediante su función de activación softmax a partir del array obtenido.
Una vez hemos entrenado el modelo con un total de 100 épocas y un ratio de aprendizaje α de 0.00, guardamos el modelo en nuestro directorio con una extensión de .h.
Otro de los problemas que nos hemos encontrado es que el modelo.h no es capaz de ser soportado por el Arduino Nano 33 debido a su escasa capacidad, por lo que tendremos que transformar el modelo a un modelo de TensorFlow Lite y luego a un modelo de TensorFlow Lite Micro.
Para la primera transformación a micro, hemos creado el siguiente método donde se utiliza el conversor de TensorFlow Lite y vamos a guardar el modelo con el nombre que aparece a continuación:
Una vez transformado, ahora pasaremos a transformar el modelo en un archivo de C que contenga un array unidimensional del modelo mediante dos comandos en Linux:
Una vez hemos creado ya el modelo, pasamos al Arduino IDE. Uno de los nuevos problemas con los que nos hemos encontrado ha sido el hecho de no poder encontrar las librerías de TensorFlow Lite Micro para Arduino. Las librerías hasta hace poco tiempo se encontraban aquí: https://www.tensorflow.org/resources/libraries-extensions?hl=es-419. Después de indagar descubrimos que los creadores de dichas librerías tuvieron problemas con los creadores de Arduino y tuvieron que borrar las librerías. En la actualidad, las librerías se pueden obtener en el GitHub de algunos usuarios que tenían dichas librerías, como es el caso de este repositorio:
https://github.com/mbernico/tflite-micro
La librería se importa como cualquier otra en el IDE y ahora pasamos a configurar la cámara y el modelo. Para que la placa no tenga problemas de memoria, tenemos que preparar el modelo. Primero, tenemos que especificar qué operaciones se han llevado a cabo en la red neuronal:
Sin embargo, debido a problemas entre versiones del keras y TensorFlow, no se pueden compilar las capas AddConv2d, AddMaxPool2D y el layers.Rescaling. Por tanto, hemos tenido que crear una nueva red neuronal con capas que se encuentran en la versión adecuada, como puede ser Conv2D y AvgPool2D, además de eliminar el Rescaling. Este modelo ha perdido es menos fiable que el anterior pero es soportado por una versión más antigua:
Ahora pasaremos a predefinir el área de memoria que vamos a usar para la entrada, salida u otros arrays de TensorFlow (se debe de ir ajustando a ojo):
Una vez hecho lo anterior y configurado la cámara, ejecutamos el archivo y vuelve a fallar. El error que nos sucede ahora al ejecutarlo es que una de las versiones con las que compilamos el modelo a TensorFlow Lite Micro no es compatible con la versión que hemos descargado del GitHub. No pudimos solucionar este error ya que no encontramos una librería que fuera compatible con nuestro modelo.
En el siguiente enlace se encuentra un hilo de discusión del foro de Arduino donde se explica lo sucedido. Además, hay enlaces a librerías de TensorFlowLite anteriores.
Aunque nuestro equipo no pudo completar esta fase por falta de tiempo, invitamos a futuros grupos a intentarlo, partiendo ya desde el punto donde lo dejamos.
Finalmente, para evitar problemas con la librería hemos hecho uso de la plataforma web de EdgeImpulse, que es una página donde seremos capaces de elaborar una dataset, entrenar un modelo, editar el modelo, exportar el modelo y diversas funciones más. Tan solo hemos tenido que especificarle a la página cómo sería nuestra dataset, qué queremos clasificar y la red neuronal. La red neuronal que hemos usado en EdgeImpulse es muy parecida a la red del modelo anterior ya que genera un modelo muy liviano y eficiente. Aquí se muestra una configuración de las imágenes de entrada parecida a la del modelo anterior:
Entrenamiento del modelo:
Aquí se nos muestra una gráfica de la tasa de acierto que ha tenido el modelo en la etapa de aprendizaje y la pérdida:
Podemos observar también cómo se han ido dispersando los datos a la hora de clasificarlos. El tamaño del modelo es de 149K, donde el tamaño máximo de la placa es de 256K. Hemos notado cómo si el modelo pesa más de 160K, la placa se queda sin memoria y no se le puede meter el modelo.
El modelo que tenemos lo podemos reentrenar (puede darse el caso de que al ajustar pesos mejore el modelo o empeore) y testear con datos de la dataset para que prediga la clase a la que pertenece la imagen. Al fin y al cabo, la parte de testing la haríamos nosotros en nuestro sistema empotrado a la hora de identificar un objeto u otro.
Por último, el modelo ya implementado en el Arduino IDE no nos ha dado ningún problema y es capaz de tomar fotos e identificarlas. El problema nos ha surgido a la hora de establecer una conexión entre la placa del Arduino Uno y la del Arduino Nano 33. La conexión se ha realizado mediante un protocolo de comunicación I2C, donde seremos capaces de comunicar las dos placas mediante el Serial. Hemos sido capaces de mandar un serial del Uno al Nano y que el Nano lo recibe. Sin embargo, a la hora de que el Nano avise al Uno y le mande un Serial daba problemas y no se podía enviar correctamente. La placa del Arduino Nano 33 al estar tan limitada en cuanto a pines se refiere no pudimos encontrar una solución mejor para establecer una comunicación.
Todo el código que se ha explicado anteriormente se encuentra en el siguiente GitHub, tanto la parte de TensorFlow como el modelo de EdgeImpulse:
https://github.com/jaimachu/Maching-Learning-TensorFlow
5. Hardware
En este apartado vamos a hablar de los materiales utilizados para el desarrollo de la parte física del proyecto, el coste de estos, del montaje, de los problemas que surgieron durante este y de las soluciones tomadas.
A parte de los componentes que hemos visto anteriormente, para la realización de la estructura donde irán todas las partes del proyecto hemos utilizado contrachapado ya que es fácil de manejar. Con este material también hemos creado las diferentes piezas del brazo mecánico, y para la cesta de este hemos utilizado un vaso de cartón recortado. Para unir estas piezas hemos utilizado tornillos y cinta de doble cara.
Para la estructura de la cinta podemos ver que hemos utilizado palitos de madera que hemos cortado para hacer el camino de la cinta. También hemos creado unos ejes sobre los que puede pivotar para poder avanzar y unas cajitas de cartulina donde poder poner las herramientas para que la cámara no tenga problemas al reconocer los objetos y para que estos no se caigan de la cinta.
Por último, para la caja de clasificación de herramientas hemos utilizado unos pequeños botes de plástico que hemos recortado y un trozo de cartón pluma.
Costes de los materiales | |
Palos de madera | 6.55€ |
Tabla de aglomerado | 0.75€ |
Tornillos | 1.20€ |
Cartulina | 0.50€ |
Cinta de doble cara | 1.15€ |
Tableros de contrachapado | 12.98€ |
Electroimán | 5.00€ |
Servomotores 180 | 12.00€ |
Servomotor continuo | 12.50€ |
Total: 52.63€ |
Implementación fallos y soluciones
Para la implementación del proyecto lo dividimos por partes. Una parte que corresponde a la cinta, otra al brazo mecánico y otra a las cajitas de clasificación.
Para la parte de la cinta usamos el servomotor continuo, palos de madera para crear el camino que seguirá la cinta, palitos para crear los ejes donde pivota la cinta y una cinta de ejercicios.
El servo se empezará a mover cuando pulsemos el botón que indica que se mueva la cinta hacia adelante. Luego usaremos otro botón para indicar que el Arduino nano 33 puede procesar la imagen del objeto. El joystick se usará en esta parte para indicar el objeto que ha identificado la IA.
En primer lugar, para crear la cinta pensamos en el sensor de ultrasonidos del kit, para saber cuando la caja con el objeto llegaba a la localización donde poder procesar el objeto, esto daba bastantes fallos así que decidimos cambiar el sensor por un botón para mover la cinta hasta el punto exacto. También probamos con el motor paso a paso del kit para hacer que se mueva la cinta, pero este era bastante lento así que decidimos comprar otro servomotor.
En el caso del brazo, creamos las piezas con contrachapado y las juntamos con cinta de doble cara y tornillos. El primer brazo que construimos tenía un electroimán en lugar de una cesta, pero el imán comprado no tenía fuerza suficiente para poder levantar los tornillos más grandes y decidimos cambiar el diseño, sustituyendo el imán por la cesta. La última parte son las cajas de clasificación. Usamos el motor paso a paso del kit para poder girar las cajas en función de la herramienta reconocida. Al pulsar el botón que indica a la IA que puede pasar a identificar, tras haber identificado el objeto el motor gira para posicionar las cajas correctamente, el brazo recoge la herramienta y la suelta en la caja, después las cajas vuelven a la posición por defecto.
En esta parte no hubo muchos problemas, simplemente cambiar la velocidad del motor, pero nos dimos cuenta de que tampoco era tan relevante y decidimos dejarlo con la que tenía. Finalmente juntamos todas estas partes en un tablón de aglomerado al que pusimos unas patas para poder levantarlo de la mesa, esto lo hicimos para poder hacer un agujero y meter el motor paso a paso, de esta manera las cajas de clasificación quedan más bajar para que al rotar la cesta del brazo a la hora de soltar el objeto no haya colisiones.
6. Software
En esta parte explicaremos el funcionamiento del software y sus casos de uso.
Casos de uso
En primer lugar, pulsaremos el botón amarillo para poder mover la cinta hasta su posición, una vez en el lugar correspondiente pulsaremos el rojo para indicar que ya puede pasar a identificar el objeto. Cuando ha identificado el objeto tenemos cuatro casos de uso:
– Tornillo: El primer caso es que haya identificado un tornillo. Para este no vamos a mover las cajas, solo se movería el brazo para recoger la herramienta y para soltarla en la caja de la posición por defecto, ya que esta será la que guarde los tornillos.
– Tuerca: En este segundo caso cuando se identifique que el objeto es una tuerca vamos a mover las cajas solo un cuarto de vuelta para guardarla en la siguiente caja, se movería el brazo y finalmente las cajas giraran tres cuartos de vuelta para ponerse en la misma posición en la que estaban al principio.
– Arandela: En el tercer caso, tras reconocer la arandela, movemos las cajas, media vuelta para posicionar la tercera caja en el punto donde el brazo suelta la herramienta, el brazo se mueve y volvemos a dar media vuelta.
– Mariposa: En este último caso giraremos las cajas tres cuartos de vuelta, movemos el brazo y volvemos a girar las cajas solo un cuarto de vuelta para dejarlas como al principio.
Ahora que sabemos cómo funcionan los distintos casos de uso vamos a pasar a ver el código.
Código
Como ya vamos a explicar el código en profundidad en el video del proyecto, aquí vamos a ver algunas de las partes más importantes.
Lo primero que podemos ver es la declaración de algunas contantes que usaremos para indicar los pines del motor paso a paso, la declaración de los servomotores que vamos a usar, contadores para medir los grados de la posición de los servos de 180 grados, una matriz para guardar las posiciones del motor paso a paso y los pines de entrada de datos para los botones junto con la asignación de los pines del joystick y de los grados para usar este último.
Después de esto pasamos con la parte del setup. Esta parte se ejecuta solo una vez así que aquí vamos a asignar de que tipo van a ser los pines, si de entrada o salida. También vamos a asignar el pin correspondiente a cada servo, decir cual es la posición inicial de los de 180 e iniciamos el serial y el bus de I2C.
Tras el setup pasamos a ver el loop. El loop se va a ejecutar en bucle después del setup. Aquí vamos a comprobar el estado del botón amarillo, si está o no pulsado. En caso de estar pulsado vamos a hacer que la cinta se mueva hacia adelante y si no lo está haremos que esté parada.
En caso de estar parada, no estar el botón amarillo pulsado, vamos a comprobar el estado del botón rojo. Si el amarillo no está pulsado y pulsamos el botón rojo se ejecutará la parte del código que llama a la placa de Arduino nano 33 para que esta identifique el objeto. Finalmente, como no pudimos hacer que se comunicasen de forma bidireccional las dos placas esta parte llamara a la función identificar. Esta función leerá el dato introducido gracias al joystick y en función de la posición de este mandará un número u otro, este numero corresponderá con el objeto reconocido.
Tras recibir el número de la salida de la función identificar vamos a mover el brazo hasta la cinta para recoger el objeto. Una vez esté colocado se moverá la cinta para dejar caer el objeto en la cesta del brazo y volverá atrás para no interferir en el movimiento del brazo.
Por último, llamaremos al método organizar para que se coloquen las cajas de clasificación de la forma correcta y el brazo pueda soltar el objeto. Tras esto volveríamos al principio para poder volver a leer el estado del botón y seguir moviendo la cinta.
Algunas de los métodos más importantes del programa son identificar() que se encarga de leer el estado del joystick para devolver el número correspondiente al objeto clasificado o la de mover_brazo() y mover_cajas() que se encargan de mover los servomotores del brazo y de mover el motor paso a paso para colocar las cajas, respectivamente.
En el siguiente enlace de GitHub se puede encontrar todo el código del proyecto: