GUI con Tkinter y OpenCV en Python | Imágenes ?️

Por Administrador

Te doy la bienvenida a un nuevo tutorial en el que veremos como crear una GUI o interfaz gráfica de usuario con ayuda de Tkinter, y además vamos a ver como acoplar las imágenes que usamos y generamos con OpenCV. ¡Vamos por ello!.

CONTENIDO

  • GUI usando Tkinter y OpenCV con Python
    • Importando los paquetes necesarios
    • Función para leer la imagen de entrada con OpenCV y visualizarla en la GUI.
      • Insertar la imagen leída con OpenCV a la GUI
      • Añadir un label o etiqueta de información para la imagen de entrada
      • Limpiar la imagen de salida y la selección de los radio buttons cada vez que se lea una nueva imagen
    • Función que realizará el efecto de resaltar un color de acuerdo a la selección de los radio buttons
      • Insertar la imagen de salida obtenida con OpenCV en la GUI
      • Añadir un label o etiqueta de información para la imagen ed salida
    • Creación de la ventana, botón, labels y radio buttons con Tkinter
      • Creando los labels donde se ubicarán las imágenes de entrada y salida dadas por OpenCV
      • Añadimos un label de información para preguntar al usuario por el color que desea resaltar
      • Creando radio buttons con Tkinter
      • Creando un botón para leer la imagen de entrada con Tkinter y OpenCV
    • Probándo la GUI creada con Tkinter
    • Referencias

GUI usando Tkinter y OpenCV con Python

Para este tutorial no he querido únicamente leer una imagen y mostrarla en una GUI, sino que he querido realizar un mini proyecto con una imagen de entrada y salida. Para esto usaré uno de los tutoriales que ya habíamos visto previamente, sobre el efecto sin city o resaltar un color de una imagen, mientras el resto de la imagen permanece en escala de grises. Esto lo vimos en este tutorial: ? RESALTANDO UN COLOR de un fondo en grises | OpenCV con Python. O también puedes echarle un vistazo al videotutorial.

La única diferencia con aquel videotutorial es que hoy no solo estaremos resaltando el color rojo, sino también el amarillo y azul, dependiendo de lo que vaya eligiendo el usuario. Por ello te recomiendo revisar la programación que vimos antes con relación a este efecto, ya que te ayudará mucho a entender los procedimientos que realizaremos. Además hoy nos centraremos en la realización de la GUI con Tkinter y OpenCV.

Las imágenes que usaremos hoy las he almacenado en una carpeta en de mi escritorio, las puedes ver a continuación:

Figura 1: Imágenes de entrada.

Cabe destacar que puedes realizar pruebas con otras imágenes también.

Así que nuestro objetivo de hoy será construir la siguiente GUI:

Figura 2: GUI objetivo.

En la figura 2 podemos ver la GUI que hoy realizaremos. A la izquierda tenemos:

  • Botón Elegir Imagen, que nos permitirá elegir una imagen.
  • Label «IMAGEN DE ENTRADA».
  • Imagen de entrada.
  • Label «¿Qué color te gustaría resaltar?».
  • 3 Radio butttons con los colores a elegir: rojo, amarillo y azul celes.

Mientras que a la derecha de la GUI tenemos:

  • Label «IMAGEN DE SALIDA».
  • Imagen de salida.

Ahora que sabemos lo que debemos realizar, ¡vamos con el código!.

Importando los paquetes necesarios

from tkinter import *
from tkinter import filedialog
from PIL import Image
from PIL import ImageTk
import cv2
import imutils
import numpy as np

Línea 1 y 2: Vamos a importar Tkinter para poder construir nuestra GUI con distintos widgets. En la línea 2 de tkinter importamos filedialog que nos permitirá crear un diálogo de archivos.

Como te comentaba, Tkinter nos va a servir para generar la interfaz gráfica de usuario o GUI. Este ya viene integrado en Python como paquete estándar, por lo que no es necesario instalarlo. Si quieres entender su funcionamiento o practicar un poco, te recomiendo este post, que está lleno de ejemplos y explicado de una forma fácil de entender.

