Imágenes

Procesamiento, manipulación y análisis de imágenes en R

r
image-processing
magick
ebimage
imager
opencv
computer-vision
microscopy
Referencia comentada de los paquetes que estructuran el flujo de trabajo con imágenes en R: edición general, análisis numérico, microscopía biomédica, visión por computador y preprocesado para deep learning.

Sobre imágenes en R

R no es el lenguaje natural para visión por computador moderna, ese terreno lo dominan Python con OpenCV, PIL/Pillow y los pipelines de PyTorch / TensorFlow. Aun así, hay un ecosistema sólido de paquetes que cubre con solvencia tres usos legítimos: edición y manipulación general (rótulos, recortes, conversiones por lotes), análisis numérico de imágenes como matrices o arrays (cuantificación, segmentación, morfología) y microscopía / imagen biomédica integrada con Bioconductor.

Antes de elegir paquete, conviene tener claras tres decisiones:

  • ¿Editar o analizar? Si la salida es una imagen para humanos (composición, anotación, conversión de formato), magick es casi siempre la opción correcta. Si la salida es una medida numérica (intensidad, área, recuento de objetos), EBImage o imager son más naturales.
  • ¿Rango de píxeles? Casi todo el ecosistema R analítico (EBImage, imager) trabaja con valores en [0, 1] en double. magick y la mayoría de formatos en disco usan [0, 255] en integer/raw. Confundir ambos rangos al pasar imágenes entre paquetes es la fuente número uno de bugs silenciosos (umbrales que “no hacen nada”, saturación inesperada, histogramas planos).
  • ¿Orden de canales? La convención mayoritaria en R es RGB(A). OpenCV (vía opencv) usa BGR por defecto, herencia de su origen en C++. Si encadenas paquetes, verifica el orden antes de aplicar máscaras o cálculos por canal.

Instalación canónica de los paquetes más usados:

install.packages(c("magick", "imager", "opencv"))      # CRAN

if (!require("BiocManager", quietly = TRUE))
  install.packages("BiocManager")
BiocManager::install("EBImage")                        # Bioconductor

Esta página cataloga cinco paquetes que cubren la mayoría de los flujos de imagen en R. El orden refleja jerarquía conceptual: primero la herramienta general más usada (magick), después los dos paquetes de análisis numérico (imager, EBImage), luego visión por computador (opencv) y finalmente el preprocesado para deep learning (torch / keras3).


magick

magick es el wrapper de R sobre ImageMagick, la librería C de referencia para edición de imágenes desde hace dos décadas. Es la opción por defecto para casi cualquier tarea de manipulación general: leer y escribir prácticamente cualquier formato (JPEG, PNG, TIFF, GIF, WebP, PDF, SVG, HEIC), redimensionar, recortar, rotar, anotar con texto, aplicar filtros, componer capas y generar animaciones.

Desarrollado y mantenido por Jeroen Ooms (rOpenSci). Es además el motor que ggplot2 y otros paquetes usan internamente para exportar a formatos no triviales.

Cuándo usarlo

  • Conversión y normalización de imágenes por lotes (cambiar formato, redimensionar, recomprimir).
  • Anotación: añadir texto, marcas de agua, leyendas o sellos sobre una imagen existente.
  • Composición de figuras: combinar paneles, apilar imágenes, generar GIFs animados o tiras de frames.
  • Lectura de formatos exóticos (PDF multipágina, SVG, HEIC) que el resto del ecosistema no soporta.

Cuándo NO usarlo

  • Análisis cuantitativo de píxeles. magick no expone la matriz de píxeles cómodamente. Cada operación devuelve un objeto magick-image opaco. Para umbralizar, segmentar, contar objetos o medir intensidad, usa EBImage (microscopía / biomedicina) o imager (visión general, basado en CImg).
  • Procesado en bucle muy intensivo. El coste por llamada (ida y vuelta a C) es notable. Para operar píxel a píxel sobre miles de imágenes, conviene cargarlas como array y usar imager o álgebra de matrices directa.
  • Preprocesado para redes neuronales. Usa los pipelines nativos del framework: torchvision::transform_* en torch o tf$image / capas keras3 de preprocesado. Son más rápidos y mantienen el grafo en GPU.

