Imágenes como arrays NumPy
En OpenCV, toda imagen es un array NumPy. Cuando cargas una imagen con cv2.imread(), obtienes un objeto numpy.ndarray que almacena los valores de los píxeles como números enteros.
import cv2
import numpy as np
# Cargar una imagen
imagen = cv2.imread("foto.jpg")
# Verificar que es un array NumPy
print(type(imagen)) # <class 'numpy.ndarray'>
print(imagen.shape) # (alto, ancho, canales) p. ej. (480, 640, 3)
print(imagen.dtype) # uint8
print(imagen.size) # número total de elementos
print(imagen.ndim) # 3 para imágenes color, 2 para escala de grises
Esta representación como array NumPy es fundamental: permite usar todas las operaciones vectorizadas y funciones matemáticas de NumPy directamente sobre las imágenes.
El sistema de color BGR
A diferencia de la convención habitual RGB (Rojo-Verde-Azul), OpenCV utiliza el orden BGR (Azul-Verde-Rojo). Este es un detalle histórico heredado de las primeras versiones de la biblioteca, pero tiene implicaciones prácticas importantes.
import cv2
import numpy as np
# Crear una imagen de prueba con un píxel de color rojo puro
# En RGB: (255, 0, 0) → en BGR: (0, 0, 255)
imagen = np.zeros((200, 200, 3), dtype=np.uint8)
imagen[:] = (0, 0, 255) # Rojo en BGR
cv2.imwrite("rojo_bgr.png", imagen)
# Acceder a los canales
b = imagen[:, :, 0] # Canal Azul (Blue)
g = imagen[:, :, 1] # Canal Verde (Green)
r = imagen[:, :, 2] # Canal Rojo (Red)
print(f"Valor B en (100,100): {b[100, 100]}") # 0
print(f"Valor G en (100,100): {g[100, 100]}") # 0
print(f"Valor R en (100,100): {r[100, 100]}") # 255
Dimensiones de la imagen: shape
El atributo shape de un array NumPy devuelve una tupla con las dimensiones:
- Imágenes en color:
(alto, ancho, 3)— tres canales BGR - Imágenes en escala de grises:
(alto, ancho)— sin canal de color - Imágenes con canal alfa:
(alto, ancho, 4)— cuatro canales BGRA
import cv2
# Imagen en color
img_color = cv2.imread("foto.jpg")
alto, ancho, canales = img_color.shape
print(f"Alto: {alto}px, Ancho: {ancho}px, Canales: {canales}")
# Imagen en escala de grises
img_gris = cv2.imread("foto.jpg", cv2.IMREAD_GRAYSCALE)
alto_g, ancho_g = img_gris.shape
print(f"Grises — Alto: {alto_g}px, Ancho: {ancho_g}px")
print(f"Dimensiones: {img_gris.ndim}") # 2
# Imagen con canal alfa (PNG con transparencia)
img_rgba = cv2.imread("icono.png", cv2.IMREAD_UNCHANGED)
if img_rgba is not None and img_rgba.shape[2] == 4:
print(f"Imagen con alfa: {img_rgba.shape}") # (alto, ancho, 4)
Estructura interna de una imagen como array
El siguiente diagrama muestra cómo se organiza una imagen en color dentro de un array NumPy tridimensional:
flowchart TD
A[Imagen BGR] --> B["ndarray (Height x Width x Channels)"]
B --> C[Eje 0: Height - filas]
B --> D[Eje 1: Width - columnas]
B --> E[Eje 2: Channels - 3 canales]
E --> F[Canal 0: Blue]
E --> G[Canal 1: Green]
E --> H[Canal 2: Red]
C --> I["Cada celda: valor uint8 (0-255)"]
F --> I
G --> I
H --> I
Cada píxel ocupa 3 bytes (uno por canal BGR) con valores entre 0 y 255. Una imagen en escala de grises tiene solo 2 dimensiones (Height x Width) y un único valor por píxel.
Tipo de datos (dtype)
El tipo de datos por defecto de las imágenes OpenCV es uint8 (entero sin signo de 8 bits), que almacena valores entre 0 y 255.
import cv2
import numpy as np
img = cv2.imread("foto.jpg")
print(f"Tipo de datos: {img.dtype}") # uint8
print(f"Valor mínimo: {img.min()}") # 0
print(f"Valor máximo: {img.max()}") # 255
# Convertir a float32 para operaciones que requieren precisión decimal
img_float = img.astype(np.float32)
print(f"Float32 — dtype: {img_float.dtype}") # float32
# Normalizar al rango [0.0, 1.0]
img_normalizada = img.astype(np.float32) / 255.0
print(f"Min: {img_normalizada.min():.2f}, Max: {img_normalizada.max():.2f}")
# Volver a uint8
img_recuperada = (img_normalizada * 255).astype(np.uint8)
Acceso y modificación de píxeles
Acceder a un píxel individual
import cv2
img = cv2.imread("foto.jpg")
# Leer píxel en fila=100, columna=200 (notación [fila, columna])
pixel = img[100, 200]
print(f"Píxel (100,200): B={pixel[0]}, G={pixel[1]}, R={pixel[2]}")
# En escala de grises, un solo valor
img_gris = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
valor = img_gris[100, 200]
print(f"Intensidad en escala de grises: {valor}")
Modificar un píxel
import cv2
import numpy as np
img = cv2.imread("foto.jpg")
# Modificar un píxel individual
img[100, 200] = [255, 0, 0] # Azul puro en BGR
# Verificar el cambio
print(img[100, 200]) # [255 0 0]
Regiones de interés (ROI) con slicing
El slicing de NumPy permite extraer y modificar regiones rectangulares de una imagen de forma muy eficiente.
import cv2
import numpy as np
img = cv2.imread("foto.jpg")
# Extraer una ROI (Region of Interest)
# Sintaxis: img[y_inicio:y_fin, x_inicio:x_fin]
roi = img[50:200, 100:300]
print(f"Tamaño de la ROI: {roi.shape}")
# Copiar la ROI a otra posición de la misma imagen
img[300:450, 100:300] = roi
# Rellenar una región con un color sólido
img[10:60, 10:60] = [0, 255, 0] # Cuadrado verde
cv2.imwrite("imagen_con_roi.png", img)
Separar y fusionar canales
import cv2
import numpy as np
img = cv2.imread("foto.jpg")
# Separar en canales individuales
b, g, r = cv2.split(img)
print(f"Canal B: shape={b.shape}, dtype={b.dtype}") # (alto, ancho), uint8
# Crear imagen solo con canal rojo activo
solo_rojo = np.zeros_like(img)
solo_rojo[:, :, 2] = r # Canal R en posición 2
# Fusionar canales
img_fusionada = cv2.merge([b, g, r])
print(f"Imagen fusionada: {img_fusionada.shape}")
# Acceso alternativo más eficiente que split (copia la vista del array)
canal_b = img[:, :, 0]
canal_g = img[:, :, 1]
canal_r = img[:, :, 2]
Crear imágenes desde cero
import cv2
import numpy as np
# Imagen negra (ceros) de 300x400 píxeles en color
negro = np.zeros((300, 400, 3), dtype=np.uint8)
# Imagen blanca
blanco = np.ones((300, 400, 3), dtype=np.uint8) * 255
# Imagen de color uniforme (azul en BGR)
azul = np.full((300, 400, 3), (255, 0, 0), dtype=np.uint8)
# Imagen de ruido aleatorio
ruido = np.random.randint(0, 256, (300, 400, 3), dtype=np.uint8)
# Gradiente horizontal en escala de grises
gradiente = np.tile(np.arange(0, 256, dtype=np.uint8), (300, 1))
gradiente_ancho = cv2.resize(gradiente, (400, 300))
cv2.imwrite("negro.png", negro)
cv2.imwrite("blanco.png", blanco)
cv2.imwrite("ruido.png", ruido)
Información completa de una imagen
import cv2
import numpy as np
def info_imagen(img, nombre="imagen"):
"""Muestra información completa sobre un array de imagen."""
if img is None:
print(f"{nombre}: None (error al cargar)")
return
print(f"\n--- {nombre} ---")
print(f" Tipo Python: {type(img)}")
print(f" Shape: {img.shape}")
print(f" dtype: {img.dtype}")
print(f" Num elementos: {img.size:,}")
print(f" Memoria (bytes):{img.nbytes:,}")
print(f" Valor mín: {img.min()}")
print(f" Valor máx: {img.max()}")
print(f" Valor medio: {img.mean():.2f}")
img_color = cv2.imread("foto.jpg")
img_gris = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
info_imagen(img_color, "Imagen color BGR")
info_imagen(img_gris, "Imagen escala de grises")
Comprender la representación interna de las imágenes como arrays NumPy es la base de todo lo que haremos con OpenCV. Cada operación que apliquemos (filtros, transformaciones, detección de bordes) actúa sobre esta estructura matricial.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, OpenCV es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de OpenCV
Explora más contenido relacionado con OpenCV y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Entender que las imágenes en OpenCV son arrays NumPy de tipo ndarray. Conocer el orden de dimensiones (alto, ancho, canales) y el sistema de color BGR. Acceder y modificar píxeles individuales y regiones de interés (ROI) con slicing. Trabajar con los tipos de datos uint8, float32 y float64 en imágenes. Separar y fusionar canales de color con cv2.split() y cv2.merge().