0%
OpenCV con Python: introducción al procesamiento de imágenes

OpenCV con Python: introducción al procesamiento de imágenes

Empieza con el procesamiento de imágenes con OpenCV y Python: espacios de color, filtros, contornos, umbralización, detección de rostros y lectura de flujos de webcam.

I

InSkillCoach

· min

OpenCV con Python: introducción al procesamiento de imágenes

OpenCV (Open Source Computer Vision Library) es la biblioteca de referencia en visión por computador. Con su interfaz para Python, permite leer, transformar y analizar imágenes y vídeos en pocas líneas: filtrado, detección de contornos, reconocimiento de rostros, procesamiento de flujos de webcam en tiempo real. Esta guía cubre las operaciones fundamentales que utiliza cualquier proyecto de procesamiento de imágenes, con ejemplos completos y ejecutables.

Instalación

OpenCV se instala mediante el paquete opencv-python, que incluye los módulos principales:

pip install opencv-python

# Si necesitas los módulos adicionales (contrib)
pip install opencv-contrib-python

Verificación:

import cv2

print(cv2.__version__)
# 4.10.0 (o una versión más reciente)

Ten en cuenta que el módulo se importa con el nombre cv2, sea cual sea el nombre del paquete instalado. OpenCV representa las imágenes como matrices de NumPy, que se instala automáticamente como dependencia.

Leer, mostrar y guardar una imagen

import cv2

# Lectura de una imagen desde el disco
image = cv2.imread("photo.jpg")

# imread NO lanza una excepción si no encuentra el archivo: devuelve None
if image is None:
    raise FileNotFoundError("Impossible de lire photo.jpg")

# Una imagen es una matriz de NumPy: (alto, ancho, canales)
print(image.shape)
# (720, 1280, 3)
print(image.dtype)
# uint8 (valores de 0 a 255)

# Visualización en una ventana
cv2.imshow("Ma photo", image)
cv2.waitKey(0)            # espera una tecla (0 = indefinidamente)
cv2.destroyAllWindows()   # cierra todas las ventanas

# Guardado (el formato se deduce de la extensión)
cv2.imwrite("photo_copie.png", image)

La comprobación if image is None es imprescindible: olvidarla es la primera causa de errores crípticos entre los principiantes.

Espacios de color: la trampa del BGR

Cuidado con la trampa clásica: OpenCV carga las imágenes en BGR (azul, verde, rojo) y no en RGB. Si muestras una imagen de OpenCV con Matplotlib sin convertirla, los rojos y los azules aparecen invertidos.

import cv2

image = cv2.imread("photo.jpg")

# Conversión de BGR a RGB (necesaria para Matplotlib, Pillow, etc.)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# Conversión a escala de grises (un solo canal)
gris = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
print(gris.shape)
# (720, 1280) : ya no hay dimensión de color

# Conversión a HSV (tono, saturación, valor)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

El espacio HSV resulta muy útil para aislar un color, ya que el tono es independiente de la luminosidad:

import numpy as np

# Aislar las zonas azules de la imagen
borne_basse = np.array([100, 100, 50])    # tono, saturación, valor mínimos
borne_haute = np.array([130, 255, 255])
masque = cv2.inRange(hsv, borne_basse, borne_haute)

# Aplicar la máscara: solo las zonas azules permanecen visibles
resultat = cv2.bitwise_and(image, image, mask=masque)

Redimensionar y recortar

import cv2

image = cv2.imread("photo.jpg")
hauteur, largeur = image.shape[:2]

# Redimensionado a unas dimensiones concretas
petite = cv2.resize(image, (640, 360))

# Redimensionado por factor (aquí: la mitad) conservando las proporciones
moitie = cv2.resize(image, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)

# Recorte: una imagen es una matriz de NumPy, así que se corta por rebanadas
# Sintaxis: image[y1:y2, x1:x2]
recadree = image[100:400, 200:600]

cv2.imwrite("recadree.jpg", recadree)

Para reducir una imagen, la interpolación cv2.INTER_AREA da el mejor resultado; para ampliarla, es preferible cv2.INTER_CUBIC o cv2.INTER_LINEAR.

Dibujar sobre una imagen

Las funciones de dibujo modifican la imagen in situ. Los colores están en BGR:

import cv2

