Una vez que sabemos cargar imágenes con OpenCV, el siguiente paso natural es empezar a transformarlas. Eso es justamente el procesamiento básico de imágenes: aplicar operaciones simples pero muy útiles para modificar, preparar o resaltar información visual.
Estas operaciones son fundamentales porque aparecen en casi cualquier pipeline real. Antes de detectar objetos, entrenar una red o segmentar una escena, muchas veces conviene ajustar el formato, cambiar el brillo, convertir a grises, binarizar o suavizar la imagen.
En este tema veremos las operaciones básicas más comunes con OpenCV y entenderemos para qué sirven dentro de un flujo de visión por computadora.
Procesar una imagen significa aplicar transformaciones sobre su representación numérica. Como ya vimos, una imagen digital es una matriz o conjunto de matrices. Entonces, procesarla implica modificar esos valores con algún objetivo.
Ese objetivo puede ser muy variado:
Casi todos los ejemplos de procesamiento básico empiezan con la lectura de una imagen:
import cv2
imagen = cv2.imread("foto.jpg")
if imagen is None:
raise ValueError("No se pudo cargar la imagen")
A partir de ahí podemos aplicar operaciones sucesivas según la necesidad del problema.
Una de las primeras transformaciones más comunes es convertir la imagen a escala de grises. Esto simplifica el procesamiento y reduce la cantidad de canales a uno solo.
gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)
Esta operación se usa mucho como paso previo para umbralización, detección de bordes, análisis de intensidad y filtrado clásico.
Redimensionar una imagen es otra operación básica. Puede hacerse para adaptar el tamaño a una visualización, reducir costo computacional o normalizar entradas antes de aplicar un algoritmo.
redimensionada = cv2.resize(imagen, (400, 300))
Recordar que en OpenCV el nuevo tamaño se especifica como (ancho, alto).
Otra operación muy frecuente es extraer solo una parte de la imagen, es decir, una región de interés:
recorte = imagen[100:300, 150:400]
Los recortes permiten focalizar el análisis en una zona concreta y también se usan mucho para preparar entradas a modelos o inspeccionar resultados.
Después de transformar una imagen, es habitual guardar el resultado para compararlo o usarlo en otra etapa:
cv2.imwrite("resultado.jpg", recorte)
Este paso resulta útil tanto en experimentos como en pipelines automáticos.
Modificar el brillo significa aumentar o disminuir la intensidad general de los píxeles. Una forma simple de hacerlo es sumar un valor constante:
import numpy as np
brillo = np.clip(imagen + 40, 0, 255).astype(np.uint8)
El uso de np.clip evita que los valores salgan del rango permitido. Este tipo de ajuste puede ser útil para compensar imágenes oscuras o generar variantes de entrenamiento.
El contraste controla la diferencia entre regiones claras y oscuras. Una forma simple de modificarlo es multiplicar por un factor:
contraste = np.clip(imagen * 1.2, 0, 255).astype(np.uint8)
Cuando aumentamos el contraste, las diferencias visuales se intensifican. Esto puede ayudar a resaltar detalles, aunque un exceso también puede saturar información.
OpenCV ofrece una función conveniente para ajustar brillo y contraste al mismo tiempo:
ajustada = cv2.convertScaleAbs(imagen, alpha=1.2, beta=30)
Aquí:
alpha controla el contraste.beta controla el brillo.Esta función es práctica y suele ser preferible a implementar estas operaciones manualmente.
Una operación sencilla pero ilustrativa consiste en invertir intensidades. En una imagen de 8 bits, esto puede hacerse así:
invertida = 255 - imagen
En una imagen en escala de grises, los píxeles oscuros pasan a claros y viceversa. Aunque no siempre tiene utilidad práctica directa, ayuda a entender cómo las operaciones numéricas alteran la imagen.
Una de las operaciones más importantes del procesamiento básico es la umbralización. Consiste en convertir una imagen de grises en una imagen binaria, donde cada píxel pasa a ser negro o blanco según supere o no un umbral.
_, binaria = cv2.threshold(gris, 127, 255, cv2.THRESH_BINARY)
Esto significa:
La umbralización es muy útil para separar foreground y background en problemas relativamente simples.
OpenCV soporta varios tipos de umbralización, no solo la binaria tradicional. Algunas variantes importantes son:
THRESH_BINARYTHRESH_BINARY_INVTHRESH_TRUNCTHRESH_TOZEROTHRESH_TOZERO_INVCada una modifica la imagen de manera diferente, y su utilidad depende del problema específico.
Cuando la iluminación no es uniforme, usar un único umbral global puede fallar. En esos casos, OpenCV ofrece umbralización adaptativa, que calcula umbrales locales para distintas regiones.
adaptativa = cv2.adaptiveThreshold(
gris,
255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
11,
2
)
Este tipo de técnica es muy útil en documentos, escenas con sombras o imágenes donde la iluminación varía significativamente.
El suavizado reduce variaciones bruscas entre píxeles vecinos. Esto puede ayudar a disminuir ruido y preparar mejor la imagen para otras etapas.
Una forma simple es aplicar desenfoque promedio:
suavizada = cv2.blur(imagen, (5, 5))
Aquí el kernel de tamaño (5, 5) promedia los valores cercanos.
Otra técnica muy usada es el desenfoque gaussiano, que suaviza de forma más natural que el promedio simple:
gauss = cv2.GaussianBlur(imagen, (5, 5), 0)
Este filtro será especialmente importante cuando lleguemos a detección de bordes y reducción de ruido.
OpenCV también permite dibujar elementos gráficos directamente sobre una imagen. Esto es muy útil para anotaciones, visualización de resultados o debugging.
Por ejemplo, dibujar un rectángulo:
cv2.rectangle(imagen, (50, 50), (200, 200), (0, 255, 0), 2)
O escribir texto:
cv2.putText(imagen, "Objeto", (50, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
Estas herramientas son muy valiosas cuando queremos mostrar predicciones o resaltar regiones.
Otra operación básica muy frecuente es cambiar el espacio de color. Por ejemplo:
hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)
Este tipo de conversión es útil cuando ciertas tareas, como la segmentación por color, resultan más sencillas en otro espacio diferente de BGR.
OpenCV permite trabajar por separado sobre cada canal:
b, g, r = cv2.split(imagen)
Y luego volver a unirlos:
combinada = cv2.merge([b, g, r])
Esto puede ser útil para inspección, ajustes selectivos o análisis de componentes específicas.
Entre las operaciones elementales también están los volteos y algunas rotaciones simples. Por ejemplo:
horizontal = cv2.flip(imagen, 1)
vertical = cv2.flip(imagen, 0)
Estas transformaciones son sencillas pero útiles tanto para inspección visual como para aumentar datos en entrenamiento.
En muchos problemas reales, el procesamiento básico aparece como una secuencia de pasos concatenados. Por ejemplo:
Esto muestra que las operaciones básicas rara vez aparecen aisladas. Suelen integrarse en pipelines más amplios.
Veamos ahora una aplicación completa que reúne, en una sola interfaz, gran parte de las funcionalidades básicas estudiadas en este tema:
import os
import cv2
import numpy as np
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
EXTENSIONES_VALIDAS = (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff", ".webp")
class AplicacionOpenCV:
def __init__(self, root):
self.root = root
self.root.title("Procesamiento básico de imágenes con OpenCV")
self.root.geometry("1450x850")
self.imagen_original = None
self.imagen_actual = None
self.nombre_archivo_actual = None
self.crear_interfaz()
self.cargar_lista_imagenes()
def crear_interfaz(self):
contenedor = ttk.Frame(self.root, padding=10)
contenedor.pack(fill="both", expand=True)
panel_izquierdo = ttk.Frame(contenedor)
panel_izquierdo.pack(side="left", fill="y", padx=(0, 10))
ttk.Label(panel_izquierdo, text="Imágenes del directorio actual").pack(anchor="w")
self.lista_imagenes = tk.Listbox(panel_izquierdo, width=35, height=18)
self.lista_imagenes.pack(fill="y", pady=5)
self.lista_imagenes.bind("<<ListboxSelect>>", self.seleccionar_imagen)
ttk.Button(panel_izquierdo, text="Actualizar lista", command=self.cargar_lista_imagenes).pack(fill="x", pady=2)
ttk.Button(panel_izquierdo, text="Abrir otra imagen...", command=self.abrir_otra_imagen).pack(fill="x", pady=2)
ttk.Button(panel_izquierdo, text="Restablecer imagen", command=self.restablecer_imagen).pack(fill="x", pady=2)
ttk.Button(panel_izquierdo, text="Guardar resultado", command=self.guardar_resultado).pack(fill="x", pady=2)
ttk.Separator(panel_izquierdo, orient="horizontal").pack(fill="x", pady=8)
ttk.Label(panel_izquierdo, text="Parámetros").pack(anchor="w")
ttk.Label(panel_izquierdo, text="Brillo").pack(anchor="w")
self.scale_brillo = tk.Scale(panel_izquierdo, from_=-100, to=100, orient="horizontal", length=250)
self.scale_brillo.set(40)
self.scale_brillo.pack()
ttk.Label(panel_izquierdo, text="Contraste").pack(anchor="w")
self.scale_contraste = tk.Scale(panel_izquierdo, from_=0.1, to=3.0, resolution=0.1,
orient="horizontal", length=250)
self.scale_contraste.set(1.2)
self.scale_contraste.pack()
ttk.Label(panel_izquierdo, text="Umbral").pack(anchor="w")
self.scale_umbral = tk.Scale(panel_izquierdo, from_=0, to=255, orient="horizontal", length=250)
self.scale_umbral.set(127)
self.scale_umbral.pack()
ttk.Label(panel_izquierdo, text="Alpha (contraste convertScaleAbs)").pack(anchor="w")
self.scale_alpha = tk.Scale(panel_izquierdo, from_=0.1, to=3.0, resolution=0.1,
orient="horizontal", length=250)
self.scale_alpha.set(1.2)
self.scale_alpha.pack()
ttk.Label(panel_izquierdo, text="Beta (brillo convertScaleAbs)").pack(anchor="w")
self.scale_beta = tk.Scale(panel_izquierdo, from_=-100, to=100, orient="horizontal", length=250)
self.scale_beta.set(30)
self.scale_beta.pack()
ttk.Label(panel_izquierdo, text="Nuevo ancho").pack(anchor="w")
self.entry_ancho = ttk.Entry(panel_izquierdo)
self.entry_ancho.insert(0, "400")
self.entry_ancho.pack(fill="x", pady=2)
ttk.Label(panel_izquierdo, text="Nuevo alto").pack(anchor="w")
self.entry_alto = ttk.Entry(panel_izquierdo)
self.entry_alto.insert(0, "300")
self.entry_alto.pack(fill="x", pady=2)
ttk.Label(panel_izquierdo, text="Texto para dibujar").pack(anchor="w")
self.entry_texto = ttk.Entry(panel_izquierdo)
self.entry_texto.insert(0, "Objeto")
self.entry_texto.pack(fill="x", pady=2)
ttk.Separator(panel_izquierdo, orient="horizontal").pack(fill="x", pady=8)
panel_centro = ttk.Frame(contenedor)
panel_centro.pack(side="left", fill="y", padx=(0, 10))
ttk.Label(panel_centro, text="Herramientas OpenCV").pack(anchor="w")
self.frame_botones = ttk.Frame(panel_centro)
self.frame_botones.pack(fill="y", pady=5)
botones = [
("Escala de grises", self.a_grises),
("Redimensionar", self.redimensionar),
("Recorte central", self.recorte_central),
("Ajustar brillo", self.ajustar_brillo),
("Ajustar contraste", self.ajustar_contraste),
("Brillo + contraste", self.ajustar_convert_scale_abs),
("Invertir", self.invertir),
("Threshold binario", self.threshold_binario),
("THRESH_BINARY_INV", self.threshold_binary_inv),
("THRESH_TRUNC", self.threshold_trunc),
("THRESH_TOZERO", self.threshold_tozero),
("THRESH_TOZERO_INV", self.threshold_tozero_inv),
("Threshold adaptativo", self.threshold_adaptativo),
("Blur promedio", self.blur_promedio),
("Gaussian Blur", self.gaussian_blur),
("Dibujar rectángulo", self.dibujar_rectangulo),
("Dibujar texto", self.dibujar_texto),
("Convertir a HSV", self.convertir_hsv),
("Canal azul", self.mostrar_canal_azul),
("Canal verde", self.mostrar_canal_verde),
("Canal rojo", self.mostrar_canal_rojo),
("Reconstruir BGR", self.reconstruir_bgr),
("Flip horizontal", self.flip_horizontal),
("Flip vertical", self.flip_vertical),
("Rotar 90", self.rotar_90),
("Ejemplo integrado", self.ejemplo_integrado),
]
fila = 0
col = 0
for texto, comando in botones:
btn = ttk.Button(self.frame_botones, text=texto, command=comando, width=22)
btn.grid(row=fila, column=col, padx=3, pady=3, sticky="ew")
fila += 1
if fila == 13:
fila = 0
col += 1
panel_derecho = ttk.Frame(contenedor)
panel_derecho.pack(side="left", fill="both", expand=True)
ttk.Label(panel_derecho, text="Imagen original").pack(anchor="center", pady=(0, 5))
self.label_original = ttk.Label(panel_derecho, relief="solid", anchor="center")
self.label_original.pack(fill="both", expand=True, padx=5, pady=(0, 10))
ttk.Label(panel_derecho, text="Imagen procesada").pack(anchor="center", pady=(0, 5))
self.label_procesada = ttk.Label(panel_derecho, relief="solid", anchor="center")
self.label_procesada.pack(fill="both", expand=True, padx=5, pady=(0, 5))
self.label_info = ttk.Label(panel_derecho, text="Seleccione una imagen para comenzar.", foreground="blue")
self.label_info.pack(anchor="w", pady=5)
def cargar_lista_imagenes(self):
self.lista_imagenes.delete(0, tk.END)
archivos = sorted([
archivo for archivo in os.listdir(".")
if os.path.isfile(archivo) and archivo.lower().endswith(EXTENSIONES_VALIDAS)
])
for archivo in archivos:
self.lista_imagenes.insert(tk.END, archivo)
def seleccionar_imagen(self, event=None):
seleccion = self.lista_imagenes.curselection()
if not seleccion:
return
nombre = self.lista_imagenes.get(seleccion[0])
imagen = cv2.imread(nombre)
if imagen is None:
messagebox.showerror("Error", f"No se pudo cargar la imagen:\\n{nombre}")
return
self.nombre_archivo_actual = nombre
self.imagen_original = imagen.copy()
self.imagen_actual = imagen.copy()
self.mostrar_imagen(self.imagen_original, self.label_original)
self.mostrar_imagen(self.imagen_actual, self.label_procesada)
self.actualizar_info()
def abrir_otra_imagen(self):
ruta = filedialog.askopenfilename(
title="Seleccionar imagen",
filetypes=[
("Imágenes", "*.jpg *.jpeg *.png *.bmp *.tif *.tiff *.webp"),
("Todos los archivos", "*.*")
]
)
if not ruta:
return
imagen = cv2.imread(ruta)
if imagen is None:
messagebox.showerror("Error", "No se pudo cargar la imagen seleccionada.")
return
self.nombre_archivo_actual = os.path.basename(ruta)
self.imagen_original = imagen.copy()
self.imagen_actual = imagen.copy()
self.mostrar_imagen(self.imagen_original, self.label_original)
self.mostrar_imagen(self.imagen_actual, self.label_procesada)
self.actualizar_info()
def mostrar_imagen(self, imagen_cv, label):
if imagen_cv is None:
return
imagen_mostrar = self.convertir_para_tk(imagen_cv)
max_ancho = 500
max_alto = 700
alto, ancho = imagen_mostrar.shape[:2]
escala = min(max_ancho / ancho, max_alto / alto, 1.0)
nuevo_ancho = int(ancho * escala)
nuevo_alto = int(alto * escala)
imagen_redim = cv2.resize(imagen_mostrar, (nuevo_ancho, nuevo_alto))
imagen_pil = Image.fromarray(imagen_redim)
imagen_tk = ImageTk.PhotoImage(imagen_pil)
label.configure(image=imagen_tk)
label.image = imagen_tk
def convertir_para_tk(self, imagen_cv):
if len(imagen_cv.shape) == 2:
return imagen_cv
return cv2.cvtColor(imagen_cv, cv2.COLOR_BGR2RGB)
def actualizar_vista_procesada(self):
if self.imagen_actual is not None:
self.mostrar_imagen(self.imagen_actual, self.label_procesada)
self.actualizar_info()
def actualizar_info(self):
if self.imagen_actual is None:
self.label_info.config(text="No hay imagen cargada.")
return
forma = self.imagen_actual.shape
if len(forma) == 2:
texto = f"Archivo: {self.nombre_archivo_actual} | Tamaño: {forma[1]} x {forma[0]} | 1 canal (grises)"
else:
texto = f"Archivo: {self.nombre_archivo_actual} | Tamaño: {forma[1]} x {forma[0]} | {forma[2]} canales"
self.label_info.config(text=texto)
def verificar_imagen(self):
if self.imagen_actual is None:
messagebox.showwarning("Aviso", "Primero debe seleccionar una imagen.")
return False
return True
def obtener_gris(self):
if len(self.imagen_actual.shape) == 2:
return self.imagen_actual.copy()
return cv2.cvtColor(self.imagen_actual, cv2.COLOR_BGR2GRAY)
def obtener_bgr(self):
if len(self.imagen_actual.shape) == 2:
return cv2.cvtColor(self.imagen_actual, cv2.COLOR_GRAY2BGR)
return self.imagen_actual.copy()
def restablecer_imagen(self):
if self.imagen_original is None:
return
self.imagen_actual = self.imagen_original.copy()
self.actualizar_vista_procesada()
def guardar_resultado(self):
if not self.verificar_imagen():
return
ruta = filedialog.asksaveasfilename(
title="Guardar imagen procesada",
defaultextension=".png",
filetypes=[
("PNG", "*.png"),
("JPG", "*.jpg"),
("BMP", "*.bmp"),
("Todos los archivos", "*.*")
]
)
if not ruta:
return
ok = cv2.imwrite(ruta, self.imagen_actual)
if ok:
messagebox.showinfo("Guardar", "Imagen guardada correctamente.")
else:
messagebox.showerror("Error", "No se pudo guardar la imagen.")
def a_grises(self):
if not self.verificar_imagen():
return
self.imagen_actual = self.obtener_gris()
self.actualizar_vista_procesada()
def redimensionar(self):
if not self.verificar_imagen():
return
try:
ancho = int(self.entry_ancho.get())
alto = int(self.entry_alto.get())
if ancho <= 0 or alto <= 0:
raise ValueError
self.imagen_actual = cv2.resize(self.imagen_actual, (ancho, alto))
self.actualizar_vista_procesada()
except ValueError:
messagebox.showerror("Error", "Ingrese ancho y alto válidos.")
def recorte_central(self):
if not self.verificar_imagen():
return
h, w = self.imagen_actual.shape[:2]
x1 = w // 4
x2 = 3 * w // 4
y1 = h // 4
y2 = 3 * h // 4
self.imagen_actual = self.imagen_actual[y1:y2, x1:x2]
self.actualizar_vista_procesada()
def ajustar_brillo(self):
if not self.verificar_imagen():
return
valor = self.scale_brillo.get()
img = self.imagen_actual.astype(np.int16) + valor
self.imagen_actual = np.clip(img, 0, 255).astype(np.uint8)
self.actualizar_vista_procesada()
def ajustar_contraste(self):
if not self.verificar_imagen():
return
factor = float(self.scale_contraste.get())
img = self.imagen_actual.astype(np.float32) * factor
self.imagen_actual = np.clip(img, 0, 255).astype(np.uint8)
self.actualizar_vista_procesada()
def ajustar_convert_scale_abs(self):
if not self.verificar_imagen():
return
alpha = float(self.scale_alpha.get())
beta = int(self.scale_beta.get())
self.imagen_actual = cv2.convertScaleAbs(self.imagen_actual, alpha=alpha, beta=beta)
self.actualizar_vista_procesada()
def invertir(self):
if not self.verificar_imagen():
return
self.imagen_actual = 255 - self.imagen_actual
self.actualizar_vista_procesada()
def threshold_binario(self):
if not self.verificar_imagen():
return
gris = self.obtener_gris()
umbral = self.scale_umbral.get()
_, self.imagen_actual = cv2.threshold(gris, umbral, 255, cv2.THRESH_BINARY)
self.actualizar_vista_procesada()
def threshold_binary_inv(self):
if not self.verificar_imagen():
return
gris = self.obtener_gris()
umbral = self.scale_umbral.get()
_, self.imagen_actual = cv2.threshold(gris, umbral, 255, cv2.THRESH_BINARY_INV)
self.actualizar_vista_procesada()
def threshold_trunc(self):
if not self.verificar_imagen():
return
gris = self.obtener_gris()
umbral = self.scale_umbral.get()
_, self.imagen_actual = cv2.threshold(gris, umbral, 255, cv2.THRESH_TRUNC)
self.actualizar_vista_procesada()
def threshold_tozero(self):
if not self.verificar_imagen():
return
gris = self.obtener_gris()
umbral = self.scale_umbral.get()
_, self.imagen_actual = cv2.threshold(gris, umbral, 255, cv2.THRESH_TOZERO)
self.actualizar_vista_procesada()
def threshold_tozero_inv(self):
if not self.verificar_imagen():
return
gris = self.obtener_gris()
umbral = self.scale_umbral.get()
_, self.imagen_actual = cv2.threshold(gris, umbral, 255, cv2.THRESH_TOZERO_INV)
self.actualizar_vista_procesada()
def threshold_adaptativo(self):
if not self.verificar_imagen():
return
gris = self.obtener_gris()
self.imagen_actual = cv2.adaptiveThreshold(
gris,
255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
11,
2
)
self.actualizar_vista_procesada()
def blur_promedio(self):
if not self.verificar_imagen():
return
self.imagen_actual = cv2.blur(self.imagen_actual, (5, 5))
self.actualizar_vista_procesada()
def gaussian_blur(self):
if not self.verificar_imagen():
return
self.imagen_actual = cv2.GaussianBlur(self.imagen_actual, (5, 5), 0)
self.actualizar_vista_procesada()
def dibujar_rectangulo(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
h, w = img.shape[:2]
cv2.rectangle(img, (w // 6, h // 6), (5 * w // 6, 5 * h // 6), (0, 255, 0), 2)
self.imagen_actual = img
self.actualizar_vista_procesada()
def dibujar_texto(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
texto = self.entry_texto.get().strip()
if not texto:
texto = "Objeto"
cv2.putText(img, texto, (30, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
self.imagen_actual = img
self.actualizar_vista_procesada()
def convertir_hsv(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
self.imagen_actual = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
self.actualizar_vista_procesada()
def mostrar_canal_azul(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
b, g, r = cv2.split(img)
self.imagen_actual = b
self.actualizar_vista_procesada()
def mostrar_canal_verde(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
b, g, r = cv2.split(img)
self.imagen_actual = g
self.actualizar_vista_procesada()
def mostrar_canal_rojo(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
b, g, r = cv2.split(img)
self.imagen_actual = r
self.actualizar_vista_procesada()
def reconstruir_bgr(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
b, g, r = cv2.split(img)
self.imagen_actual = cv2.merge([b, g, r])
self.actualizar_vista_procesada()
def flip_horizontal(self):
if not self.verificar_imagen():
return
self.imagen_actual = cv2.flip(self.imagen_actual, 1)
self.actualizar_vista_procesada()
def flip_vertical(self):
if not self.verificar_imagen():
return
self.imagen_actual = cv2.flip(self.imagen_actual, 0)
self.actualizar_vista_procesada()
def rotar_90(self):
if not self.verificar_imagen():
return
self.imagen_actual = cv2.rotate(self.imagen_actual, cv2.ROTATE_90_CLOCKWISE)
self.actualizar_vista_procesada()
def ejemplo_integrado(self):
if not self.verificar_imagen():
return
img = self.obtener_bgr()
img = cv2.resize(img, (400, 300))
gris = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gauss = cv2.GaussianBlur(gris, (5, 5), 0)
_, binaria = cv2.threshold(gauss, 120, 255, cv2.THRESH_BINARY)
self.imagen_actual = binaria
self.actualizar_vista_procesada()
if __name__ == "__main__":
root = tk.Tk()
app = AplicacionOpenCV(root)
root.mainloop()
Esta aplicación funciona como un pequeño laboratorio interactivo: permite cargar imágenes, aplicar transformaciones, ajustar parámetros y visualizar inmediatamente el resultado. Es un buen ejemplo de cómo muchas operaciones básicas de OpenCV pueden integrarse en una herramienta práctica y reutilizable.
Al empezar con procesamiento básico de imágenes suelen aparecer errores frecuentes:
Estos errores son normales al principio, pero conviene detectarlos pronto porque afectan mucho la calidad del pipeline.
El procesamiento básico de imágenes con OpenCV constituye la base práctica del trabajo clásico en visión por computadora. Aunque estas operaciones son relativamente simples, tienen un valor enorme porque preparan la imagen, simplifican la información y hacen posible etapas posteriores de análisis.
Dominar estas herramientas permite construir una intuición muy útil sobre cómo responden las imágenes a distintas transformaciones y qué tipo de preprocesamiento conviene aplicar en cada caso.
En el próximo tema profundizaremos en las operaciones sobre píxeles y matrices, donde veremos con más detalle la manipulación numérica directa de la imagen.