🐍

[Python] Pillowより10倍高速に画像をリサイズする

2024/06/23に公開

はじめに

この記事では、cykooz.resizerを使って高速に画像をリサイズする方法を紹介します。

cykooz.resizer

cykooz.resizerは画像のリサイズを行うための Python パッケージです。
Rust crate のfast_image_resizeを pyo3 を使ってバインディングしています。
fast_image_resize は SIMD を活用することで高速に画像のリサイズを実現しています。

ベンチマーク例

これ以降は公式の README を引用しながら紹介していきます。まずはベンチマークの例です。

環境

  • CPU: AMD Ryzen 9 5950X
  • RAM: DDR4 4000 MHz
  • Ubuntu 22.04 (linux 6.5.0)
  • Python 3.10
  • Rust 1.78.0
  • cykooz.resizer = "3.0"
  • Pillow = "10.3.0"

RGBA 4928x3279 => 852x567

nasa-4928x3279.pngの画像を対象に、各アルゴリズムでリサイズを行った場合の処理時間を比較しています。最適化オプションなしでも Pillow より高速に処理できており、SSE4.1 や AVX2 を使うことでさらに高速になります。

Package (time in ms) nearest bilinear lanczos3
Pillow 0.93 104.77 191.08
cykooz.resizer 0.20 28.50 56.33
cykooz.resizer - sse4_1 0.20 12.28 24.31
cykooz.resizer - avx2 0.20 8.58 21.62

grayscale (U8) 4928x3279 => 852x567

先ほどの画像をグレースケールに変換してリサイズを行った場合の処理時間を比較しています。RGBA の時と同様に最適化オプションなしでも Pillow より高速に処理できており、SSE4.1 や AVX2 を使うことでさらに高速になります。

Package (time in ms) nearest bilinear lanczos3
Pillow 0.25 20.62 51.62
cykooz.resizer 0.18 6.25 13.06
cykooz.resizer - sse4_1 0.18 2.12 5.75
cykooz.resizer - avx2 0.18 1.96 4.41

サポートしている画像形式と最適化オプション

この記事を執筆した 2024/06 現在では、cykooz.resizer は以下の画像形式と最適化オプションをサポートしています。

Format Description SSE4.1 AVX2 Neon
U8 One u8 component per pixel (e.g. L) + + +
U8x2 Two u8 components per pixel (e.g. LA) + + +
U8x3 Three u8 components per pixel (e.g. RGB) + + +
U8x4 Four u8 components per pixel (e.g. RGBA, RGBx, CMYK) + + +
U16 One u16 components per pixel (e.g. L16) + + +
U16x2 Two u16 components per pixel (e.g. LA16) + + +
U16x3 Three u16 components per pixel (e.g. RGB16) + + +
U16x4 Four u16 components per pixel (e.g. RGBA16, RGBx16, CMYK16) + + +
I32 One i32 component per pixel - - -
F32 One f32 component per pixel - - -

サポートしているリサイズアルゴリズム

cykooz.resizer は以下のリサイズアルゴリズムをサポートしています。

  • Nearest
  • box
  • bilinear
  • catmull_rom
  • mitchell
  • gaussian
  • lanczos3

サンプルコード

cykooz.resizer を使って画像をリサイズするコード例は以下のようになります。

from PIL import Image

from cykooz.resizer import FilterType, ResizeAlg, Resizer, ResizeOptions


resizer = Resizer()
dst_size = (255, 170)
dst_image = Image.new('RGBA', dst_size)

for i in range(1, 10):
    image = Image.open('nasa_%d-4928x3279.png' % i)
    resizer.resize_pil(image, dst_image)
    dst_image.save('nasa_%d-255x170.png' % i)

# Resize using a bilinear filter and ignoring an alpha channel.
image = Image.open('nasa-4928x3279.png')
resizer.resize_pil(
    image,
    dst_image,
    ResizeOptions(
        resize_alg=ResizeAlg.convolution(FilterType.bilinear),
        use_alpha=False,
    )
)

benchmark 実行

cykooz.resizer のレポジトリにベンチマーク用のスクリプトが用意されているので、それを使って実際にベンチマークを実行してみます。

環境

  • python_version: 3.12.2.final.0 (64 bit)
  • cpuinfo_version: [9, 0, 0]
  • cpuinfo_version_string: 9.0.0
  • arch: ARM_8
  • bits: 64
  • count: 8
  • arch_string_raw: arm64
  • brand_raw: Apple M1 Pro
  • cykooz.resizer: 3.0
  • Pillow: 10.3.0

実行結果

python -m pytest -s tests/test_benchmark.py
Package (time in ms) nearest bilinear lanczos3
Pillow 0.95 52.84 88.04
cykooz.resizer 0.41 31.37 73.15
cykooz.resizer - neon 0.43 20.01 55.00
Pillow U8 0.33 13.95 21.37
cykooz.resizer U8 0.18 7.71 16.42
cykooz.resizer U8 - neon 0.26 3.99 13.26

結果を見ると、Pillow よりも cykooz.resizer の方が高速にリサイズできていることがわかります。nearest では最適化オプションを使ってない方が良い結果となっていますが、bilinear と lanczos3 では最適化オプションを使った方が高速にリサイズできています。
公式の README との実行環境の差もあるためか、10 倍高速にリサイズはできませんでしたが、早い場合は 3 倍程度高速にリサイズできました。

まとめ

この記事では、cykooz.resizer を使って高速に画像をリサイズする方法を紹介しました。
公式のベンチマークでは 10 倍高速にリサイズされていましたが、実際に試してみたところ良い場合で 3 倍程度高速にリサイズできました。

Discussion