Pirobits
  

Ambilight casero con Arduino, una tira Led RGB y GoLang

alberto avatar Alberto Sola · 6/11/2021 · 6 min

Los proyectos relacionados con iluminación utilizando la tecnología LED me encantan. Desde siempre he estado interesado en este tema.

Un ejemplo muy chulo es el Ambilight de Philips, que lo incluyen en algunas de sus televisiones. La idea es analizar diferentes regiones de la imagen, obtener el color dominante, y actualizar el color de los LEDs asociados a cada región en tiempo real. Así cuando vemos una serie o película, la pared se ilumina según los colores de la escena, lo que mejora la inmersión.

Desde pequeño he querido realizar este proyecto por mi cuenta, pero no fui capaz debido a que el proyecto es más complejo de lo que parece.

La otra tarde me animé y realicé un prototipo, y el resultado es bastante chulo, así que en el post hoy voy a explicar qué he utilizado, y qué problemas he tenido que resolver.

El proyecto es de código libre, puedes verlo en GitHub pinchando este enlace. Recuerda que es un prototipo hecho en una tarde, hay muchos aspectos que se pueden mejorar.

Comencemos!

Cliente

El primer paso era diseñar un programa que tiene que realizar tres funciones:

  1. Capturar la pantalla.
  2. Extraer el color dominante.
  3. Enviar este color al Arduino.

El requisito fundamental es que tiene que ser eficiente. Este proceso tiene que realizarse varias veces por segundo, ya que en caso contrario la experiencia visual estropearía la escena.

Por qué Go (golang)

La primera decisión era elegir un lenguaje. Hace unos años realicé un programa que capturaba la pantalla en C++ en Ubuntu con XServer, así que más o menos sabía lo que tenía que hacer. C++ es un lenguaje muy eficiente que me gusta mucho, pero hay que reconocer que no es el más productivo.

Aquí es donde entra Go (golang), un lenguaje de programación que siempre he querido aprender, ya que en mi opinión es el C++ del siglo XXI (aunque Rust le está comiendo terreno). Además Go tiene una gran comunidad, y cada vez están surgiendo más proyectos importantes que utilizan Go (golang), como Traefik o Kubernetes. Podéis explorar GitHub y ver cuántos proyectos lo usan.

Go (golang) es un lenguaje de programación Open Source, fue publicado por Google en 2009 y busca ser eficiente como C/C++ pero a su vez ofrecer otras funcionalidades como las de Python.

Capturar la pantalla

El prototipo lo he hecho en Ubuntu, que funciona con XServer, pero también tengo una versión experimental en Windows.

Me he basado en este repositorio para realizar las capturas de pantalla, aunque he tenido que modificar el código para optimizarlo y aumentar los FPS. Para mi sorpresa he conseguido unos 65fps de media (i7-6700HQ, 16GB RAM), más que suficiente.

Análisis del color

Una vez capturada la pantalla del ordenador, hay que analizar los colores de la imagen y extraer el color dominante. Esta técnica se conoce como color quantization, y consiste en reducir el espacio de colores a un subconjunto de estos que lo represente de la mejor manera posible.

Este problema es bastante común. Un ejemplo podría ser el siguiente: cuando tomas una foto con tu cámara en formato RAW, hay millones de colores que ha captado el sensor. Cuando se almacena en disco, normalmente se aplica un algoritmo de compresión con pérdida como JPG, que consiste en reducir el espacio de colores de forma que la imagen se pueda comprimir y ocupe menos espacio. Cuanto más se comprima la imagen, menos colores habrá y menor será su calidad.

Nosotros queremos hacer algo similar, pero queremos quedarnos con un color por cada región de la imagen. Para el prototipo voy a usar únicamente la región central de la imagen, aunque lo ideal sería analizar diferentes regiones cercanas a los bordes, pero como primera versión es suficiente.

Existen muchas técnicas para resolver este problema. Yo he elegido la que me ha resultado más simple para el prototipo: Kmeans. Este es un algoritmo de clustering que se utiliza en Machine Learning para encontrar, dentro de un conjunto de elementos, los subconjunto de elementos que son parecidos entre sí pero diferentes al resto. Nosotros conocemos de antemano el número de centroides (k), ya que coincide con el número de colores que queremos extraer de la imagen, y en mi caso es K=1. Por suerte existe ya un paquete que lo implementa (github.com/EdlinOrg/prominentcolor).

K=1 no tiene sentido en un problema de clasificación, pero en este caso estamos resumiendo los datos. Tal vez usando k>1 podríamos obtener una mejor precisión en el color.

¿Cómo funciona con una imagen? Cada píxel, que tiene asociado un color RGB, se puede reprepresentar como una tupla (R,G,B), donde cada valor pertence al intervalo [0, 255]. Esta representación nos permite asociar cada color a un punto en el espacio tridimensional, y por consecuente podemos calcular distancias, de forma que podemos generar grupos de puntos parecidos. Cuanto más cerca estén dos puntos, más parecidos son.

Ambilight espacio de colores

Distribución de los colores R,G,B de una imagen en el espacio 3D. Este algoritmo tiene dos problemas: no es el más eficiente y su ejecución depende de la posición inicial de los centroides, que suele ser aleatoria, y por consecuente el resultado puede variar entre ejecuciones para una misma entrada. A pesar de esto el resultado obtenido es bastante bueno, aunque ahora el framerate ha bajado a 20fps. Sigue estando dentro del umbral aceptable, y además hay mucho margen de optimización en el código.

Arduino y comunicación por el puerto serie

El último paso consiste en conectar el Arduino a una tira LED RGB. En mi caso utilizo unas que implementan el protocolo WS2812b, y que te permiten enviar un array de colores, y la propia tira asigna cada color del array a cada led de esta. Esto permite controlar los LEDs de forma independiente, aunque para el prototipo utilizo un único color para toda la tira. Por suerte para nosotros Adafruit ofrece la librería NeoPixel para controlar este tipo de tiras LED.

Finalmente tenemos que enviar desde el cliente el color a nuestro Arduino. Como primera versión he utilizado comunicación a través del puerto serie. Para ello envío cadenas con el formato R,G,B;, que el Arduino lee y decodifica.

Mejoras

El resultado ha sido genial, estoy muy contento con el resultado. Además el proyecto era mucho más complejo de lo que parecía inicialmente.

Hay muchas partes que se pueden mejorar y optimizar, por ejemplo:

  • Captura de imágenes más eficiente y multiplataforma.
  • Algoritmo de análisis de color más eficiente y determinista.
  • Utilizar un protocolo de comunicación a través de internet, para desacoplar el Arduino del ordenador.
  • Analizar diferentes regiones de la imagen e iluminar cada LED con un color.
  • Añadir una interfaz gráfica.

Si te ha resultado útil este artículo agradecería si te suscribes a mi newsletter. Recibirás contenido exclusivo de calidad y también me ayudarás enormemente. Cada suscripción apoya el trabajo que realizo y me permite conocer mejor los temas que te interesan, de forma que puedo mejorar los conocimientos que comparto contigo.


Posts recientes