Al desarrollar proyectos de visión artificial con YOLO, la etapa final suele requerir una interfaz de usuario (GUI) que permita interactuar con el modelo de forma intuitiva. Aunque herramientas como Qt son populares, Tkinter (TK) sigue siendo una opción ligera y eficiente para prototipos rápidos y herramientas internas, especialmente cuando se complementa con librerías de estilos como ttkbootstrap o herramientas de diseño visual.
Para lograr una aplicación mantenible, es recomendable separar la lógica del negocio de la capa de presentación. A continuación, se presenta una arquitectura basada en el patrón controlador-vista para integrar la detección de objetos en una ventana funcional.
Estructura del Proyecto
El sistema se divide en tres componentes principales:
- Vista (interfaz.py): Define la disposición de los elementos gráficos.
- Controlador (logica.py): Gestiona la carga de imágenes, la ejecución del modelo YOLO y el procesamiento de resultados.
- Punto de entrada (app.py): Inicializa y coordina los módulos.
Implementación de la Interfaz Gráfica
En este módulo se define la clase base para la ventana, utilizando coordenadas precisas para los lienzos (canvas) donde se mostrarán las imágenes.
import tkinter as tk
from tkinter import ttk
class VentanaBase(tk.Tk):
def __init__(self):
super().__init__()
self.configurar_ventana()
self.inicializar_componentes()
def configurar_ventana(self):
self.title("Sistema de Detección YOLO")
ancho, alto = 650, 550
x_dato = (self.winfo_screenwidth() // 2) - (ancho // 2)
y_dato = (self.winfo_screenheight() // 2) - (alto // 2)
self.geometry(f'{ancho}x{alto}+{x_dato}+{y_dato}')
self.resizable(False, False)
def inicializar_componentes(self):
# Etiquetas
ttk.Label(self, text="Vista Original").place(x=100, y=20)
ttk.Label(self, text="Resultado YOLO").place(x=420, y=20)
# Áreas de visualización
self.canvas_previo = tk.Canvas(self, bg="#d1d1d1", width=280, height=220)
self.canvas_previo.place(x=20, y=50)
self.canvas_resultado = tk.Canvas(self, bg="#d1d1d1", width=280, height=220)
self.canvas_resultado.place(x=340, y=50)
# Entrada de datos y control
ttk.Label(self, text="Ruta de imagen:").place(x=20, y=320)
self.entrada_ruta = ttk.Entry(self)
self.entrada_ruta.place(x=120, y=320, width=350)
self.btn_detectar = ttk.Button(self, text="Ejecutar Inferencia")
self.btn_detectar.place(x=480, y=318, width=140)
class AppFinal(VentanaBase):
def __init__(self, controlador):
self.ctrl = controlador
super().__init__()
self.conectar_eventos()
self.ctrl.vincular_ui(self)
def conectar_eventos(self):
self.btn_detectar.config(command=self.ctrl.procesar_deteccion)
Lógica de Detección y Procesamiento
El controlador se encarga de invocar a la librería ultralytics para procesar la imagen y actualizar los elementos visuales de Tkinter mediante PIL (Pillow).
import os
from PIL import Image, ImageTk
from ultralytics import YOLO
class ProcesadorYOLO:
def __init__(self, ruta_modelo):
self.modelo = YOLO(ruta_modelo)
self.ui = None
self.ref_img1 = None
self.ref_img2 = None
def vincular_ui(self, interfaz):
self.ui = interfaz
def redimensionar_para_canvas(self, ruta, max_w=280, max_h=220):
img = Image.open(ruta)
img.thumbnail((max_w, max_h), Image.Resampling.LANCZOS)
return ImageTk.PhotoImage(img)
def procesar_deteccion(self):
ruta_img = self.ui.entrada_ruta.get()
if not os.path.exists(ruta_img):
print("Archivo no encontrado")
return
# Ejecutar modelo
inferencia = self.modelo(ruta_img)
temp_path = "output_temp.jpg"
for resultado in inferencia:
resultado.save(filename=temp_path)
# Actualizar UI
self.ref_img1 = self.redimensionar_para_canvas(ruta_img)
self.ref_img2 = self.redimensionar_para_canvas(temp_path)
self.ui.canvas_previo.create_image(140, 110, image=self.ref_img1)
self.ui.canvas_resultado.create_image(140, 110, image=self.ref_img2)
Ejecución del Sistema
Finalmente, el script principal instancia el motor de detección y arranca el bucle de la aplicación.
from interfaz import AppFinal
from logica import ProcesadorYOLO
if __name__ == "__main__":
# Ajustar la ruta al peso (.pt) de tu modelo entrenado
cerebro = ProcesadorYOLO("weights/yolov8n.pt")
app = AppFinal(cerebro)
app.mainloop()
Este flujo permite escalar la herramienta fácilmente. Por ejemplo, se pueden añadir selectores de archivos mediante filedialog o barras de progreso sin afectar la lógica de inferencia del modelo YOLO.