Conceptos clave

  • El objeto magick-image es inmutable y vectorizado: una “imagen” es en realidad un vector de frames. Las operaciones devuelven una nueva imagen, no modifican la anterior.
  • image_info() resume formato, dimensiones, profundidad de color y espacio de color (sRGB, Gray, CMYK).
  • Las operaciones se encadenan idiomáticamente con |> (o %>%): image_read() |> image_resize() |> image_annotate() |> image_write().
  • Las dimensiones se expresan como "WxH" (geometry strings de ImageMagick). Admiten sufijos como "500x500!" (forzar) o "50%" (relativo).
  • image_data() devuelve un array crudo en bruto (raw) cuando necesitas bajar a píxeles. Rara vez es lo que quieres si vas a hacer análisis serio.

Patrón mínimo

library(magick)

img <- image_read("foto.jpg")
image_info(img)

# Redimensionar manteniendo aspecto, anotar y guardar
img |>
  image_resize("1200x") |>
  image_annotate(
    "© Telómera 2026",
    gravity = "southeast",
    size    = 24,
    color   = "white",
    boxcolor = "#00000080",
    location = "+20+20"
  ) |>
  image_write("foto_anotada.png", format = "png")

# GIF a partir de un vector de frames
frames <- image_read(list.files("frames/", full.names = TRUE))
gif    <- image_animate(frames, fps = 10)
image_write(gif, "salida.gif")

Trampas habituales

  • Memoria con TIFF / PDF multipágina. Un PDF de 200 páginas leído con image_read("doc.pdf") carga las 200 páginas en RAM. Usa image_read_pdf(..., pages = 1:5) o procesa por bloques.
  • Geometry strings con ! y ^. "500x500" ajusta dentro del marco (mantiene aspecto). "500x500!" fuerza dimensiones (deforma). "500x500^" rellena el marco (recorta). Confundirlos produce imágenes deformadas o cortadas sin error visible.
  • image_write() no respeta la extensión si no le pasas format explícitamente: guardará en el formato del objeto, no en el de la ruta. Especifica siempre format = "png" (o equivalente) cuando importe.
  • Conversión a array. as.integer(image_data(img)) devuelve dimensiones c(channels, width, height), no c(height, width, channels) como espera imager o EBImage. Transponer correctamente es obligatorio antes de pasar la imagen a otro paquete.

Enlaces

Relacionados en esta página

  • imager, análisis numérico general (CImg backend).
  • EBImage, análisis cuantitativo orientado a biomedicina.

imager

imager es un wrapper de R sobre CImg, la librería C++ de procesamiento de imágenes desarrollada por David Tschumperlé. Su objetivo es complementario al de magick: no se centra en editar para humanos, sino en tratar la imagen como un array numérico de cuatro dimensiones (x × y × z × channel, donde z es profundidad para volúmenes 3D) y exponer la maquinaria clásica de procesado de señal: convoluciones, derivadas, morfología, gradientes, FFT, transformadas en frecuencia.

Desarrollado por Simon Barthelmé. Encaja en el nicho de visión general y procesado de señal cuando no se necesita el ecosistema biomédico de Bioconductor.

Cuándo usarlo

  • Filtrado y convolución arbitraria (isoblur, imgradient, convolve, kernels manuales).
  • Detección de bordes y características clásicas (Canny vía cannyEdges, Hessian, Harris).
  • Operaciones morfológicas básicas (dilate, erode, mclosing).
  • Procesado de vídeo o volúmenes 3D, el eje z está integrado en la clase.
  • Cuando ya trabajas con el array de píxeles como double en [0, 1] y necesitas operaciones idiomáticas vectorizadas.

Cuándo NO usarlo

  • Microscopía / análisis cuantitativo de objetos biológicos. EBImage tiene primitivas específicas (bwlabel, computeFeatures, segmentación watershed orientada a células) que en imager hay que reconstruir manualmente.
  • Edición y composición. magick es mucho más cómodo para anotar, redimensionar por lote o cambiar formato.
  • Visión por computador moderna (detección de objetos, tracking, calibración de cámara). Usa opencv.

Conceptos clave

  • La clase es cimg, un array 4D x × y × z × c. Para imágenes 2D corrientes, z = 1.
  • Píxeles en double rango [0, 1]. as.cimg() desde una matriz integer 0-255 hay que escalarla a mano (/ 255).
  • API idiomática vectorizada: imsplit() separa por eje (típicamente por canal). imappend() une de nuevo. imsub() recorta con expresiones.
  • plot() sobre un cimg produce visualización directa con orientación correcta (origen arriba-izquierda).
  • Convierte a/de magick con magick2cimg() y cimg2magick().

Patrón mínimo

library(imager)

img <- load.image("foto.jpg")        # cimg, double [0,1], x × y × 1 × 3
dim(img)

