lunes, 19 de febrero de 2024

Mapas estilo LEGO con R

Tras el bombardeo que el algoritmo de LinkedIn me ha propinado días atrás con mapas hechos de fichas LEGO con ArcGIS Pro o QGIS, he querido escribir mi propia función en R que vaya un poco más allá: se trata no solo de representar mapas con fichas de LEGO, sino que se utilicen diferentes tamaños y orientaciones de los ladrillos como ocurriría con un LEGO real.



Tomamos como ejercicio base el siguiente mapa de elevación de la Sierra de Madrid coloreado en gradientes, y al que aplicaremos una serie de pasos:


  • Reescalado: reducimos la imagen al tamaño deseado para el mapa de estilo LEGO final, en este caso 64x64 píxeles. El remuestreo por media de altitudes funciona muy bien en un mapa con degradados suaves como éste, aunque luego veremos casos donde será más conveniente usar otro tipo de promediados (mediana, moda, nearest neighbour):

  • Segmentación de color: ahora discretizamos los anteriores degradados con un clustering k-means de 6 colores obteniendo una imagen con una paleta de colores reducida. Se muestra también el histograma con el color de cada centroide:


  • LEGO: la imagen simplificada tanto en resolución como en color ya podemos transformarla en un mapa LEGO. Se ejecuta una rutina que, en base a una lista de fichas LEGO suministrada por nosotros (en este caso bloques de tamaños: 8x8, 6x6, 4x6, 4x4, 2x4, 2x3, 2x2, 1x4, 1x3, 1x2, 1x1), respeta los colores que necesitamos lograr en cada píxel buscando zonas de color continuo donde encajen las fichas. Se empieza por las más grandes, y las que no son cuadradas se tratan de colocar en las dos posibles orientaciones, normal y rotadas 90º (hacer clic para ver el mapa a máxima resolución y poder distinguir las piezas usadas):

  • Color: para el coloreado de las piezas se aplica una curva gamma por canal (y=x1/gamma) que lleva el gris medio del bloque unitario LEGO de partida, un bitmap monocromo de 46x46 píxeles, al color del clúster correspondiente. Los tonos por encima o por debajo de este gris medio se acomodan en la salida de acuerdo a la curva. Finalmente se dibujan unos bordes pseudo 3D en la periferia del ladrillo para resaltar sus dimensiones:

  • Inventario: mientras se ejecutaba el paso anterior íbamos contando el número de piezas de cada tamaño y color que se iban encajando, lo que nos permite dibujar este inventario indicando el número de fichas empleadas de cada tipo. Sería algo así como el manual de instrucciones de montaje (hacer clic par ver en resolución original):

~~~

Como también hay que contar las miserias, decir que el algoritmo de segmentación usado (k-means) penaliza mucho los colores que aparecen poco en la imagen descartándolos. Esto que puede sonar razonable, significa que nos tenemos que despedir de los detalles de color pequeños.

Así es una constante que aplicado a retratos "perdamos" el color de los ojos del retratado, lo que se percibe de forma bastante contraproducente. Solución: coger un retrato de Rihanna con los ojos cerrados!


En cualquier caso el procesado no está pensado con fotografías en mente, sino más bien imágenes sintéticas que dispongan de una reducida gama de tonos, claramente identificables y donde estos aparezcan en grandes áreas, como por ejemplo mapas.

~~~

Cuando la imagen a reducir tiene un fondo de color uniforme que no queremos convertir en piezas LEGO (p.ej. regiones de mar en un mapa), los bordes se "contaminan" de este fondo dando lugar a clústers falsos que generan colores intermedios indeseables.

Personalizamos una rutina que puede parametrizarse con cierto color de fondo: los píxeles de la imagen de entrada que tengan ese color serán ignorados, no participando ni en el remuestreo ni en el clustering de colores posterior.

En las zonas frontera se calcula sobre una cuadrícula definida por el tamaño de salida el % de superficie ocupada por fondo y por mapa: si en una celda de la cuadrícula más del 50% es fondo, esa celda se clasificará como fondo en la imagen de salida siendo ignorada en el clustering. Si por el contrario al menos el 50% de una celda corresponde a mapa, dará lugar a un píxel LEGO en el mapa de salida, con el color promedio en la cuadrícula pero habiendo descartado previamente los píxeles con color de fondo contenidos en ella. Se explica gráficamente aquí.

Este mapa de África tenía originalmente un fondo blanco. Alternamos el perfilado del remuestreo normal (con "contaminación" en los bordes), con la versión mejorada:



El clustering se hace como siempre sobre los colores presentes en la imagen reescalada, de ahí que sea muy rápido. Aquí podemos ver el proceso para llegar a un resultado donde los bordes de África han quedado perfectamente definidos sin contaminarse del color blanco del fondo (hacer clic para ver en alta resolución):


~~~

Al margen del problema en los bordes contra el fondo que acabamos de solucionar, el reescalado por media funciona bien con gradientes de color, pero en imágenes con amplias zonas de color uniforme también va a generar nuevos colores intermedios en las zonas frontera que van a introducir ruido a la hora de hacer el clustering de color.

Así otro punto de mejora es permitir realizar remuestreos diferentes a la media que no "fabriquen" nuevos colores: mediana, moda o nearest neighbour. La mediana todavía puede dar lugar a colores nuevos cuando se toma un número par de muestras, así que añadimos una versión alternativa que evita el problema y que llamaremos creativamente "mediana 2.0".

Reducimos a la cuarta parte de su tamaño original un mapa de Formentera compuesto de 4 colores base categóricos (el mar y tres categoría de isla). El reescalado por media y la mediana general dan lugar a nuevos colores. En cambio nearest neighbour (muestra), la moda (valor más frecuente) y la mediana modificada (valor central) no se salen de la paleta original al tomar valores presentes en la imagen de partida:


~~~

Por último he añadido la opción de aleatorizar los colores de salida, de modo que en los gradientes suaves pasen a distinguirse mejor los clústers (hacer clic para ver a mayor resolución):


~~~

Para terminar añadimos algunos ejemplos más:






Y una animación utilizando la función diseñada, en este caso con un acabado más futurista, 'The Grid':


~~~

Repositorio con el código R: GitHub.

No hay comentarios:

Publicar un comentario

Por claridad del blog, por favor trata de utilizar una sintaxis lo más correcta posible y no abusar del uso de emoticonos, mayúsculas y similares.