在 python lib 中导入和裁剪 jpeg 的快速方法

Posted

技术标签:

【中文标题】在 python lib 中导入和裁剪 jpeg 的快速方法【英文标题】:Fast way to import and crop a jpeg in python lib 【发布时间】:2019-03-04 00:28:14 【问题描述】:

我有一个 python 应用程序,它导入 200k+ 图像,裁剪它们,并将裁剪的图像呈现给 pyzbar 以解释条形码。裁剪很有帮助,因为图像上有多个条形码,并且在给定较小的图像时,大概 pyzbar 会更快一些。

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

平均而言,导入和裁剪图像需要 262 毫秒,而 pyzbar 需要 8 毫秒。

典型的运行时间约为 21 小时。

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

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

【问题讨论】:

Python 不是高性能计算的首选语言。 也许如果您发布一些代码,我们可以评论效率,并且在不知道图像大小的情况下,我们无法真正说出 262 毫秒是慢还是快,尽管直觉反应很慢。你也试过 OpenCV 甚至 ImageMagick 的批处理工具吗? “我想知道除了 Pillow 之外的库是否可以在加载/裁剪方面提供实质性改进。”您可以使用不同的库进行测试。 @KenY-N 建议我发布一些代码。这里是: imgc = Image.open(infile).crop((0, 150, 270, 1050)) 输入文件都是 2544x4200 px 的 jpg 文件,范围从 500KB 到 1.5MB 取决于有多少空白空间页。此步骤的平均时间为 250 毫秒。 @Trilarion:显然我可以。我希望有人拥有并将分享他们的经验。 【参考方案1】:

由于您没有提供示例图像,因此我制作了一个 2544x4200 尺寸、1.1MB 大小的虚拟文件,并在答案末尾提供。我制作了该图像的 1,000 个副本,并为每个基准测试处理了所有 1,000 个图像。

由于您只在 cmets 区域中提供了您的代码,因此我接受了它,对其进行了格式化并尽我所能。我还把它放在一个循环中,这样它就可以处理许多文件,只需一次调用 Python 解释器 - 当你有 20,000 个文件时,这一点变得很重要。

看起来像这样:

#!/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 并行,和/或 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 :-)


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

brew install parallel

【讨论】:

不错!您可以使用new_from_file(filename, access='sequential') 使 pyvips 更快一点。它只会打扰解码裁剪区域。 @jcupitt 太好了,谢谢!我稍后会尝试,或者明天再报告。 哦,工作方式:sequential 模式会将图像从源文件流式传输到目标内存区域。因为jpg是按需解压的,一旦需求停止,解压就放弃了。所以在这种情况下,它只会解压到第 1200 行,而忽略其他 3000 行。 @jcupitt 嗯,这显着改善了顺序和parallel -X,但使中间的更糟......不知道那里发生了什么。 哦,好的,是的,每次调用只裁剪一张图像,开始/停止时间占主导地位。我在 10 秒内看到 1000 种作物,使用顺序 pyvips,2.6 秒使用并行 pyvips。【参考方案2】:

您可以查看PyTurboJPEG,它是libjpeg-turbo 的 Python 包装器,在解码大型 JPEG 图像时具有极其快速的重新缩放(1/2、1/4、1/8),返回的 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()

用于 macOS 和 Linux 的 libjpeg-turbo 预构建二进制文件也可通过here 获得。

【讨论】:

以上是关于在 python lib 中导入和裁剪 jpeg 的快速方法的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 项目中导入和显示 csv 文件

在反应中导入和导出模块

如何在 Vue 单文件组件中导入和使用图片?

在 cassandra 中导入和导出模式

如何在 material-ui 主题中导入和使用自定义字体?

在 OpenGL 中导入和显示 .fbx 文件