En el artículo de hoy te voy a enseñar a difuminar una imagen aplicando el desenfoque de caja con Python. Vamos a leer la imagen como una matriz e ir modificando cada pixel.
El desenfoque de caja se logra tomando los pixeles vecinos del pixel en cuestión, tomando los RGB de cada uno y promediándolos.
Con “pixeles vecinos” me refiero al pixel que está arriba, abajo, a la derecha, a la izquierda, y en las 4 diagonales posibles.
Al final podremos implementar el desenfoque de caja o box blur en Python manualmente sin usar librerías.
Requisitos
Necesitas Python y PIP. Cuando tengas pip instala numpy e imageio con: pip install numpy imageio
No es obligatorio usar numpy (pero el ejercicio sí lo requiere, así que lo he usado), basta con imageio para obtener los pixeles de la imagen como una matriz así como lo hice cuando trabajé con esteganografía en Python.
Vamos a trabajar con una imagen BMP pero cualquier imagen que pueda ser convertida a matriz debería funcionar.
Calculando difuminado
Este algoritmo es un poco complejo de explicar por dos cosas: el recorrido de los pixeles vecinos cuando estamos en alguna esquina, y el cálculo de los valores.
Para este caso vamos a suponer que sabemos cómo recorrer los vecinos y que solo nos debemos preocupar por acceder a ellos y calcular el nuevo color.
Nota: no debemos modificar la matriz original; debemos usarla únicamente para calcular el color de difuminado de este desenfoque de caja en Python, pero los nuevos valores deben ser colocados en una matriz limpia.
He creado una tabla para ponerte un simple ejemplo; en este caso estoy suponiendo que tenemos una imagen de 3 x 3 pixeles, pero nos va a servir.
Cada cuadro de la tabla representa un pixel, y los 3 valores representan los niveles de Red, Green y Blue respectivamente.
| X / Y | 1 | 2 | 3 |
| ----------- | ----------- | ----------- |----------- |
| 1 | `[200, 4, 8]` | `[60, 90, 25]` | `[5, 7, 18]` |
| 2 | `[21, 6, 28]` | `[158, 219, 123]` | `[85, 115, 92]` |
| 3 | `[76, 35, 63]` | `[82, 95, 122]` | `[222, 235, 172]` |
Entonces para aplicar el difuminado tenemos que ir pixel por pixel y revisar sus vecinos. Para el primer ejemplo tenemos el pixel que está en 1,1 mismo que es [200, 4, 8]
.
Sus vecinos son los que están en 2,1
, 1,2
y 2,2
. Entonces los valores que tenemos (también contamos el pixel actual) son:
- 200, 4, 8
- 60, 90, 25,
- 21, 6, 28
- 158, 219, 123
Así que para cada nivel de color del pixel de 1,1
vamos a poner el promedio de valores de sus vecinos. Calculemos sumatorias.
Para el nivel Red tenemos 200 + 60 + 21 + 158 = 439
. Luego el Verde queda en 319
y el Azul en 184
.
El promedio se calcula dividiendo los valores entre 4 (porque en este caso en total son 4 pixeles contando los vecinos y el actual; en ocasiones habrá 8 vecinos).
Así que para este pixel específico (el de 1,1) pasa de ser [200, 4, 8]
a [109.75, 79.75, 46]
. Debemos redondear así que el final sería [109, 79, 46]
.
Y eso que acabo de describir debemos hacerle a todos los pixeles.
Recorriendo mini-matriz dentro de matriz
Antes de pasar al código para el difuminado de una imagen con desenfoque de caja en Python veamos cómo recorrer una pequeña matriz dentro de nuestra matriz.
La pequeña matriz a la que me refiero es la de los vecinos de cada pixel, que en algunos casos será de 3 x 3 y en otros (en las esquinas) de 2 x 2.
Para no complicarnos con infinitas sentencias if vamos a definir el inicio y fin del recorrido de los vecinos, tomando en cuenta que cuando sea una esquina no debemos salirnos de los límites.
# Vamos a recorrer una mini matriz en la caja
inicio_y = y-1
inicio_x = x-1
fin_y = y+1
fin_x = x+1
if inicio_y < 0:
inicio_y = 0
if inicio_x < 0:
inicio_x = 0
if fin_x >= ancho:
fin_x = ancho - 1
if fin_y >= alto:
fin_y = alto - 1
suma_red = 0
suma_green = 0
suma_blue = 0
conteo = 0
while inicio_y <= fin_y:
indice_x = inicio_x
while indice_x <= fin_x:
# Aquí calculamos el promedio
indice_x += 1
conteo += 1
inicio_y += 1
Para no salirnos de los límites o irnos a índices negativos estamos comparando desde las líneas 6 hasta la 13, y arreglando también esos problemas.
Después estamos haciendo un ciclo while recorriendo los vecinos en las líneas 18 a 20. Por cierto fíjate en que también llevamos un conteo, ese nos va a servir para sacar el promedio.
Esto del recorrido de los vecinos me recuerda al juego de Conecta 4 y 3 en línea.
Desenfoque de caja con Python
Después de todo lo anterior es momento de ver la función. Debo decir que me hizo pensar mucho más que los otros ejercicios sobre los filtros en las imágenes.
def difuminado(nombre_imagen):
matriz_original = leer_imagen(nombre_imagen)
# Eliminar referencia para crear una nueva matriz
matriz_difuminada = matriz_original[:]
ancho = len(matriz_difuminada[0])
alto = len(matriz_difuminada)
for y in range(alto):
for x in range(ancho):
# Vamos a recorrer una mini matriz en la caja
inicio_y = y-1
inicio_x = x-1
fin_y = y+1
fin_x = x+1
if inicio_y < 0:
inicio_y = 0
if inicio_x < 0:
inicio_x = 0
if fin_x >= ancho:
fin_x = ancho - 1
if fin_y >= alto:
fin_y = alto - 1
suma_red = 0
suma_green = 0
suma_blue = 0
conteo = 0
while inicio_y <= fin_y:
indice_x = inicio_x
while indice_x <= fin_x:
pixel_matriz_original = matriz_original[inicio_y][indice_x]
suma_red += pixel_matriz_original[0]
suma_green += pixel_matriz_original[1]
suma_blue += pixel_matriz_original[2]
indice_x += 1
conteo += 1
inicio_y += 1
promedio_red = round(suma_red/conteo)
promedio_green = round(suma_green/conteo)
promedio_blue = round(suma_blue/conteo)
matriz_difuminada[y][x] = [
promedio_red, promedio_green, promedio_blue]
return matriz_difuminada
Esa es la función que recibe la matriz de la imagen y la recorre normalmente. Luego hace el recorrido de los vecinos como ya lo expliqué anteriormente, y ya en las líneas 30 hasta 39 calcula el promedio de los colores (convertimos a entero con round).
Luego en la línea 40 asigna el nuevo pixel, con el nivel R, G y B promediados. Y así por cada pixel hasta tener el resultado final.
Obviamente esa función no es la única, ya que nos falta la que convierte la imagen a matriz. El código completo queda así:
"""
https://parzibyte.me/blog
"""
import numpy as np
import imageio
NOMBRE_IMAGEN = "travel.bmp"
def leer_imagen(ruta):
return np.array(imageio.imread(ruta), dtype='int').tolist()
def guardar_imagen(ruta, matriz):
return imageio.imwrite(ruta, np.array(matriz, dtype="uint8"))
def difuminado(nombre_imagen):
matriz_original = leer_imagen(nombre_imagen)
# Eliminar referencia para crear una nueva matriz
matriz_difuminada = matriz_original[:]
ancho = len(matriz_difuminada[0])
alto = len(matriz_difuminada)
for y in range(alto):
for x in range(ancho):
# Vamos a recorrer una mini matriz en la caja
inicio_y = y-1
inicio_x = x-1
fin_y = y+1
fin_x = x+1
if inicio_y < 0:
inicio_y = 0
if inicio_x < 0:
inicio_x = 0
if fin_x >= ancho:
fin_x = ancho - 1
if fin_y >= alto:
fin_y = alto - 1
suma_red = 0
suma_green = 0
suma_blue = 0
conteo = 0
while inicio_y <= fin_y:
indice_x = inicio_x
while indice_x <= fin_x:
pixel_matriz_original = matriz_original[inicio_y][indice_x]
suma_red += pixel_matriz_original[0]
suma_green += pixel_matriz_original[1]
suma_blue += pixel_matriz_original[2]
indice_x += 1
conteo += 1
inicio_y += 1
promedio_red = round(suma_red/conteo)
promedio_green = round(suma_green/conteo)
promedio_blue = round(suma_blue/conteo)
matriz_difuminada[y][x] = [
promedio_red, promedio_green, promedio_blue]
return matriz_difuminada
guardar_imagen("travel_difuminado.bmp", difuminado(NOMBRE_IMAGEN))
Al ejecutarlo, la salida es correcta, si te fijas se ve borroso, desenfocado o difuminado en la comparativa con la imagen de la izquierda. Con esto hemos aplicado efectivamente el box blur o desenfoque de caja.
Nota: curiosamente la imagen difuminada pesa menos que la imagen original. Me imagino que se comprime de mejor manera o algo así, ya que son menos colores. Que venga un experto en imágenes a iluminarnos y decirnos por qué pasa eso.
Para terminar te dejo con más tutoriales de programación en Python.