# Gradiente, magnitud y bordes Canny
gx <- imgradient(img, "x")
gy <- imgradient(img, "y")
mag <- sqrt(gx^2 + gy^2)

edges <- cannyEdges(grayscale(img), sigma = 1.5)
plot(edges)

# Convolución manual: detector de bordes Sobel
sobel_x <- as.cimg(matrix(c(-1,0,1,-2,0,2,-1,0,1), 3, 3))
edge_x  <- convolve(grayscale(img), sobel_x)

Trampas habituales

  • Orden de ejes. cimg es x × y × z × c, no height × width × channels. La intuición habitual de “filas son y, columnas son x” se invierte al indexar arrays nativos. Usa los helpers (imsub, imsplit) y evita indexación posicional manual.
  • Rango [0, 1] vs [0, 255]. Si lees con magick y conviertes con magick2cimg(), el escalado se hace solo. Si construyes un cimg desde una matriz cruda con as.cimg(), escala dividiendo por 255 antes, si no, las operaciones de filtrado producen saturaciones absurdas.
  • grayscale() vs canal único. grayscale() devuelve un cimg con c = 1 (luminancia). Indexar el canal R con R(img) también devuelve c = 1 pero conceptualmente no es lo mismo. Muchos errores en pipelines vienen de mezclar ambos.

Enlaces

Relacionados en esta página

  • magick, edición y composición general. Conversión bidireccional vía magick2cimg.
  • EBImage, análogo orientado a biomedicina con primitivas de segmentación celular.

EBImage

EBImage es el paquete de Bioconductor para procesamiento y análisis cuantitativo de imágenes, con fuerte orientación a microscopía y high-content screening. Cubre el ciclo completo: lectura de formatos científicos (TIFF multicanal, multistack), filtrado, segmentación (umbralización, watershed), etiquetado de objetos conectados y extracción de descriptores morfológicos y de intensidad por objeto.

Desarrollado en el grupo de Wolfgang Huber (EMBL). Maduro, bien integrado con el resto de Bioconductor (los descriptores se exportan como data.frame listos para enlazar con SummarizedExperiment u otros pipelines ómicos).

Cuándo usarlo

  • Microscopía celular: contar células, medir área, intensidad media, formas, ratios.
  • Imagen biomédica multicanal (fluorescencia con varios fluoróforos, IHC).
  • Cualquier flujo en el que la salida sea una tabla de objetos con descriptores cuantitativos y no una imagen anotada.
  • Integración con flujos Bioconductor downstream (linkar a metadatos de muestra, modelar con limma o DESeq2, etc.).

Cuándo NO usarlo

  • Edición general o composición para humanos: magick es más cómodo y rápido.
  • Visión por computador clásica (matching de features, homografías, calibración): opencv es muy superior.
  • Deep learning para segmentación (Cellpose, StarDist, Mesmer). EBImage hace segmentación clásica (umbralización + watershed). Si la morfología es difícil, los modelos preentrenados modernos rinden mejor, invocables desde R vía reticulate o como pipeline externo.

Conceptos clave

  • La clase es Image, un array con atributo colormode (Grayscale o Color). Los píxeles son double en [0, 1], igual que imager.
  • Imágenes multicanal: el último eje del array es el canal (Color). Imágenes multistack (z-stack o time-lapse) tienen un eje adicional con colormode = Grayscale.
  • Pipeline canónico de segmentación: gblur() (suavizar) → thresh() o otsu() (umbralizar) → bwlabel() (etiquetar componentes conectados) → watershed() (separar objetos pegados) → computeFeatures.*() (extraer descriptores).
  • display() abre visor interactivo. EBImage::display(img, method = "raster") lo embebe en R Markdown / Quarto.
  • computeFeatures.shape(), computeFeatures.basic() (intensidad), computeFeatures.moment() y computeFeatures.haralick() cubren las cuatro familias clásicas de descriptores.

Patrón mínimo

library(EBImage)

img <- readImage("nuclei.tif")           # multicanal o grises
nuc <- channel(img, "gray")              # o img[,,1] si es multicanal

# Suavizado + umbralización Otsu
smooth <- gblur(nuc, sigma = 2)
mask   <- smooth > otsu(smooth)

# Etiquetado y separación de objetos pegados con watershed
labels <- bwlabel(mask)
dist   <- distmap(mask)
seeds  <- watershed(dist, tolerance = 1, ext = 1)

