Python tiene múltiples librerías para aplicaciones de la vida real. Una de esas librerías es OpenCV, la cual es usada en Visión Computacional. Dicha librería incluye aplicaciones como captura y procesamiento de videos e imágenes, siendo mayormente utilizada en transformación de imágenes, detección de objetos y reconocimiento facial.
Al final de este artículo, nuestra meta es transformar una imagen en su caricatura. Para ello, construiremos una aplicación en Python que convertirá nuestra imagen en una caricatura usando OpenCV.
—
Importaremos los siguientes módulos:
cv2: Es una librería altamente optimizada que se enfoca en aplicaciones en tiempo real.
easygui: Módulo para la programación GUI fácil y rápidamente en Python.
Numpy: Es una librería para Python con soporte para matrices y arrays grandes y multidimensionales, junto con una gran colección de funciones matemáticas de alto nivel para operar en estas matrices.
Imageio: Es una librería que proporciona una interfaz fácil para leer y escribir una amplia gama de datos de imágenes.
Matplotlib: Librería para crear visualizaciones estáticas, animadas e interactivas en Python.
os: Librería que nos proporciona funciones para interactuar con el sistema operativo.
import cv2
import easygui
import numpy as np
import imageio
import sys
import matplotlib as mpl
import os
import tkinter as tk
from tkinter import messagebox
from tkinter import filedialog
from tkinter import *
from PIL import ImageTk, Image
En este paso, construimos la ventana principal donde se encontrarán los botones, etiquetas e imágenes de nuestra aplicación.
def cargar():
rutaImagen = easygui.fileopenbox()
caricaturizar(rutaImagen)
El código anterior lanza una ventana emergente para seleccionar un archivo de nuestro dispositivo cada vez que lo ejecutamos. El método fileopenbox()
en el módulo easyGUI
nos devuelve la ruta del archivo seleccionado en formato string.
Ahora debes preguntarte, ¿cómo nuestro programa procesará una imagen?. Debemos recordar que para nuestra computadora todo esta formado por números. Por lo tanto, en el siguiente código, usaremos Numpy para convertir nuestra imagen en un array.
def caricaturizar(ruta_imagen)
# leer la imagen
imagenOriginal = cv2.imread(rutaImagen)
imagenOriginal = cv2.cvtColor(imagenOriginal, cv2.COLOR_BGR2RGB)
# confirmar que la imagen ha sido seleccionada
if imagenOriginal is None:
print("No se encontró ninguna imagen. Elige un archivo apropiado")
sys.exit()
redimen1 = cv2.resize(imagen_original, (960, 540))
imread()
es un método de cv2
que es usado para almacenar imágenes en forma de arrays. Esto nos permite realizar operaciones de acuerdo con nuestras necesidades. La imagen es procesada como un array, cuyos valores representan los valores R, G, y B de cada pixel.
NOTA: redimensionamos la imagen después de cada transformación para desplegar todas las imágenes en una escala similar.
Para convertir una imagen en una caricatura se requiere de muchas transformaciones. Primero, la imagen es convertida a escala de grises. Después, la imagen en escala de grises es suavizada, y tratamos de extraer los bordes de la imagen. Finalmente, formamos una imagen a color y la unimos con los bordes. Esto crea una caricatura con los bordes y los colores de la imagen original realzados.
#Convertir imagen a escala de grises
imagenEscalaGrises = cv2.cvtColor(imagenOriginal, cv2.COLOR_BGR2GRAY)
redimen2 = cv2.resize(imagenEscalaGrises, (960, 540))
cvtColor()
es un método de cv2
que recibe una imagen como primer argumento y una conversión de espacio de colores como segundo argumento. En este caso hemos usado BGR2GRAY
para convertir nuestra imagen a una escala de grises.
El código anterior producirá el siguiente resultado:
#Aplicar medianBlur para suavizar imagen
escalaGriseSuavizada = cv2.meadianBlur(imagenEscalaGrises, 5)
redimen3 = cv2.resize(escalaGriseSuavizada, (960, 540))
Para suavizar una imagen simplemente aplicamos un efecto de desenfoque. Esto lo hacemos usando la función medianBlur()
, la cual reemplaza el elemento central de la imagen por la mediana de todos los pixeles en el área del kernel. Esta operación procesa los bordes mientras elimina el ruido, creando un efecto de desenfoque.
El código anterior producirá el siguiente resultado:
#recuperando los bordes de imagen para efecto caricatura
bordesImagen = cv2.adaptiveThreshold(escalaGriseSuavizada, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 9, 9)
redimen4 = cv2.resize(bordesImagen, (960,540))
El efecto de caricatura tiene dos características:
En este paso, trabajaremos en la primer característica. Trataremos de recuperar los bordes y remarcarlos. Esto lo logramos con el uso del método adaptiveThreshold()
.
El código anterior producirá el siguiente resultado:
#aplicar filtro bilateral para remover ruido
imagenColor = cv2.bilateralFilter(imagenOriginal, 9, 300, 300)
redimen5 = cv2.resize(imagenColor, (960, 540))
En el código anterior, trabajamos en la segunda característica. Hemos preparado una imagen colorida para unirla con los bordes y crear un efecto de caricatura. Usando el método bilateralFilter()
removemos el ruido y la aspereza en los colores.
El código anterior producirá el siguiente resultado:
#unir bordes con nuestra imagen a color
imagenCaricaturizada = cv2.bitwise_and(imagenColor, imagenColor, mask=bordesImagen)
redimen6 = cv2.resize(imagenCaricaturizada, (960, 540))
Es momento de combinar las dos características. Esto podemos lograrlo realizando un “enmascarado”. Mediante el método bitwise_and
enmascaramos las dos imágenes previamente obtenidas. Esto finalmente “caricaturizará” nuestra imagen.
El código anterior producirá el siguiente resultado:
#Plotear todas las transiciones
imagenes = [redimen1, redimen2, redimen3, redimen4, redimen5, redimen6]
fig, axes = plt.subplots(3, 2, figsize=(8.8),
subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0.1, wspace=0.1))
for i, ax in enumerate(axes.flat):
ax.imshow(images[i], cmap='gray')
//código botón guardado
plt.show
Para plotear todas las imágenes, primero debemos hacer una lista de ellas. La lista es llamada imagenes
y contiene todas las imágenes redimensionadas. Después, creamos una figura para plotear una a una las imágenes en cada subplot usando el método imshow()
.
Finalmente, la función plt.show()
plotea todas las transiciones en un solo plot.
El código anterior producirá el siguiente resultado:
ventanaPrincipal = tk.Tk()
ventanaPrincipal.geometry('400x400')
ventanaPrincipal.title('Caricaturiza tu imagen')
ventanaPrincipal.configure(background='white')
etiqueta = Label(ventanaPrincipal, background='#CDCDCD', font=('calibri', 20, 'bold'))
botonCaricaturizar = Button(ventanaPrincipal, text="Caricaturizar una imagen",
command=cargar, padx=10, pady=5)
botonCaricaturizar.configure(background='#364156', foreground='white', font=('calibri', 10, 'bold'))
botonCaricaturizar.pack(side=TOP, pady=50)
botonGuardar = Button(ventanaPrincipal, text='Guardar imagen caricaturizada',
command=lambda: guardar(redimen6, rutaImagen), padx=30, pady=5)
botonGuardar.configure(background='#364156', foreground='white',
font=('calibri', 10, 'bold'))
botonGuardar.pack(side=TOP, pady=50)
El código anterior crea un botón tan pronto como la transformación de la imagen es hecha. Dándole al usuario la opción de guardar la imagen caricaturizada.
def guardar(redimen6, rutaImagen):
#guardar imagen usando imwrite()
nombreNuevo = "imagen_caricaturizada"
ruta1 = os.path.dirname(rutaImagen)
extension = os.path.splitext(rutaImagen)[1]
ruta = os.path.join(ruta1, nombreNuevo+extension)
cv2.imwrite(ruta, cv2.cvtColor(redimen6, cv2.COLOR_RGB2BGR))
mensaje = "Imagen guardada como " + nombreNuevo + "en " + path
messagebox.showinfo(title=None, message=mensaje)
En este paso, la idea es guardar nuestra imagen caricaturizada. Para esto, tomamos rutaImagen
y cambiamos el nombre del antiguo archivo por un nuevo nombre. Posteriormente, almacenamos la imagen caricaturizada en la misma carpeta que imagenOriginal
agregando el nuevo nombre al título del archivo.
Para esto, extraemos el título del archivo usando el método os.path.dirname()
. Además, el método os.path.splitext()
es usado para extraer la extensión del archivo.
La variable nombreNuevo
almacena "imagen_caricaturizada"
como el nombre de un nuevo archivo. Mientras que la expresión os.path.join(ruta1, nombreNuevo+extension)
concatena el directorio del archivo con su nuevo nombre y extensión. Esto forma la ruta completa para nuestro nuevo archivo.
El método imwrite()
de la librería cv2
es usado para guardar el archivo en la ruta mencionada anteriormente. La expresión cv2.cvtColor(redimen6, cv2.COLOR_RGB2BGR)
es usada para asegurar que ningún color se extraiga o resalte mientras guardamos nuestra imagen. Así, finalmente, se le da al usuario la confirmación de que la imagen ha sido guardada con el nombre y la ruta del archivo.
ventanaPrincipal.mainloop()
Puedes descargar el código fuente del projecto aquí