image = cv2.imread("photo.jpg")

# Rectángulo: esquinas opuestas, color BGR, grosor (-1 = relleno)
cv2.rectangle(image, (50, 50), (250, 200), (0, 255, 0), 2)

# Círculo: centro, radio
cv2.circle(image, (400, 300), 60, (255, 0, 0), 3)

# Línea
cv2.line(image, (0, 0), (640, 360), (0, 0, 255), 2)

# Texto
cv2.putText(image, "Detection", (50, 40),
            cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2)

cv2.imwrite("annotee.jpg", image)

Desenfoques y filtros

El desenfoque gaussiano es el componente básico del preprocesamiento: atenúa el ruido antes de una detección de contornos o una umbralización.

import cv2

image = cv2.imread("photo.jpg")

# Desenfoque gaussiano: el kernel (15, 15) debe tener dimensiones impares
flou = cv2.GaussianBlur(image, (15, 15), 0)

# Desenfoque mediano: muy eficaz contra el ruido "sal y pimienta"
median = cv2.medianBlur(image, 5)

# Filtro bilateral: suaviza las zonas planas preservando los contornos
bilateral = cv2.bilateralFilter(image, 9, 75, 75)

Cuanto mayor es el kernel, más pronunciado es el desenfoque. Un kernel (5, 5) suele bastar como preprocesamiento.

Detección de contornos: Canny y findContours

El algoritmo de Canny detecta los contornos (las zonas de fuerte cambio de intensidad):

import cv2

image = cv2.imread("photo.jpg")
gris = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Reducir el ruido antes de la detección
gris_flou = cv2.GaussianBlur(gris, (5, 5), 0)

# Canny: los dos umbrales controlan la sensibilidad
# Por debajo de 50: descartado; por encima de 150: contorno seguro
contours_canny = cv2.Canny(gris_flou, 50, 150)
cv2.imwrite("canny.jpg", contours_canny)

findContours va más allá: extrae los contornos en forma de listas de puntos, aprovechables para medir, contar o enmarcar objetos:

# findContours espera una imagen binaria (resultado de Canny o de una umbralización)
contours, _ = cv2.findContours(contours_canny,
                               cv2.RETR_EXTERNAL,      # solo contornos externos
                               cv2.CHAIN_APPROX_SIMPLE)  # compresión de los puntos

print(f"Nombre de contours trouvés : {len(contours)}")

# Enmarcar cada contorno lo bastante grande
for contour in contours:
    aire = cv2.contourArea(contour)
    if aire > 500:  # ignorar el ruido
        x, y, w, h = cv2.boundingRect(contour)
        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2.imwrite("objets_detectes.jpg", image)

Umbralización

La umbralización transforma una imagen en escala de grises en una imagen binaria (blanco y negro), paso previo a numerosos análisis:

import cv2

gris = cv2.imread("document.jpg", cv2.IMREAD_GRAYSCALE)

# Umbralización simple: todo píxel > 127 pasa a 255; si no, a 0
_, binaire = cv2.threshold(gris, 127, 255, cv2.THRESH_BINARY)