# Descriptores por objeto
feats <- computeFeatures.shape(seeds)
head(feats)

Trampas habituales

  • Rango [0, 1]. readImage() escala automáticamente. writeImage() también. Pero si pasas un array desde magick o desde lectura cruda, divide por 255 antes, un umbral como img > 0.5 sobre datos [0, 255] selecciona absolutamente todo.
  • bwlabel() etiqueta 4-conectado por defecto. Para 8-conexión hay que pasar por morfología (dilate + bwlabel) o usar watershed(). Objetos diagonales contiguos pueden quedar separados sin error visible.
  • Watershed sin marcadores produce oversegmentación. El patrón típico es aplicar distmap() sobre la máscara binaria y usarlo como input, sin esa transformación, el resultado tiene cientos de regiones espurias. Ajusta tolerance para controlar la sensibilidad.
  • Multicanal vs multistack. colormode(img) distingue uno de otro. Operaciones que asumen Color sobre un stack de z (o viceversa) producen errores opacos al final del pipeline.

Enlaces

Relacionados en esta página

  • imager, análogo general, sin orientación biomédica.
  • magick, lectura/escritura de formatos exóticos antes de pasar a EBImage.

opencv

opencv (paquete de R por Jeroen Ooms) es el wrapper sobre OpenCV, la librería estándar de visión por computador. Cubre lo que el ecosistema R no toca: detección de caras, detección de objetos (Haar cascades, HOG), cálculo de flujo óptico, calibración de cámara, feature matching (ORB, SIFT cuando está compilado), transformaciones geométricas avanzadas (homografías, undistort) y manipulación de vídeo en tiempo real desde webcam.

El paquete expone una fracción de la API completa de OpenCV, para uso intensivo, casi todo el mundo usa OpenCV en Python. Aun así, es la mejor opción dentro de R para tareas de visión clásica.

Cuándo usarlo

  • Detección de caras u objetos con clasificadores preentrenados (Haar, DNN).
  • Captura desde webcam o procesado de vídeo (ocv_video, ocv_camera).
  • Transformaciones geométricas no triviales: perspectiva, undistort, registro de imágenes.
  • Cuando necesitas mantenerte en R por motivos de pipeline pero la operación no existe en imager ni EBImage.

Cuándo NO usarlo

  • Visión por computador moderna basada en deep learning (detección con YOLO, segmentación con Cellpose / SAM, OCR con TrOCR). Aquí Python es estándar de facto. Conviene invocar el modelo externamente o vía reticulate.
  • Análisis cuantitativo biomédico: EBImage está mejor integrado con el resto del flujo (descriptores, multicanal).
  • Tareas simples que magick o imager resuelven en menos líneas y sin la dependencia binaria pesada de OpenCV.

Conceptos clave

  • La clase es opencv-image (puntero a cv::Mat en C++). Las operaciones devuelven nuevos punteros y se encadenan con |>.
  • Orden de canales BGR (no RGB) por herencia de OpenCV. Si exportas a magick o imager, conviene reordenar canales o aceptar el desplazamiento.
  • Píxeles en rango [0, 255] uint8 (no [0, 1]). Otra diferencia respecto a EBImage / imager.
  • ocv_picture() lee imagen. ocv_video() itera frames de vídeo o webcam aplicando una función por frame.
  • ocv_face() y ocv_bbox() son las primitivas más usadas para detección de caras / cajas.

Patrón mínimo

library(opencv)

img <- ocv_read("retrato.jpg")

# Detección de caras con cascade preentrenado
faces <- ocv_face(img)                     # cajas dibujadas sobre la imagen
boxes <- attr(faces, "faces")              # coordenadas (x, y, w, h)

# Conversión a array R para análisis manual
mat <- ocv_bitmap(img)                     # array uint8, h × w × 3 (BGR)

# Procesado de vídeo desde webcam (cierra con Esc)
ocv_video(function(frame) ocv_face(frame))

Trampas habituales

  • BGR vs RGB. El error clásico: ves caras azules tras convertir a magick. Reordena con mat <- mat[,, c(3,2,1)] antes de cruzar a otro paquete, o usa ocv_bgr2rgb() si está disponible en tu versión.
  • Coordenadas con origen arriba-izquierda y eje Y hacia abajo. Estándar en imagen, pero diferente de plots clásicos de R (origen abajo-izquierda). Al dibujar cajas con rect() sobre un plot(), hay que invertir Y o usar ylim = c(h, 0).
  • Tamaño del binario. El paquete opencv instala una versión recortada de OpenCV. Funcionalidades como SIFT o módulos extra requieren reinstalar con flags de compilación específicos en Linux/macOS, en Windows es directamente difícil. Verifica con ocv_version() qué módulos están disponibles antes de planificar.

