在Python中生成图像缩略图的最快方法是什么?

ens*_*are 26 python imagemagick python-imaging-library

我正在使用Python构建照片库,并希望能够快速生成高分辨率图像的缩略图.

为各种图像源生成高质量缩略图的最快方法是什么?

我应该使用像imagemagick这样的外部库吗?还是有一种有效的内部方式来做到这一点?

已调整大小的图像的尺寸将为(最大尺寸):

120x120
720x720
1600x1600
Run Code Online (Sandbox Code Playgroud)

质量是一个问题,因为我希望保留尽可能多的原始颜色并最小化压缩工件.

谢谢.

Jak*_*yer 24

你想要PIL它可以轻松完成

from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = ['a.jpg','b.jpg','c.jpg']

for image in files:
    for size in sizes:
        im = Image.open(image)
        im.thumbnail(size)
        im.save("thumbnail_%s_%s" % (image, "_".join(size)))
Run Code Online (Sandbox Code Playgroud)

如果你迫切需要速度.然后线程化,多处理它或获得另一种语言.

  • 最新版本的PIL不再支持`import Image`你应该使用`来自PIL import Image` (3认同)

Mar*_*ell 20

我觉得很有趣,所以我对上面提到的各种方法和我自己的一些想法做了一些基准测试.

我收集了1000张高分辨率12MP iPhone 6s图像,每张4032x3024像素,并使用8核iMac.

以下是技术和结果 - 每个都在其自己的部分.


方法1 - 顺序ImageMagick

这是简单的,未经优化的代码.读取每个图像并生成缩略图.然后再次读取它并生成不同大小的缩略图.

#!/bin/bash

start=$SECONDS
# Loop over all files
for f in image*.jpg; do
   # Loop over all sizes
   for s in 1600 720 120; do
      echo Reducing $f to ${s}x${s}
      convert "$f" -resize ${s}x${s} t-$f-$s.jpg
   done
done
echo Time: $((SECONDS-start))
Run Code Online (Sandbox Code Playgroud)

结果:170秒


方法2 - 顺序ImageMagick单负载和连续调整大小

这仍然是顺序但略微更聪明.每个图像仅读取一次,然后将加载的图像调整为三个并以三个分辨率保存.改进之处在于每个图像只读取一次,而不是3次.

#!/bin/bash

start=$SECONDS
# Loop over all files
N=1
for f in image*.jpg; do
   echo Resizing $f
   # Load once and successively scale down
   convert "$f"                              \
      -resize 1600x1600 -write t-$N-1600.jpg \
      -resize 720x720   -write t-$N-720.jpg  \
      -resize 120x120          t-$N-120.jpg
   ((N=N+1))
done
echo Time: $((SECONDS-start))
Run Code Online (Sandbox Code Playgroud)

结果:76秒


方法3 - GNU Parallel + ImageMagick

这建立在前一种方法的基础上,使用GNU Parallel并行处理N图像,其中N是您机器上CPU核心的数量.

#!/bin/bash

start=$SECONDS

doit() {
   file=$1
   index=$2
   convert "$file"                               \
      -resize 1600x1600 -write t-$index-1600.jpg \
      -resize 720x720   -write t-$index-720.jpg  \
      -resize 120x120          t-$index-120.jpg
}

# Export doit() to subshells for GNU Parallel   
export -f doit

