Python库中快速导入和裁剪JPEG的方法

6
我有一个Python应用程序,导入了200k+张图片,然后对其进行剪裁,并将剪裁后的图像呈现给pyzbar来解码条形码。剪裁有助于识别多个条形码,并且假定在提供较小的图像时,pyzbar更快。

目前我正在使用Pillow来导入和剪裁图像。

平均来说,导入和剪裁一张图像需要262毫秒,而pyzbar则需要8毫秒。

典型运行大约需要21小时。

我想知道除Pillow之外的其他库是否可以在加载/剪裁方面提供实质性改进。理想情况下,该库应可用于MacOS,但我也可以在虚拟Ubuntu机器上运行整个过程。

我正在开发一个可以并行处理的版本,这将是一个很大的改进,但如果我能从不同的库中获得25%或更高速度的提升,我也会添加它们。

Python并不是高性能计算的首选语言。 - Scott Hunter
我想知道除了Pillow库之外,是否还有其他库可以在加载/裁剪方面提供实质性的改进。你可以尝试使用不同的库进行测试。 - Trilarion
你也可以分别计时加载和裁剪操作。我猜裁剪比加载快得多,这将把整个问题简化为最快的图像加载方式。为了加速实验,硬件IO的性能也很重要。 - Trilarion
1
@Trilarion 至少90%的时间用于加载和解压缩JPEG,然后大部分数据在裁剪中被丢弃。我这样表达是希望也许有人知道一种算法,通过提前使用裁剪信息来节省解压缩时间。我认识到这远非易事,但这确实是我看到的唯一显著改进的方法。 - WesR
1
pyvips 只会解压你最终使用的 JPEG 的部分。Mark 在下面提供了一些不错的示例代码。 - jcupitt
显示剩余4条评论
2个回答

8
由于您没有提供样本图像,我制作了一个大小为1.1MB,尺寸为2544x4200的虚拟文件,并在答案末尾提供。我复制了该图像1000次,并处理了每个基准测试的1000个图像。
由于您只在评论区提供了代码,所以我对其进行了格式化并尽可能地优化了它。我还将其放入循环中,以便可以在Python解释器的一次调用中处理多个文件-当您有20000个文件时,这变得非常重要。
效果如下:
#!/usr/bin/env python3

import sys
from PIL import Image

# Process all input files so we only incur Python startup overhead once
for filename in sys.argv[1:]:
   print(f'Processing: {filename}')
   imgc = Image.open(filename).crop((0, 150, 270, 1050))

我怀疑我可以使用以下方法使其更快:

  • GNU Parallel,和/或
  • pyvips

这是您代码的pyvips版本:

#!/usr/bin/env python3

import sys
import pyvips
import numpy as np

# Process all input files so we only incur Python startup overhead once
for filename in sys.argv[1:]:
   print(f'Processing: {filename}')

   img = pyvips.Image.new_from_file(filename, access='sequential')
   roi = img.crop(0, 150, 270, 900)
   mem_img = roi.write_to_memory()

   # Make a numpy array from that buffer object
   nparr = np.ndarray(buffer=mem_img, dtype=np.uint8,
                   shape=[roi.height, roi.width, roi.bands])

这里是结果:

顺序原始代码

./orig.py bc*jpg
224 seconds, i.e. 224 ms per image, same as you

并行原始代码

parallel ./orig.py ::: bc*jpg
55 seconds

并行原始代码,但尽可能传递多个文件名

parallel -X ./orig.py ::: bc*jpg
42 seconds   

顺序pyvips

./vipsversion bc*
30 seconds, i.e. 7x as fast as PIL which was 224 seconds

并行 pyvips

parallel ./vipsversion ::: bc*
32 seconds

尽可能传递多个文件名的并行pyvips

parallel -X ./vipsversion ::: bc*
5.2 seconds, i.e. this is the way to go :-)

enter image description here


请注意,您可以使用homebrew在macOS上安装GNU Parallel:
brew install parallel

1
不错!你可以使用new_from_file(filename, access='sequential')来加快pyvips的速度。它只会解码裁剪区域。 - jcupitt
@jcupitt 嗯,这显著提高了顺序和parallel -X的性能,但是第二个变得更糟了...不确定出了什么问题。 - Mark Setchell
哦,好的,是的,每次调用只裁剪一张图像,开始/停止时间占主导地位。我看到使用顺序pyvips在10秒内进行了1000次裁剪,在并行pyvips中只需要2.6秒。 - jcupitt
1
@jcupitt 我想我会保留它。我想要诚实,而且它实际上说明了我在开始时关于每个图像启动一个全新的Python解释器的开销所说的内容。再次感谢您的见解。 - Mark Setchell
1
哦,没问题。如果您喜欢的话,您也可以将格式行替换为格式字符串:f'Processing: {filename}' 稍微更整洁一些。 - jcupitt
显示剩余3条评论

2
你可以看一下PyTurboJPEG,它是libjpeg-turbo的Python封装,具有极快的缩放速度(1/2、1/4、1/8),同时解码大型JPEG图像时返回的numpy.ndarray数组方便进行图像裁剪。此外,JPEG图像编码速度也很快。
from turbojpeg import TurboJPEG

# specifying library path explicitly
# jpeg = TurboJPEG(r'D:\turbojpeg.dll')
# jpeg = TurboJPEG('/usr/lib64/libturbojpeg.so')
# jpeg = TurboJPEG('/usr/local/lib/libturbojpeg.dylib')

# using default library installation
jpeg = TurboJPEG()

# direct rescaling 1/2 while decoding input.jpg to BGR array
in_file = open('input.jpg', 'rb')
bgr_array_half = jpeg.decode(in_file.read(), scaling_factor=(1, 2))
in_file.close()

# encoding BGR array to output.jpg with default settings.
out_file = open('output.jpg', 'wb')
out_file.write(jpeg.encode(bgr_array))
out_file.close()

“libjpeg-turbo”在macOS和Linux系统上的预编译二进制文件也可以在这里找到。”

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接