Enlaces

Relacionados en esta página

  • imager, operaciones de filtrado / convolución más idiomáticas en R.
  • magick, lectura/escritura cómoda antes y después de pasar por OpenCV.

torch / keras3 (preprocesado para deep learning)

Para preprocesado de imágenes que alimenta una red neuronal, ni magick ni imager son la opción correcta. Los frameworks de deep learning en R, torch (binding directo a libtorch, sin Python) y keras3 (wrapper sobre TensorFlow vía reticulate), incluyen sus propias capas de transformación que operan sobre tensores y se ejecutan en GPU cuando está disponible. Mezclar el preprocesado clásico de CPU con el grafo del modelo es lento, frágil y rompe la reproducibilidad.

No es estrictamente un “paquete de imágenes”, pero esta página estaría incompleta sin señalarlo: es el destino natural de muchos pipelines que empiezan con magick o imager.

Cuándo usarlo

  • Entrenar o hacer inferencia con CNNs (clasificación, segmentación, detección) en R.
  • Cuando el preprocesado (resize, normalize, augmentation) debe ejecutarse en GPU junto al modelo.
  • Cuando necesitas augmentación estocástica reproducible por batch: flips, rotaciones, color jitter, mixup, cutmix.

Cuándo NO usarlo

  • Preprocesado offline una sola vez (precomputar features, normalizar para análisis estadístico clásico). Ahí imager o EBImage siguen siendo la opción correcta y más legible.
  • Tareas de edición o composición. Estos paquetes no están diseñados para anotar, redimensionar por lote para exportar a disco, ni cambiar formato.

Conceptos clave

  • En torch R, las imágenes son torch_tensor de forma c(N, C, H, W) (NCHW, convención PyTorch). Tipo float32, rango típico [0, 1] tras normalización.
  • torchvision::transform_* provee resize, crop, normalización con media/desviación de ImageNet, augmentación básica. Se encadenan con transform_compose().
  • En keras3, las imágenes son tensores c(N, H, W, C) (NHWC, convención TensorFlow). Las capas layer_random_flip(), layer_random_rotation(), layer_rescaling() se integran como primeras capas del modelo y se ejecutan en GPU durante el fit().
  • dataset_image_folder() (torch) o image_dataset_from_directory() (keras3) son los lectores idiomáticos cuando los datos están en estructura train/clase_X/*.jpg.

Patrón mínimo

library(torch)
library(torchvision)

transform <- function(img) {
  img |>
    transform_to_tensor() |>
    transform_resize(c(224, 224)) |>
    transform_normalize(
      mean = c(0.485, 0.456, 0.406),       # estadísticas ImageNet
      std  = c(0.229, 0.224, 0.225)
    )
}

ds <- image_folder_dataset("datos/train", transform = transform)
dl <- dataloader(ds, batch_size = 32, shuffle = TRUE)

batch <- dataloader_make_iter(dl) |> dataloader_next()
dim(batch[[1]])   # [1] 32  3 224 224

Trampas habituales

  • NCHW vs NHWC. torch espera c(N, C, H, W). keras3 / TensorFlow esperan c(N, H, W, C). Si construyes el tensor a mano desde magick o imager, una sola permutación mal puesta produce un modelo que entrena (la pérdida baja un poco por azar) pero da resultados absurdos. Verifica dim(batch) en el primer batch siempre.
  • Normalización con estadísticas de ImageNet vs propias. Si finetuneas un modelo preentrenado en ImageNet, normaliza con las medias/desviaciones canónicas (las del snippet). Si entrenas desde cero, calcula media y desviación sobre tu dataset, usar las de ImageNet sobre microscopía o imagen médica degrada el aprendizaje sin error visible.
  • Augmentación fuera del grafo. Si haces augmentación con magick o imager antes de pasar a torch, ocurre en CPU y serializada. Cualquier augmentación moderna debe vivir dentro de transform_* (torch) o como capas iniciales (keras3) para aprovechar GPU y paralelismo del dataloader.

Enlaces

Relacionados en esta página

  • magick y imager, útiles para inspección manual y preprocesado offline, no para alimentar el grafo de entrenamiento.
  • EBImage, preprocesado biomédico clásico que puede combinarse con modelos de deep learning vía reticulate (Cellpose, StarDist).