Pythonライブラリ、画像読み込み速度比較

# はじめに

最近、大量の高画質な画像を処理するようになり、読み込み速度すら気になってきました。
そこで、主な画像読み込み関数が含まれるPythonライブラリを使用して、読み込み度比較をします。

ネットの海を探したら、速度比較は出てくるのですが、画像の拡張子ごとで比較しているものがなかったので、画像拡張子含めて時間を計測してみたいと思います。できれば、PNGが早く読み込めるライブラリがあるといいな。

### 使用するライブラリ

| Library        | Version |
|:---------------|--------:|
| OpenCV *1      | 4.5.5   |
| Pillow         | 9.0.1   |
| Scikit-image   | 0.20.0  |
| matplotlib     | 3.7.1   | 
| imageio        | 2.27.0  |
| torchvision *2 | 0.15.1+cu118 |
| Lycon *2       | 0.2.0   |

*1 OpenCVは、ソースコートを色々なオプションをオンにしてビルドして使用しているので、pipなどで入れると速度が違う可能性があります
*2 対応しているJPEGとPNGのみ計測します

### 使用するPCのスペック

| 項目    | スペック |
|:------:|:-------:|
| CPU    | 12th Gen Intel® Core™ i7-12700 × 20 | 
| Memory | 32GB |
| GPU    | GeForce RTX 3080 |
| OS     | Ubuntu 22.04 LTS |
| Python | 3.10.6 |
| Numpy  | 1.24.2 |

### 比較する画像

比較する画像は、スマートフォンで撮影したJPEG画像(4032x2272)をGIMPで他の拡張子にエクスポートして、作成しました。
※写っているのはホロライブのグッズです。

![DSC_3407.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104675/317f32e0-5530-ba72-d573-397a4411d355.jpeg)

比較する画像拡張子の一覧はこちら。

| 拡張子 | 圧縮       | 上図のサイズ | 
|:-----:|:---------:|-------:|
|  bmp  | 無圧縮     | 27.5MB |
|  jpg  | 非可逆圧縮  |  3.4MB |
|  png  | 可逆圧縮   | 11.6MB |
| tiff  | 無圧縮     | 27.5MB |

各拡張子の違いについては、下記のサイトを参照してください。

https://www.itra.co.jp/webmedia/image-fomat.html

# 速度比較コード

画像を10, 100, 1000, 2000, 3000回と連続で読み込みます。
OpenCV以外の読み込みに関しては、画像読み込み後にOpenCVで扱えるフォーマットに変換するまでを読み込み時間とします。

```python:test.py
# coding:utf-8

import time
import numpy as np
import cv2
from PIL import Image
import matplotlib.image as mpimg
import skimage.io as io
import imageio
import tensorflow as tf
import lycon
import pandas as pd
from torchvision.io import read_image

def imreader(method:str, img_name):
    img = None
    if method is "opencv":
        img = cv2.imread(img_name)
    elif method is "pillow":
        buf = np.array(Image.open(img_name), dtype=np.uint8)
        img = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    elif method is "matplot":
        buf = mpimg.imread(img_name)
        img = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    elif method is "skimage":
        buf = io.imread(img_name)
        img = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    elif method is "imageio":
        buf = imageio.v2.imread(img_name)
        img = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    elif method is "keras":
        buf = tf.keras.utils.load_img(img_name)
        buf = tf.keras.utils.img_to_array(buf)
        img = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    elif method is 'lycon':
        if '.png' in img_name or '.JPG' in img_name:
            buf = lycon.load(img_name)
            img = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    elif method is "torch":
        if '.png' in img_name or '.JPG' in img_name:
            buf1 = read_image(path=img_name)
            buf2 = buf1.permute(1, 2, 0).to('cpu').detach().numpy().copy()
            img = cv2.cvtColor(buf2, cv2.COLOR_RGB2BGR)
    return img

def main():

    libraries = ["torch", "lycon"] #["opencv", "pillow", "matplot", "skimage", "imageio", "lycon"]
    read_num  = [10, 20, 1000, 2000, 3000]
    extension = ['tiff', 'png', 'JPG', 'bmp']
    img_name = "data/DSC_3407"

    index_list = [f"{_exe}:{_method}" for _method in libraries for _exe in extension]

    df = pd.DataFrame(index=index_list, columns=read_num)

    # 画像拡張子別
    for _exe in extension:
        _name = f"{img_name}.{_exe}"
        # ライブラリ
        for _method in libraries:
            # 読み込み回数
            for r in read_num:
                st = time.time()
                for i in range(r):
                    img = imreader(_method, _name)
                et = time.time()
                outstr = f"{_exe} {_method}  {r:4d}: {(et-st):.2f}[s]"
                print(outstr)
                df.loc[f"{_exe}:{_method}", r] = (et -st)
    
    df.sort_index(inplace=True)
    df.to_csv('output.csv')
    print(df.to_markdown())

if __name__=="__main__":
    main()
```

# 結果

各読み込み回数ごとの実測値を表にまとめます。単位は、秒です。

