[Python] Pillowより10倍高速に画像をリサイズする
はじめに
この記事では、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