# Umbralización de Otsu: el umbral óptimo se calcula automáticamente
_, otsu = cv2.threshold(gris, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# Umbralización adaptativa: umbral calculado localmente, ideal para un
# documento fotografiado con iluminación desigual
adaptatif = cv2.adaptiveThreshold(gris, 255,
                                  cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                  cv2.THRESH_BINARY, 11, 2)

Detección de rostros con cascadas de Haar

OpenCV incorpora clasificadores preentrenados para detectar rostros, ojos o sonrisas. Este es un detector de rostros completo:

import cv2

# Carga del clasificador preentrenado que se incluye con OpenCV
cascade_visages = cv2.CascadeClassifier(
    cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)

image = cv2.imread("groupe.jpg")
if image is None:
    raise FileNotFoundError("Impossible de lire groupe.jpg")

# La detección trabaja sobre una imagen en escala de grises
gris = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# detectMultiScale devuelve un rectángulo (x, y, ancho, alto) por rostro
visages = cascade_visages.detectMultiScale(
    gris,
    scaleFactor=1.1,    # reducción de la imagen en cada pasada (10 %)
    minNeighbors=5,     # más alto = menos falsos positivos
    minSize=(40, 40),   # tamaño mínimo de un rostro en píxeles
)

print(f"{len(visages)} visage(s) détecté(s)")
# 3 visage(s) détecté(s)

# Enmarcar cada rostro
for (x, y, w, h) in visages:
    cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2.imwrite("visages_detectes.jpg", image)
cv2.imshow("Visages", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Ajusta minNeighbors si la detección produce demasiados falsos positivos (auméntalo) o se deja rostros sin detectar (disminúyelo). Las cascadas de Haar son rápidas, pero sensibles a la orientación: detectan sobre todo rostros de frente.

Leer un flujo de vídeo o la webcam

Un vídeo no es más que una sucesión de imágenes: se leen los fotogramas uno a uno en un bucle. El siguiente ejemplo detecta rostros en directo con la webcam:

import cv2

cascade_visages = cv2.CascadeClassifier(
    cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)

# 0 = primera webcam; sustitúyelo por "video.mp4" para un archivo
capture = cv2.VideoCapture(0)
if not capture.isOpened():
    raise RuntimeError("Impossible d'ouvrir la webcam")

while True:
    # ret vale False si la lectura falla (fin de archivo, webcam desconectada)
    ret, frame = capture.read()
    if not ret:
        break

    gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    visages = cascade_visages.detectMultiScale(gris, 1.1, 5, minSize=(40, 40))

    for (x, y, w, h) in visages:
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

    cv2.imshow("Webcam - appuyez sur Q pour quitter", frame)

    # waitKey(1): esperar 1 ms; salir si se pulsa la tecla Q
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Liberar la webcam y cerrar las ventanas
capture.release()
cv2.destroyAllWindows()

Para ir más allá: el módulo DNN

Las cascadas de Haar datan de 2001. Para obtener detecciones más precisas y robustas, OpenCV integra un módulo de redes neuronales profundas (cv2.dnn) capaz de cargar modelos preentrenados en formatos ONNX, TensorFlow o Caffe:

import cv2

# Ejemplo: cargar un modelo de detección en formato ONNX
# net = cv2.dnn.readNetFromONNX("modele_detection.onnx")

Los siguientes pasos naturales tras esta guía son: la detección de rostros mediante DNN (modelo YuNet integrado en OpenCV a través de cv2.FaceDetectorYN), la detección de objetos con YOLO y la estimación de pose. El principio sigue siendo el mismo que en esta guía: leer la imagen, preprocesarla, analizarla y dibujar el resultado.

Buenas prácticas

  • Comprueba siempre el valor devuelto por imread y por capture.read(): OpenCV devuelve None o False en lugar de lanzar una excepción.
  • No olvides nunca la conversión BGR/RGB cuando intercambies imágenes con Matplotlib, Pillow o un modelo de deep learning.
  • Preprocesa antes de analizar: pasar a escala de grises y aplicar después un desenfoque gaussiano mejora casi siempre Canny y la umbralización.
  • Libera los recursos: capture.release() y cv2.destroyAllWindows() al final del procesamiento de vídeo.
  • Trabaja sobre copias (image.copy()) cuando dibujes, para conservar intacta la imagen original.
  • Filtra por área (cv2.contourArea) tras findContours para eliminar el ruido.

Conclusión

Ya dispones de los fundamentos del procesamiento de imágenes con OpenCV: carga y guardado, espacios de color (y la trampa del BGR), redimensionado, filtros, detección de contornos con Canny y findContours, umbralización, detección de rostros mediante cascadas de Haar y procesamiento de un flujo de webcam en tiempo real. Estas operaciones cubren la gran mayoría de las necesidades de un primer proyecto de visión por computador.

Para profundizar, consulta la documentación oficial de OpenCV y sus tutoriales de Python, y explora después el módulo DNN para pasar a las detecciones mediante aprendizaje profundo.

InSkillCoach

Acerca de InSkillCoach

Experto en formación y tecnologías

Coach especializado en tecnologías avanzadas e IA, impulsado por GNeurone Inc.

Certificaciones:

  • AWS Certified Solutions Architect – Professional
  • Certificaciones de Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
779
73

Comentarios

Los comentarios funcionan con GitHub Discussions

Inicia sesión con GitHub para participar en la discusión

¡Enlace copiado!