| Extension | library   | 読み込み回数-> |   10 |   100 |   1000 |   2000 |   3000 |
|:---------:|:----------|:------|-----:|------:|-------:|-------:|-------:|
| tiff      | skimage   | tiff  | 0.10 |  0.87 |   8.68 |  17.42 |  26.25 |
|           | pillow    | tiff  | 0.27 |  2.74 |  27.39 |  54.71 |  82.13 |
|           | opencv    | tiff  | 0.13 |  1.24 |  12.31 |  24.68 |  37.21 |
|           | matplot   | tiff  | 0.28 |  2.83 |  28.36 |  56.74 |  84.94 |
|           | imageio   | tiff  | 0.10 |  0.89 |   8.84 |  17.61 |  26.47 |
| png       | skimage   | png   | 2.22 | 22.40 | 223.75 | 447.92 | 670.06 |
|           | pillow    | png   | 2.24 | 22.33 | 223.79 | 453.66 | 680.40 |
|           | opencv    | png   | 1.86 | 18.39 | 184.40 | 367.79 | 550.46 |
|           | matplot   | png   | 2.65 | 26.69 | 266.39 | 532.85 | 798.22 |
|           | imageio   | png   | 2.27 | 22.94 | 229.66 | 459.87 | 690.42 |
|           | torchvison| png   | 2.00 |  3.85 | 190.83 | 379.48 | 572.58 |
|           | Lycon     | png   | 1.90 |  3.72 | 184.89 | 368.64 | 554.95 |
| jpg       | skimage   | jpg   | 0.58 |  5.85 |  58.38 | 116.24 | 175.22 |
|           | pillow    | jpg   | 0.55 |  5.50 |  55.16 | 110.82 | 165.92 |
|           | opencv    | jpg   | 0.41 |  4.05 |  40.22 |  80.53 | 120.98 |
|           | matplot   | jpg   | 0.59 |  5.84 |  58.36 | 116.68 | 175.12 |
|           | imageio   | jpg   | 0.59 |  5.94 |  58.96 | 117.98 | 177.08 |
|           | torchvison| jpg   | 0.38 |  0.77 |  38.27 |  76.60 | 114.81 |
|           | Lycon     | jpg   | 0.38 |  0.75 |  37.46 |  74.95 | 111.14 |
| bmp       | skimage   | bmp   | 0.31 |  2.92 |  29.16 |  58.87 |  89.01 |
|           | pillow    | bmp   | 0.36 |  3.63 |  31.80 |  57.59 |  86.39 |
|           | opencv    | bmp   | 0.08 |  0.62 |   6.03 |  12.02 |  18.10 |
|           | matplot   | bmp   | 0.30 |  3.03 |  30.19 |  60.40 |  90.56 |
|           | imageio   | bmp   | 0.30 |  2.99 |  29.64 |  58.76 |  92.12 |

## 3000回の読み込みの比較

太字が各拡張子ごとの最速のライブラリ

| library   | tiff      | png        | jpg        | bmp       | 
|:----------|----------:|-----------:|-----------:|----------:|
| skimage   | **26.25** | 670.06     | 175.22     | 89.01     |
| pillow    | 82.13     | 680.40     | 165.92     | 86.39     |
| opencv    | 37.21     | **550.46** | 120.98     | **18.10** |
| matplot   | 84.94     | 798.22     | 175.12     | 90.56     |
| imageio   | 26.47     | 690.42     | 177.08     | 92.12     |
| torchvison| -----     | 572.58     | 114.81     | -----     |
| Lycon     | -----     | 554.95     | **111.14** | -----     |

### tiff

tiffは、scikit-imageとimageioの勝利でした。
OpenCVよりも速いのが驚きでした。

![tiff_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104675/0307fc30-d1c0-e755-07e7-2192dc4a89d6.png)

### PNG

pngはOpenCVが圧勝するかと思いきや、Lyconがほぼ同じぐらいの速度を出しています。
PNGの読み込み速度は、ここが限界なのかもしれません。

![png_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104675/91ea8cde-e0f1-4744-e0e0-afa178eac102.png)

### JPEG(jpg)

JPEGはなんと、Lyconが優勝でした。さすが高速化ライブラリですね。
torchvisionも速いので、学習系で使用する際はtorchvisionで読み込んで使った方が、変換処理が必要ない分だけ更に速いかもしれません。

![jpg_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104675/a08e3320-8aa9-6de3-04e0-6904748d9d06.png)

### Bitmap(bmp)

Bitmapは、OpenCVが優勝でした。他のライブラリはそれほど差がない結果となりました。

![bmp_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104675/d29ce739-1925-77b9-55bf-bc51764c3b29.png)

# 結論

+ OpenCVをビルドして使用するのがほぼ最強
+ LyconはPNG,JPEGの読み込みではかなり使える
+ torchvisionも速いので、学習系ではtorchvisionを使用する方が速い可能性あり

# 参考サイト

https://developers.10antz.co.jp/archives/1505

https://qiita.com/derodero24/items/f22c22b22451609908ee

https://qiita.com/tand826/items/b8a7548c4c0f48f8617a

https://hyper-pigeon.hatenablog.com/entry/2020/07/22/231055

http://blog.livedoor.jp/dividend_tomtom/archives/10952549.html