Redimensionar una carpeta de imágenes con Pillow conservando los originales
Redimensiona varias imágenes en una carpeta nueva sin estirarlas ni sustituir los archivos de origen. Un pequeño conjunto de imágenes sintéticas permite calcular fácilmente a mano las dimensiones esperadas de salida.
Contenido revisado 2026.09.20Incluye archivos de ejemplo
Ver el índice
La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English
Para quién esEsta guía está dirigida a personas que necesitan copias más pequeñas de muchas imágenes conservando las proporciones de cada una y dejando intactos los originales.
Preparación
Python 3.12 y un comando de terminal que inicie esa versión.
Pillow instalado con python -m pip install Pillow.
Una carpeta de trabajo donde los scripts puedan crear nuevas carpetas dentro de outputs.
Espacio suficiente en disco para las imágenes sintéticas originales y las copias redimensionadas.
01Definir primero la regla de redimensionado
El objetivo es ajustar cada imagen dentro de un cuadro de 300 by 300 pixel conservando su relación de aspecto. Las imágenes que ya son más pequeñas que ese cuadro no se amplían. Las imágenes de origen permanecen sin cambios y cada copia redimensionada se escribe en una carpeta de salida independiente.
Para una imagen con ancho W y alto H, calcula scale como el menor valor entre 300/W, 300/H y 1. Después calcula el nuevo ancho y alto a partir de ese único scale factor. Usar un solo factor para ambas dimensiones evita el estiramiento.
Los siguientes archivos son sintéticos y fueron creados específicamente para este artículo. El script de preparación crea cuatro imágenes PNG sencillas con dimensiones conocidas. Su contenido visual no es importante; las dimensiones se eligieron para que todos los resultados esperados del redimensionado puedan comprobarse manualmente.
Los cuatro originales contienen un total de 1,240,000 pixels: 480,000 + 480,000 + 20,000 + 360,000. El número de pixels es útil para comprender el ejemplo, pero no equivale al tamaño del archivo en bytes porque la compresión PNG depende del contenido de la imagen.
03Calcular manualmente las cuatro dimensiones esperadas
Para landscape.png, la dimensión limitante es el ancho: 300/800 = 0.375. Multiplicar ambas dimensiones por 0.375 da 300 by 225. Para portrait.png, la altura es la limitante: 300/800 = 0.375, produciendo 225 by 300.
small.png ya cabe porque ambas dimensiones son iguales o inferiores a 300. El scale está limitado a 1, por lo que permanece en 200 by 100. Para wide.png, el ancho limita el resultado: 300/1200 = 0.25, dando 300 by 75.
Archivo
Scale
Salida esperada
Pixels de salida
landscape.png
0.375
300 × 225
67,500
portrait.png
0.375
225 × 300
67,500
small.png
1
200 × 100
20,000
wide.png
0.25
300 × 75
22,500
Por tanto, las copias redimensionadas contienen un total de 177,500 pixels. Los originales siguen presentes por separado, así que esta cifra no representa un ahorro de espacio en disco.
04Redimensionar la carpeta hacia un nuevo destino
Guarda el siguiente script como batch_image_resize.py. Procesa archivos PNG, JPEG y WebP directamente dentro de SOURCE. No busca subcarpetas de forma recursiva. El destino se crea con mkdir sin exist_ok, por lo que un directorio de salida ya existente hace que el script se detenga en lugar de mezclar los resultados nuevos con los de una ejecución anterior.
python
from pathlib import Path
from PIL import Image, ImageOps
SOURCE = Path("outputs") / "image_resize_demo"
OUTPUT_DIR = Path("outputs") / "image_resize_result"
MAX_WIDTH = 300
MAX_HEIGHT = 300
SUPPORTED = {".png", ".jpg", ".jpeg", ".webp"}
def target_size(width: int, height: int) -> tuple[int, int]:
scale = min(MAX_WIDTH / width, MAX_HEIGHT / height, 1)
new_width = max(1, int(width * scale))
new_height = max(1, int(height * scale))
return new_width, new_height
def main() -> None:
if not SOURCE.is_dir():
raise FileNotFoundError(f"Source folder not found: {SOURCE}")
if MAX_WIDTH < 1 or MAX_HEIGHT < 1:
raise ValueError("Maximum dimensions must be positive integers.")
source_resolved = SOURCE.resolve()
output_resolved = OUTPUT_DIR.resolve()
if source_resolved == output_resolved:
raise ValueError("SOURCE and OUTPUT_DIR must be different.")
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir() # Refuse to reuse an existing destination.
candidates = sorted(
path for path in SOURCE.iterdir()
if path.is_file() and path.suffix.lower() in SUPPORTED
)
resized_count = 0
unchanged_size_count = 0
for source_path in candidates:
output_path = OUTPUT_DIR / source_path.name
if output_path.exists():
raise FileExistsError(f"Output already exists: {output_path}")
with Image.open(source_path) as opened:
image = ImageOps.exif_transpose(opened)
image.load()
old_size = image.size
new_size = target_size(*old_size)
if new_size == old_size:
result = image.copy()
unchanged_size_count += 1
else:
result = image.resize(new_size, Image.Resampling.LANCZOS)
resized_count += 1
save_options = {}
if source_path.suffix.lower() in {".jpg", ".jpeg"}:
save_options["quality"] = 90
result.save(output_path, **save_options)
result.close()
print(f"{source_path.name}: {old_size[0]}x{old_size[1]} -> {new_size[0]}x{new_size[1]}")
print(f"Processed {len(candidates)} images.")
print(f"Resized: {resized_count}; already within limit: {unchanged_size_count}.")
print(f"Output folder: {OUTPUT_DIR.as_posix()}")
if __name__ == "__main__":
main()
ImageOps.exif_transpose aplica la orientación EXIF de una imagen antes del cálculo de redimensionado cuando existen esos datos de orientación. Los archivos PNG sintéticos no tienen ese requisito, por lo que sus dimensiones mostradas siguen siendo las indicadas anteriormente.
05Comparar la salida esperada
Como los nombres de archivo se procesan en orden, las líneas esperadas de consola son landscape.png, portrait.png, small.png y wide.png. Tres imágenes se hacen más pequeñas y una permanece con sus dimensiones originales.
Esta salida esperada se obtuvo manualmente y no se capturó de una ejecución. Las comprobaciones importantes son las dimensiones, el número de archivos y el hecho de que los originales permanezcan en outputs/image_resize_demo.
Comprobación
Valor esperado
Archivos en la carpeta de origen
4
Archivos en la carpeta de salida
4
Archivos redimensionados
3
Archivos que ya estaban dentro del límite
1
Mayor ancho de salida
300
Mayor alto de salida
300
06Verificar personalmente las imágenes generadas
No dependas únicamente de los nombres de archivo o del texto de consola. Abre los archivos resultantes e inspecciona sus dimensiones. El siguiente comprobador opcional lee ambas carpetas e informa de las dimensiones. No realiza ninguna escritura.
python
from pathlib import Path
from PIL import Image
SOURCE = Path("outputs") / "image_resize_demo"
OUTPUT_DIR = Path("outputs") / "image_resize_result"
for source_path in sorted(SOURCE.glob("*.png")):
output_path = OUTPUT_DIR / source_path.name
with Image.open(source_path) as original, Image.open(output_path) as resized:
print(
f"{source_path.name}: "
f"original={original.size}, output={resized.size}"
)
Confirma que cada ancho de salida sea como máximo 300 y cada alto de salida sea como máximo 300.
Confirma que landscape.png siga siendo 4:3, portrait.png siga siendo 3:4 y wide.png siga siendo 4:1.
Confirma que small.png permanezca en 200 by 100 en lugar de ampliarse a 300 by 150.
Compara las fechas de modificación o los hashes si necesitas evidencia adicional de que los archivos de origen no fueron reescritos.
Ejecuta el script de redimensionado por segunda vez sin cambiar OUTPUT_DIR. Debería detenerse con FileExistsError antes de procesar otro lote.
07Reconocer errores comunes
Síntoma
Qué comprobar
ModuleNotFoundError: No module named PIL
Instala Pillow en el mismo entorno de Python utilizado para ejecutar el script con python -m pip install Pillow.
FileNotFoundError
Comprueba SOURCE y el directorio de trabajo actual de la terminal. Para este ejemplo, ejecuta primero el script de preparación sintético.
FileExistsError
La carpeta de salida ya existe. Revísala y selecciona un destino nuevo en lugar de permitir una sobrescritura automática.
Image cannot be identified
La extensión puede parecer compatible aunque el contenido esté dañado o no sea realmente una imagen. Considera incompleto el lote fallido.
Unexpected rotation
Los metadatos de orientación y la orientación de pixels pueden diferir. ImageOps.exif_transpose se utiliza antes del redimensionado, pero inspecciona visualmente las imágenes importantes.
Unexpected output file size in bytes
Las dimensiones en pixels por sí solas no determinan el tamaño del archivo comprimido. También influyen el formato, el contenido de la imagen, los metadatos y la calidad JPEG.
Si el procesamiento falla después de haber escrito algunos archivos, el script no elimina esos resultados parciales. Conserva el destino para investigarlo o utiliza un nuevo directorio de salida después de corregir el problema.
08Comprender los límites antes de utilizar imágenes reales
Este ejemplo solo maneja archivos directamente dentro de una carpeta y admite extensiones PNG, JPEG y WebP. No reproduce un árbol de directorios anidado, no redimensiona individualmente los frames animados, no conserva todas las formas de metadatos ni garantiza que la información de gestión del color permanezca sin cambios.
El remuestreo modifica los datos de pixels. Incluso cuando una imagen conserva las mismas dimensiones, abrirla y guardarla puede cambiar sus bytes codificados o sus metadatos. La salida JPEG tiene pérdida y este script guarda las copias JPEG con quality 90; eso no conserva el bitstream JPEG original ni la configuración de compresión original.
El cálculo con int redondea hacia abajo las dimensiones fraccionarias. Para tamaños de origen arbitrarios, la relación de pixels resultante puede diferir ligeramente de la relación matemática porque las dimensiones de una imagen deben ser números enteros. Las dimensiones sintéticas de esta guía se dividen exactamente, por lo que los cuatro resultados esperados no requieren un redondeo ambiguo.
Registro de ejecución y verificación
2026-09-20 · ejemplo revisado manualmente · objetivo: Python 3.12 · se requiere Pillow · sin ejecución
Se comprobaron manualmente las cuatro dimensiones sintéticas de origen: 800x600, 600x800, 200x100 y 1200x300.
Se calcularon manualmente los scale factors 0.375, 0.375, 1 y 0.25.
Se obtuvieron manualmente las dimensiones de salida esperadas 300x225, 225x300, 200x100 y 300x75.
Se calculó manualmente el total de pixels de origen como 1,240,000 y el total de pixels de salida como 177,500.
Se revisó el código para confirmar que los archivos de origen se abren para lectura, los resultados se guardan en una carpeta separada y una carpeta de destino ya existente hace que la ejecución se detenga.
Se obtuvieron manualmente la salida esperada de consola y los recuentos.
Límites de la verificación
El código no fue ejecutado por el autor de esta respuesta; aquí no se probaron el comportamiento de Pillow ni las escrituras en el sistema de archivos.
JPEG, WebP, orientación EXIF, archivos dañados, perfiles de color, animación y conservación de metadatos se comentaron, pero no se probaron.
No se predijeron tamaños de archivo comprimido en bytes porque dependen del contenido de la imagen y de la codificación.
Las URL de la documentación oficial se proporcionaron a partir de ubicaciones de documentación conocidas, pero no se comprobaron en línea.
Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.
Recorre las subcarpetas y registra en una tabla las rutas, extensiones, tamaños y fechas de modificación de los archivos. Empieza con 4 archivos de ejemplo pequeños, manteniendo intactos los originales y los resultados existentes.
Combina en orden archivos CSV con la misma estructura de columnas y añade una columna source_file. Usa conjuntos de datos pequeños para comprobar nombres de elementos con comas, columnas faltantes y salidas ya existentes.
Compara los nombres antiguos y nuevos en una tabla y comprueba conflictos. Solo un --apply explícito crea copias en una carpeta nueva, manteniendo los originales.