Línea 3 y 4: Estaremos usando PIL (Python Imaging Library) que es una librería que nos permitirá trabajar o manipular imágenes, es decir que gracias a su ayuda podremos conectar los resultados que obtengamos de OpenCV con la GUI. Para ello necesitamos Image e ImageTk. En caso de que no tengas instalada esta librería, aunque lo más seguro es que si, puedes instalarla con pip install pillow. 

Línea 5 a 7: Importamos OpenCV, imutils y numpy.

Función para leer la imagen de entrada con OpenCV y visualizarla en la GUI

A continuación vamos a construir la función elegir_imagen que estará asociada al botón Elegir Imagen. Esta nos permitirá abrir un diálogo de archivos para luego leer una imagen y ubicarla en la GUI.

def elegir_imagen():
    # Especificar los tipos de archivos, para elegir solo a las imágenes
    path_image = filedialog.askopenfilename(filetypes = [
        ("image", ".jpeg"),
        ("image", ".png"),
        ("image", ".jpg")])

    if len(path_image) > 0:
        global image

        # Leer la imagen de entrada y la redimensionamos
        image = cv2.imread(path_image)
        image= imutils.resize(image, height=380)

        # Para visualizar la imagen de entrada en la GUI
        imageToShow= imutils.resize(image, width=180)
        imageToShow = cv2.cvtColor(imageToShow, cv2.COLOR_BGR2RGB)
        im = Image.fromarray(imageToShow )
        img = ImageTk.PhotoImage(image=im)

        lblInputImage.configure(image=img)
        lblInputImage.image = img

        # Label IMAGEN DE ENTRADA
        lblInfo1 = Label(root, text="IMAGEN DE ENTRADA:")
        lblInfo1.grid(column=0, row=1, padx=5, pady=5)

        # Al momento que leemos la imagen de entrada, vaciamos
        # la iamgen de salida y se limpia la selección de los
        # radiobutton
        lblOutputImage.image = ""
        selected.set(0)

Línea 9: Creamos la función elegir_imagen.

Línea 11 a 14: Mediante filedialog.askopenfilename abriremos un diálogo de archivos en el cual podremos especificar los tipos de archivos que este nos pueda mostrar. En filetypes especificaremos los tipos de archivos, que en este caso serán jpg, jpeg y png.

Línea 16 y 17: Si existe un path asociado a la imagen, entonces procedemos a declarar global image, que nos permitirá emplear dicha variable, que corresponde a la imagen de entrada. Esta la he declarado más adelante en la línea 95.

Línea 20 a 21: Leemos la imagen de entrada y la redimensionamos a un ancho de 380 pixeles. Esta es la imagen con la que estaremos trabajando para aplicarle el efecto sin city.

Insertar la imagen leída con OpenCV a la GUI

Línea 24 y 25: Redimensionamos la imagen de entrada nuevamente, esta vez a un ancho de 180 pixeles. Y transformamos esta imagen de BGR a RGB.

Hay que tomar especial atención en este paso, debido a que OpenCV lee las imágenes en BGR mientras que PIL trabaja con imágenes en RGB. Si no realizas este cambio, las imágenes se verán así: 

Figura 3: Imágenes leídas en BGR en vez de RGB y visualizadas en la GUI.

Línea 26 y 27: Para incorporar imageToShow a la GUI vamos a usar Image.fromarray y dentro especificamos imageToShow, y esta resultante la vamos a llevar como argumento a ImageTk.PhotoImage. De este modo obtendremos la imagen en formato ImageTk.

Línea 29 y 30: Ahora vamos a ubicar img en la GUI mendiante lblInputImage (este label lo estaremos declarando en la línea 101). Luego tendremos que agregar la línea 30 para que la imagen pueda visualizarse en la GUI y no sea borrada. Puedes darle un vistazo a este post para más información.

Añadir un label o etiqueta de información para la imagen de entrada

Línea 33 y 34: Vamos a crear un label para que se ubique sobre la imagen de entrada, este dirá «IMAGEN DE ENTRADA» y se ubicará en la columna 0, fila 1. Además con padx y pady agregamos cierta separación entre los widgets.

