在Python中生成图像缩略图的最快方法是什么?
我正在用Python制作一个照片画廊,想要快速生成高分辨率图片的缩略图。
有没有什么快速的方法可以为各种图片来源生成高质量的缩略图呢?
我应该使用像imagemagick这样的外部库,还是有更有效的内部方法可以做到这一点呢?
调整大小后的图片尺寸将是(最大尺寸):
120x120
720x720
1600x1600
质量很重要,因为我希望尽可能保留原始颜色,并减少压缩带来的瑕疵。
谢谢。
8 个回答
虽然这个问题问得有点晚(才过了一年!),但我想借用一下@JakobBowyer的回答中提到的“多进程处理”部分。
这是一个很好的例子,属于一种叫做“尴尬的并行”的问题,因为代码的主要部分并不会改变外部的任何状态。它只是读取输入,进行计算,然后保存结果。
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的一样,不过我们不是在单线程的循环中运行它,而是把它放在一个函数里,通过多进程的map函数把它分散到多个核心上去处理。
你想要用PIL(Python Imaging Library),它可以轻松做到这一点。
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)))
如果你非常需要速度,那就可以考虑使用线程、进程,或者换一种语言来实现。
我想找点乐子,所以对上面提到的各种方法以及我自己的一些想法进行了性能测试。
我收集了1000张高分辨率的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))
结果:170秒
方法2 - 顺序使用ImageMagick,单次加载和连续调整大小
这个方法还是顺序的,但稍微聪明一点。每张图片只读取一次,然后将加载的图片缩小三次,保存为三种不同的分辨率。改进之处在于每张图片只读取一次,而不是三次。
#!/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))
结果: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))
结果:8秒
方法5 - 顺序使用PIL
这个方法是为了对应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:
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回答的改进,图片只加载一次,然后缩小三次,而不是每次都重新加载来生成新的分辨率。
#!/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秒