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

时间:2011-12-25 19:09:56

标签: python imagemagick python-imaging-library

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

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

我应该使用像imagemagick这样的外部库,还是有一种有效的内部方法来实现这一目标?

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

120x120
720x720
1600x1600

质量是一个问题,因为我希望尽可能多地保留原始颜色,并最大限度地减少压缩失真。

感谢。

8 个答案:

答案 0 :(得分:23)

你希望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)))

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

答案 1 :(得分:12)

这个问题有点晚了(只有一年!),但我会小心翼翼地支持@ JakobBowyer回答的“多进程”部分。

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

由于multiprocessing.Pool提供的地图功能,Python实际上非常擅长这些问题。

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))

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

答案 2 :(得分:11)

我想像一些乐趣,所以我对上面建议的各种方法和我自己的一些想法进行了基准测试。

我收集了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))

结果:1​​70秒


方法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))

结果: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))

结果:1​​8秒


方法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))

结果: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

结果:38秒


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

这是对Jakob答案的一种改进,Jakob的答案是只加载一次图像,然后将其缩小三倍,而不是每次重新加载以产生新的分辨率。

#!/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

结果: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)))))

结果: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)))))

结果:5秒

答案 3 :(得分:4)

另一个选择是使用python bindingsOpenCV。这可能比PIL或Imagemagick更快。

import cv2

sizes = [(120, 120), (720, 720), (1600, 1600)]
image = cv2.imread("input.jpg")
for size in sizes:
    resized_image = cv2.resize(image, size)
    cv2.imwrite("thumbnail_%d.jpg" % size[0], resized_image) 

这是一个更完整的演练here

如果要并行运行,请在Py3上使用concurrent.futures或在Py2.7上使用futures包:

import concurrent.futures
import cv2

def resize(input_filename, size):
    image = cv2.imread(input_filename)
    resized_image = cv2.resize(image, size)
    cv2.imwrite("thumbnail_%s%d.jpg" % (input_filename.split('.')[0], size[0]), resized_image)

executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
sizes = [(120, 120), (720, 720), (1600, 1600)]
for size in sizes:
    executor.submit(resize, "input.jpg", size)

答案 4 :(得分:3)

如果您已经熟悉imagemagick,为什么不坚持使用python-bindings?

PythonMagick

答案 5 :(得分:2)

Python 2.7,Windows,x64用户

除了@JakobBowyer& @AudionauticsPIL已经很老了,你可以找到自己的问题排查并寻找合适的版本......而是使用here source中的Pillow

更新的代码段如下所示:

im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")

创建缩略图的完整枚举脚本:

import os
from PIL import Image

output_dir = '.\\output'
thumbnail_size = (200,200)

if not os.path.exists(output_dir):
    os.makedirs(output_dir)

for dirpath, dnames, fnames in os.walk(".\\input"):
    for f in fnames:
        full_path = os.path.join(dirpath, f)
        if f.endswith(".jpg"):
            filename = 'thubmnail_{0}'.format(f) 
            new_path = os.path.join(output_dir, filename)

            if os.path.exists(new_path):
                os.remove(new_path)

            im = Image.open(full_path)
            im.thumbnail(thumbnail_size)
            im.save(new_path, "JPEG")

答案 6 :(得分:2)

又一个答案,因为(我认为?)没有人提到质量。

这是我在东伦敦奥林匹克公园用iPhone 6S拍摄的照片:

roof of olympic swimming pool

屋顶是由一组木板制成的,除非您仔细缩小尺寸,否则会产生非常讨厌的摩尔纹效果。我不得不大量压缩图像以上传到stackoverflow ---如果您感兴趣的话,the original is here

此处是cv2的大小调整:

$ python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> x = cv2.imread("IMG_1869.JPG")
>>> y = cv2.resize(x, (120, 90))
>>> cv2.imwrite("cv2.png", y)
True

这里是vipsthumbnail

$ vipsthumbnail IMG_1869.JPG -s 120 -o vips.png

这是两个并排缩小的图像,并放大了2倍,左侧有vipsthumbnail:

results of downsize to 120 pixels across

(ImageMagick提供与vipsthumbnail相同的结果)

cv2默认为BILINEAR,因此它具有固定的2x2掩码。对于输出图像中的每个点,它都会计算输入中的对应点,并取2x2的平均值。这意味着它实际上仅在每行中最多采样240个点,而忽略其他3750个点!这样会产生难看的别名。

vipsthumbnail正在执行更复杂的三阶段缩减。

  1. 它使用libjpeg加载时收缩功能将图像在每个轴上收缩8倍,并带有框式滤镜,可将图像上的4032像素变为504 x 378像素。
  2. 它进一步缩小了2 x 2的框滤镜,以获取252 x 189像素。
  3. 它以5 x 5 Lanczos3内核结束,以获取120 x 90像素的输出图像。

这应该具有与完整的Lanczos3内核同等的质量,但速度更快,因为它可以对大多数方式进行盒式过滤。

答案 7 :(得分:0)

当我试图找出应该使用哪个库时,我偶然发现了this

OpenCV显然比PIL快

也就是说,我正在使用电子表格,事实证明我正在使用openpyxl already requires me to import PIL to insert images的模块。