Limpiar la imagen de salida y el elemento seleccionado de los radio buttons cada vez que se lea una nueva imagen

Línea 39: Limpiamos el contenido de lblOutputImage, que es la etiqueta donde se presentará la imagen de salida (a esta etiqueta la declaramos en la línea 105). Esto cada vez que se escoja una nueva imagen.

Línea 40:  Limpiamos la selección del radio button cada vez que se lea una nueva imagen.

Función que realizará el efecto de resaltar un color de acuerdo a la selección en los radio buttons

Ahora incorporaremos la programación del tutorial que habíamos realizado antes, sobre resaltar un color mientras lo demás permanece en escala de grises, para obtener la imagen de salida que se visualizará en la GUI.

def deteccion_color():
    global image
    if selected.get() == 1:
        # Rojo
        rangoBajo1 = np.array([0, 140, 90], np.uint8)
        rangoAlto1 = np.array([8, 255, 255], np.uint8)
        rangoBajo2 = np.array([160, 140, 90], np.uint8)
        rangoAlto2 = np.array([180, 255, 255], np.uint8)

    if selected.get() == 2:
        # Amarillo
        rangoBajo = np.array([10, 98, 0], np.uint8)
        rangoAlto = np.array([25, 255, 255], np.uint8)

    if selected.get() == 3:
        # Azul celeste
        rangoBajo = np.array([88, 104, 121], np.uint8)
        rangoAlto = np.array([99, 255, 243], np.uint8)
        
    imageGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    imageGray = cv2.cvtColor(imageGray, cv2.COLOR_GRAY2BGR)
    imageHSV = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    if selected.get() == 1:
        # Detectamos el color rojo
        maskRojo1 = cv2.inRange(imageHSV, rangoBajo1, rangoAlto1)
        maskRojo2 = cv2.inRange(imageHSV, rangoBajo2, rangoAlto2)
        mask = cv2.add(maskRojo1, maskRojo2)
    else:
        # Detección para el color Amarillo y Azul celeste
        mask = cv2.inRange(imageHSV, rangoBajo, rangoAlto)

    mask = cv2.medianBlur(mask, 7)
    colorDetected = cv2.bitwise_and(image, image, mask=mask)

    # Fondo en grises
    invMask = cv2.bitwise_not(mask)
    bgGray = cv2.bitwise_and(imageGray, imageGray, mask=invMask)

    # Sumamos bgGray y colorDetected
    finalImage = cv2.add(bgGray, colorDetected)
    imageToShowOutput = cv2.cvtColor(finalImage, cv2.COLOR_BGR2RGB)

    # Para visualizar la imagen en lblOutputImage en la GUI
    im = Image.fromarray(imageToShowOutput)
    img = ImageTk.PhotoImage(image=im)
    lblOutputImage.configure(image=img)
    lblOutputImage.image = img

    # Label IMAGEN DE SALIDA
    lblInfo3 = Label(root, text="IMAGEN DE SALIDA:", font="bold")
    lblInfo3.grid(column=1, row=0, padx=5, pady=5)

Línea 42 y 43: Creamos la función deteccion_color y declaramos global image para usar la imagen de entrada de la línea 21.

Línea 44 a 49: Si de los radio buttons se ha seleccionado el primero correspondiente al color rojo, se especifican los rangos del color rojo en el espacio de color HSV. 

NOTA: Para más información sobre la detección de color puedes visitar el tutorial, DETECCIÓN DE COLORES Y Tracking en OpenCV – Parte2.

Línea 51 a 54: Si de los radio buttons se ha seleccionado el segundo correspondiente al color amarillo, se especifica el rango del color amarillo en el espacio de color HSV. 

Línea 56 a 59: Si de los radio buttons se ha seleccionado el tercero correspondiente al color azul celeste, se especifica el rango del color azul celeste en el rango de color HSV. 

Línea 61 a 63: Procedemos con las transformaciones a otros espacios de color, que usaremos para conseguir el efecto de resaltar un color.

