在 Python 中从单个大图像创建多个缩略图的最快方法

ens*_*are 3 python imagemagick image-processing python-3.x imagemagick-convert

我有一个大图像库(8000x6000px ~13mb),我想为其生成多个较小尺寸的缩略图,宽度为 3000px、2000px、1000px、500px、250px 和 100px。

源图像存储在平面文件中,生成的缩略图也将存储在平面文件中。

我一直在考虑在 Python 中执行此操作的最佳方法,这些是我立即想到的潜在问题:

  • 从源图像生成每个缩略图是否有意义,或者我可以从任何稍大的缩略图创建较小的缩略图吗?例如,8000px -> 3000px, 3000px --> 2000px, 1000px -> 500px, 等等...这样会不会跑得更快?
  • 在生成缩略图之前将源图像加载到内存中是否有意义?
  • 我应该使用 ImageMagick 吗?从命令行还是通过 API?
  • 有什么方法可以利用GPU?
  • 在这种情况下,多线程有意义吗?

优化缩略图生成时还有其他要记住的事情吗?非常感谢您开始使用示例代码。谢谢你。

Mar*_*ell 5

我制作了一些图像并进行了一些测试,以便您可以看到对各种技术性能的影响。

我制作的图像包含随机的、难以压缩的尺寸和文件大小的数据以匹配你的,即

convert -size 8000x6000 xc:gray +noise random -quality 35 image.jpg
Run Code Online (Sandbox Code Playgroud)

然后,ls像这样给出 13MB:

-rw-r--r--  1 mark  staff    13M 23 Aug 17:55 image.jpg
Run Code Online (Sandbox Code Playgroud)

我制作了 128 个这样的随机图像,因为它可以被我机器上的 8 个 CPU 内核很好地整除 - 稍后参见并行测试。

现在的方法...

方法一

这是一种天真的方法 - 您只需一个接一个地创建您要求的所有文件。

#!/bin/bash
for f in image*jpg; do
   for w in 3000 2000 1000 500 250 100; do
      convert $f -resize ${w}x res_${f}_${w}.jpg
   done 
done
Run Code Online (Sandbox Code Playgroud)

时间:26分46秒

方法二

在这里,我们只读取每个图像一次,但从一个输入图像生成所有输出大小,而且速度要快得多。

#!/bin/bash
for f in image*jpg; do
   convert $f -resize 3000x -write res_${f}_3000.jpg \
              -resize 2000x -write res_${f}_2000.jpg \
              -resize 1000x -write res_${f}_1000.jpg \
              -resize 500x  -write res_${f}_500.jpg  \
              -resize 250x  -write res_${f}_250.jpg  \
              -resize 100x  res_${f}_100.jpg
done
Run Code Online (Sandbox Code Playgroud)

时间:6分17秒

方法三

在这里,我们预先建议 ImageMagick 我们将需要的最大图像仅为 3000x2250 像素,因此它可以使用更少的内存并读取更少的 DCT 级别并执行更少的 I/O。这称为“负载收缩”。

#!/bin/bash
for f in image*jpg; do
   convert -define jpeg:size=3000x2250 $f            \
              -resize 3000x -write res_${f}_3000.jpg \
              -resize 2000x -write res_${f}_2000.jpg \
              -resize 1000x -write res_${f}_1000.jpg \
              -resize 500x  -write res_${f}_500.jpg  \
              -resize 250x  -write res_${f}_250.jpg  \
              -resize 100x  res_${f}_100.jpg
done
Run Code Online (Sandbox Code Playgroud)

时间:3分37秒

顺便说一句,为了演示当您预先告诉 ImageMagick 您需要一个图像有多大时减少的时间、I/O 和内存需要,比较这两个命令,两者都读取您的 8000x6000、13MB 之一图像并生成相同的缩略图:

/usr/bin/time -l convert image.jpg -resize 500x result.jpg 2>&1 | egrep "resident|real"        
1.92 real         1.77 user         0.14 sys
415727616  maximum resident set size
Run Code Online (Sandbox Code Playgroud)

即 415 MB 和 2 秒

/usr/bin/time -l convert -define jpeg:size=500x500 image.jpg -resize 500x result.jpg 2>&1 | egrep "resident|real"

0.24 real         0.23 user         0.01 sys
23592960  maximum resident set size
Run Code Online (Sandbox Code Playgroud)

即 23 MB 和 0.2 秒 - 并且输出图像具有相同的内容和质量。

方法四

在这里,我们全力以赴,使用 GNU Parallel 以及所有上述技术,让您的 CPU、风扇和功耗疯狂!!!

#!/bin/bash
for f in image*jpg; do
   cat<<EOF
convert -define jpeg:size=3000x2250 $f          \
              -resize 3000x -write res_${f}_3000.jpg \
              -resize 2000x -write res_${f}_2000.jpg \
              -resize 1000x -write res_${f}_1000.jpg \
              -resize 500x  -write res_${f}_500.jpg  \
              -resize 250x  -write res_${f}_250.jpg  \
              -resize 100x  res_${f}_100.jpg
EOF
done | parallel
Run Code Online (Sandbox Code Playgroud)

时间:56秒

总之,我们可以通过避免不必要地读取图像并为每个输入执行尽可能多的输出,通过预先告诉 ImageMagick 它需要读取多少输入图像并使用 GNU 来将处理时间从 27 分钟减少到 56 秒并行以保持所有可爱的 CPU 内核忙碌。哈。