Imágenes
Procesamiento, manipulación y análisis de imágenes en R
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),
magickes casi siempre la opción correcta. Si la salida es una medida numérica (intensidad, área, recuento de objetos),EBImageoimagerson más naturales. - ¿Rango de píxeles? Casi todo el ecosistema R analítico (EBImage, imager) trabaja con valores en [0, 1] en
double.magicky la mayoría de formatos en disco usan [0, 255] eninteger/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") # BioconductorEsta 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.
magickno expone la matriz de píxeles cómodamente. Cada operación devuelve un objetomagick-imageopaco. Para umbralizar, segmentar, contar objetos o medir intensidad, usaEBImage(microscopía / biomedicina) oimager(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
imagero álgebra de matrices directa. - Preprocesado para redes neuronales. Usa los pipelines nativos del framework:
torchvision::transform_*entorchotf$image/ capaskeras3de preprocesado. Son más rápidos y mantienen el grafo en GPU.
Conceptos clave
- El objeto
magick-imagees 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. Usaimage_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 pasasformatexplícitamente: guardará en el formato del objeto, no en el de la ruta. Especifica siempreformat = "png"(o equivalente) cuando importe.- Conversión a array.
as.integer(image_data(img))devuelve dimensionesc(channels, width, height), noc(height, width, channels)como esperaimageroEBImage. 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
zestá integrado en la clase. - Cuando ya trabajas con el array de píxeles como
doubleen [0, 1] y necesitas operaciones idiomáticas vectorizadas.
Cuándo NO usarlo
- Microscopía / análisis cuantitativo de objetos biológicos.
EBImagetiene primitivas específicas (bwlabel,computeFeatures, segmentación watershed orientada a células) que enimagerhay que reconstruir manualmente. - Edición y composición.
magickes 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 4Dx × y × z × c. Para imágenes 2D corrientes,z = 1. - Píxeles en
doublerango [0, 1].as.cimg()desde una matrizinteger0-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 uncimgproduce visualización directa con orientación correcta (origen arriba-izquierda).- Convierte a/de
magickconmagick2cimg()ycimg2magick().
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.
cimgesx × y × z × c, noheight × 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
magicky conviertes conmagick2cimg(), el escalado se hace solo. Si construyes uncimgdesde una matriz cruda conas.cimg(), escala dividiendo por 255 antes, si no, las operaciones de filtrado producen saturaciones absurdas. grayscale()vs canal único.grayscale()devuelve uncimgconc = 1(luminancia). Indexar el canal R conR(img)también devuelvec = 1pero 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íamagick2cimg.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
limmaoDESeq2, etc.).
Cuándo NO usarlo
- Edición general o composición para humanos:
magickes más cómodo y rápido. - Visión por computador clásica (matching de features, homografías, calibración):
opencves 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
reticulateo como pipeline externo.
Conceptos clave
- La clase es
Image, unarraycon atributocolormode(GrayscaleoColor). Los píxeles sondoubleen [0, 1], igual queimager. - Imágenes multicanal: el último eje del array es el canal (
Color). Imágenes multistack (z-stack o time-lapse) tienen un eje adicional concolormode = Grayscale. - Pipeline canónico de segmentación:
gblur()(suavizar) →thresh()ootsu()(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()ycomputeFeatures.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 desdemagicko desde lectura cruda, divide por 255 antes, un umbral comoimg > 0.5sobre 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 usarwatershed(). 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. Ajustatolerancepara 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
imagerniEBImage.
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:
EBImageestá mejor integrado con el resto del flujo (descriptores, multicanal). - Tareas simples que
magickoimagerresuelven en menos líneas y sin la dependencia binaria pesada de OpenCV.
Conceptos clave
- La clase es
opencv-image(puntero acv::Maten C++). Las operaciones devuelven nuevos punteros y se encadenan con|>. - Orden de canales BGR (no RGB) por herencia de OpenCV. Si exportas a
magickoimager, 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()yocv_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 conmat <- mat[,, c(3,2,1)]antes de cruzar a otro paquete, o usaocv_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 unplot(), hay que invertir Y o usarylim = c(h, 0). - Tamaño del binario. El paquete
opencvinstala 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 conocv_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í
imageroEBImagesiguen 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
torchR, las imágenes sontorch_tensorde formac(N, C, H, W)(NCHW, convención PyTorch). Tipofloat32, 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 contransform_compose().- En
keras3, las imágenes son tensoresc(N, H, W, C)(NHWC, convención TensorFlow). Las capaslayer_random_flip(),layer_random_rotation(),layer_rescaling()se integran como primeras capas del modelo y se ejecutan en GPU durante elfit(). dataset_image_folder()(torch) oimage_dataset_from_directory()(keras3) son los lectores idiomáticos cuando los datos están en estructuratrain/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 224Trampas habituales
- NCHW vs NHWC.
torchesperac(N, C, H, W).keras3/ TensorFlow esperanc(N, H, W, C). Si construyes el tensor a mano desdemagickoimager, una sola permutación mal puesta produce un modelo que entrena (la pérdida baja un poco por azar) pero da resultados absurdos. Verificadim(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
magickoimagerantes de pasar atorch, ocurre en CPU y serializada. Cualquier augmentación moderna debe vivir dentro detransform_*(torch) o como capas iniciales (keras3) para aprovechar GPU y paralelismo del dataloader.