Línea 65 a 72: Cuando se selecciona el radio button correspondiente al color rojo, entonces vamos a obtener dos imágenes binarias, las cuales tendremos que sumar para obtener mask. Mientras que si se ha seleccionado el color amarillo o azul celeste, entonces simplemente obtendremos mask usando los rangos especificados.

Línea 74 a 82: Seguimos con el procedimiento para obtener la imagen de salida finalImage, al sumar bgGray y colorDetected.

Línea 83: Transformamos la imagen de salida finalImage de BGR a RGB. Recuerda que si no lo hacemos obtendremos otros colores en la imagen. 

Insertar la imagen de salida obtenida con OpenCV en la GUI

Una vez que hemos obtenido la imagen de salida, vamos a seguir el mismo procedimiento que realizamos en las líneas 26 a 30.

Línea 86 y 87: Aplicamos a imageToShowOutput, Image.fromarray e ImageTK.PhotoImage. Ahora que tenemos nuestra imagen en formato ImageTk y vamos a ubicarla en la GUI.

Línea 88 y 89: Ubicamos img en lblOutputImage (este label lo estaremos declarando en la línea 105).

Añadir un label o etiqueta de información para la imagen de salida

Línea 92 y 93: Creamos un label para que se ubique sobre la imagen de salida, este dirá «IMAGEN DE SALIDA» y se ubicará en la columna 1, fila 0. Además con padx y pady agregamos cierta separación entre los widgets.

Creación de la ventana, botón, labels y radio buttons con Tkinter

Como te pudiste haber dado cuenta en las funciones explicadas temprano, en este post existían labels, un botón y radio buttons ya creados que estabamos llamando.

Bien puede parecer un tanto confuso, pero he explicado en un principio las funciones elegir_imagen y deteccion_color debido a que estas son llamadas por el botón de elegir imagen y los radio buttons respectivamente. Así que vamos a la parte final del programa.

Creando la ventana principal

# Creamos la ventana principal
root = Tk()

Línea 98: Creamos la ventana principal.

Creando los labels donde se ubicarán las imágenes de entrada y salida dadas por OpenCV

# Label donde se presentará la imagen de entrada
lblInputImage = Label(root)
lblInputImage.grid(column=0, row=2)

# Label donde se presentará la imagen de salida
lblOutputImage = Label(root)
lblOutputImage.grid(column=1, row=1, rowspan=6)

Línea 101 y 102: Creamos un label llamado lblInputImage que se ubicará en la ventana  root en la columna 0, fila 2.

Línea 105 y 106: Creamos un label llamado lblOutputImage que se ubicará en la ventana  root en la columna 1, fila 1 y para que ocupe el mismo espacio que los widgets de la izquierda añadiremos rowspan=6.

Añadimos un label de información para preguntar al usuario por el color que desea resaltar

# Label ¿Qué color te gustaría resaltar?
lblInfo2 = Label(root, text="¿Qué color te gustaría resaltar?", width=25)
lblInfo2.grid(column=0, row=3, padx=5, pady=5)

Línea 109 y 110: Añadimos un nuevo label llamado lblInfo2, allí especificamos root que es nuestra ventana principal, seguido el texto: «¿qué color te gustaría resaltar?», y especificaremos un ancho de 25. A este label lo ubicaremos en la columna 0, fila 3 y añadiremos igualmente padx y pady igual a 5, para agregar separación entre los widgets o componentes.

Creando radio buttons con Tkinter

# Creamos los radio buttons y la ubicación que estos ocuparán
selected = IntVar()
rad1 = Radiobutton(root, text='Rojo', width=25,value=1, variable=selected, command= deteccion_color)
rad2 = Radiobutton(root, text='Amarillo',width=25, value=2, variable=selected, command= deteccion_color)
rad3 = Radiobutton(root, text='Azul celeste',width=25, value=3, variable=selected, command= deteccion_color)
rad1.grid(column=0, row=4)
rad2.grid(column=0, row=5)
rad3.grid(column=0, row=6)

Línea 113: Digitamos selected = IntVar(), esto nos permitirá manejar diferentes valores enteros para los radio buttons.