# Use GNU Parallel to do them all in parallel
parallel doit {} {#} ::: *.jpg

echo Time: $((SECONDS-start))
Run Code Online (Sandbox Code Playgroud)

结果:18秒


方法4 - GNU Parallel + vips

这与前一个方法相同,但它vips在命令行而不是ImageMagick中使用.

#!/bin/bash

start=$SECONDS

doit() {
   file=$1
   index=$2
   r0=t-$index-1600.jpg
   r1=t-$index-720.jpg
   r2=t-$index-120.jpg
   vipsthumbnail "$file"  -s 1600 -o "$r0"
   vipsthumbnail "$r0"    -s 720  -o "$r1"
   vipsthumbnail "$r1"    -s 120  -o "$r2"
}

# Export doit() to subshells for GNU Parallel   
export -f doit

# Use GNU Parallel to do them all in parallel
parallel doit {} {#} ::: *.jpg

echo Time: $((SECONDS-start))
Run Code Online (Sandbox Code Playgroud)

结果:8秒


方法5 - 顺序PIL

这是为了对应雅各布的答案.

#!/usr/local/bin/python3

import glob
from PIL import Image

sizes = [(120,120), (720,720), (1600,1600)]
files = glob.glob('image*.jpg')

N=0
for image in files:
    for size in sizes:
      im=Image.open(image)
      im.thumbnail(size)
      im.save("t-%d-%s.jpg" % (N,size[0]))
    N=N+1
Run Code Online (Sandbox Code Playgroud)

结果:38秒


方法6 - 具有单次加载和连续调整大小的顺序PIL

这是对雅各布答案的改进,其中图像只加载一次然后调整大小三次,而不是每次重新加载以产生每个新的分辨率.

#!/usr/local/bin/python3

import glob
from PIL import Image

sizes = [(120,120), (720,720), (1600,1600)]
files = glob.glob('image*.jpg')

N=0
for image in files:
   # Load just once, then successively scale down
   im=Image.open(image)
   im.thumbnail((1600,1600))
   im.save("t-%d-1600.jpg" % (N))
   im.thumbnail((720,720))
   im.save("t-%d-720.jpg"  % (N))
   im.thumbnail((120,120))
   im.save("t-%d-120.jpg"  % (N))
   N=N+1
Run Code Online (Sandbox Code Playgroud)

结果:27秒


方法7 - 并行PIL

这是为了与Audionautics的回答相对应,只要它使用Python的多处理.它还消除了为每个缩略图大小重新加载图像的需要.

#!/usr/local/bin/python3

import glob
from PIL import Image
from multiprocessing import Pool 

def thumbnail(params): 
    filename, N = params
    try:
        # Load just once, then successively scale down
        im=Image.open(filename)
        im.thumbnail((1600,1600))
        im.save("t-%d-1600.jpg" % (N))
        im.thumbnail((720,720))
        im.save("t-%d-720.jpg"  % (N))
        im.thumbnail((120,120))
        im.save("t-%d-120.jpg"  % (N))
        return 'OK'
    except Exception as e: 
        return e 


files = glob.glob('image*.jpg')
pool = Pool(8)
results = pool.map(thumbnail, zip(files,range((len(files)))))
Run Code Online (Sandbox Code Playgroud)

结果:6秒


方法8 - 并行OpenCV

这是对bcattle答案的改进,只要它使用OpenCV,但它也不需要重新加载图像来生成每个新的分辨率输出.

#!/usr/local/bin/python3

import cv2
import glob
from multiprocessing import Pool 

def thumbnail(params): 
    filename, N = params
    try:
        # Load just once, then successively scale down
        im = cv2.imread(filename)
        im = cv2.resize(im, (1600,1600))
        cv2.imwrite("t-%d-1600.jpg" % N, im) 
        im = cv2.resize(im, (720,720))
        cv2.imwrite("t-%d-720.jpg" % N, im) 
        im = cv2.resize(im, (120,120))
        cv2.imwrite("t-%d-120.jpg" % N, im) 
        return 'OK'
    except Exception as e: 
        return e 


files = glob.glob('image*.jpg')
pool = Pool(8)
results = pool.map(thumbnail, zip(files,range((len(files)))))
Run Code Online (Sandbox Code Playgroud)

结果:5秒

  • 很好的比较,马克 (2认同)
  • 这个答案大大优于上述所有(和接受的)答案 (2认同)

Aud*_*ics 13

这个问题有点晚了(只有一年!),但我会在@ JakobBowyer回答的"多进程"部分支持.

这是一个令人尴尬的并行问题的一个很好的例子,因为代码的主要部分不会改变自身外部的任何状态.它只是读取输入,执行计算并保存结果.

由于提供的map函数,Python实际上非常擅长这些类型的问题multiprocessing.Pool.

from PIL import Image
from multiprocessing import Pool 

def thumbnail(image_details): 
    size, filename = image_details
    try:
        im = Image.open(filename)
        im.thumbnail(size)
        im.save("thumbnail_%s" % filename)
        return 'OK'
    except Exception as e: 
        return e 

sizes = [(120,120), (720,720), (1600,1600)]
files = ['a.jpg','b.jpg','c.jpg']

pool = Pool(number_of_cores_to_use)
results = pool.map(thumbnail, zip(sizes, files))
Run Code Online (Sandbox Code Playgroud)

代码的核心与@JakobBowyer完全相同,但我们不是在单个线程的循环中运行它,而是将它包装在一个函数中,通过多处理映射函数将其分布在多个核心中.

  • 难道你不想要笛卡尔积而不是`zip`吗? (2认同)