Línea 114: Vamos a crear el primer radio button. Entonces digitamos Radiobutton y especificaremos la ventana en donde se va a visualizar, luego el texto que tendrá, en este caso “Rojo” que va a ser el primero. Especificamos el ancho que tendrá que es 25, luego asignamos value=1 y variable=selected. Cada vez que se seleccione este radio button por ejemplo, selected tomará el valor de value, en este caso 1.  También tendremos que añadir command= deteccion_color para que se llame a la función deteccion_color, cada vez que se presione dicho radio button.

Entonces cada radiobutton que creemos tendrá distinto valor, pero igual variable, selected en este caso, para que cada vez que sea seleccionado uno de ellos podamos saber de cual se trata. 

Línea 115 y 116: Realizaremos el mismo procedimiento para los radio buttons, amarillo y azul celeste. Lo que tendremos que cambiar es: el texto con el que se va a visualizar y los valores en value. Es decir que para el radio button del color Amarillo establecemos value=2, mientras que para azul celeste value=3. Añadiremos también command= deteccion_color para que se llame a esa función, cada vez que se presionen los radio buttons.

Líneas 117 a 119: Ubicaremos cada uno de estos radiobuttons para que se muestren en la GUI. El primero se ubicará en la columna 0, fila 4, el segundo en la columna 0, fila 5 y el último en la columna 0 fila 6.

Creando un botón para leer la imagen de entrada con Tkinter y OpenCV

# Creamos el botón para elegir la imagen de entrada
btn = Button(root, text="Elegir imagen", width=25, command=elegir_imagen)
btn.grid(column=0, row=0, padx=5, pady=5)

root.mainloop()

Línea 122: Usando Button crearemos el botón para elegir la imagen de entrada de nuestro mini proyecto. En él especificamos el nombre de la ventana a visualizar, el texto que tendrá el botón y le especificamos un ancho de 25. Añadiremos command= elegir_imagen para que cada vez que se presione este botón, se llame a la función elegir_imagen.

Línea 123: En la siguiente línea vamos a especificar en donde se va a ubicar el botón, para ello usamos grid. Como argumentos especificamos la columna y fila donde estará ubicado. Además con padx y pady añadimos cierta separación entre widgets o componentes. 

Línea 124: Con root.mainloop() creamos un ciclo sin fin de la ventana que declaramos en la línea 98. De este modo la ventana estará disponible y se podrán visualizar además de interactuar con los componentes creados hasta que el usuario la cierre.

Probando la GUI creada con Tkinter 

Una vez que ejecutemos nuestro script se nos visualizará la siguiente ventana:

Figura 4: Ventana inicial de nuestra GUI.

Si damos clic en el botón Elegir imagen, se nos desplegará un diálogo de archivos donde podremos elegir la imagen de entrada.

Figura 5: Diálogo de archivos se abre al presionar el botón Elegir imagen.

Al elegir la imagen de entrada, esta se visualizará en la GUI.

Figura 6: Visualización de la imagen de entrada en la GUI.

Ahora podremos escoger cada uno de los colores que deseemos resaltar. Veamos los resultados obtenidos con esta imagen:

Figura 7: Pruebas realizadas leyendo la imagen de entrada y seleccionando cada uno de los colores de los radio buttons.

Al elegir una nueva imagen podremos ver como la imagen de salida se limpia y también el radio button seleccionado:

Figura 8: Leyendo una nueva imagen de entrada.

Así también podremos probar con las demás imágenes.

Figura 9: Pruebas realizadas con distintas imágenes de entrada.

Como puedes vez nuestra GUI está funcionando bastante bien. ¡Lo logramos!.

Hay que tomar en cuenta que si aún no leímos una imagen de entrada, pero hemos seleccionado los radio buttons, vamos a obtener errores. Esto podríamos corregirlo añadiendo más controles en la programación, o indicándole al usuario este error mediante mensajes de error o advertencia, pero por ahora hemos llegado al final de este post.

Espero que te haya gustado este contenido, cuéntame ¿qué te pareció?.

Referencias