【imageio| python】複数の画像データ読み込みをマルチスレッドで高速化

記事の適用シーン

大量の画像データを読み込む際に一枚ずつ処理をすると時間がかかりますので、

マルチスレッド化することで時間短縮を試みます。

また、画像縮小や簡単な画像処理(反転、回転、シフト等)もマルチスレッド化できますので、その際にはさらにマルチスレッドの効果が高まります。

筆者の環境で640枚のjpgファイルを読み込んだ時は

760ms→430ms ▲57%

ほどの短縮が可能になりました。

bmpやpng等の重いファイル、画像処理が追加されるとさらに効果が出ると思われます。

概要

処理のイメージ図は以下のようになります。

処理の流れとしては

  1. 画像ファイル名のリストを作成
  2. スレッド数分だけリストを分ける
  3. マルチスレッドで画像読み込み開始し各スレッドが画像をキューへ格納
  4. キューから画像を取り出しリストへ格納

です。

前提

imageio 2.9.0

numpy 1.18.5

opencv-python 4.4.0.42

実装(すぐに使ってみたい方)

実装して関数化したものを掲載します。

コピペして使ってみてください。

import imageio
import cv2
import numpy as np

import os

import threading

def imread_worker(input_path, filelist, q_img):
    for fi1 in filelist:
        img = imageio.imread(os.path.join(input_path, fi1))
        q_img.put([fi1, img])

def imread_multi_thread(input_path, workers=8):
    q_img = queue.Queue()
    imgs = []
    filelist = os.listdir(input_path)
    imgs_per_thread = len(filelist) // workers
    quotient = len(filelist) % workers
    filelists = [filelist[imgs_per_thread * i:imgs_per_thread * (i + 1)] for i in range(workers)]
    quot_list = filelist[imgs_per_thread * workers:imgs_per_thread * workers + quotient]
    filelists.append(quot_list)

    threads = []
    for fl in filelists:
        t = threading.Thread(target=imread_worker, args=(input_path, fl, q_img))
        t.setDaemon(True)
        t.start()
        threads.append(t)

    filelist = []

    for thread in threads:
        thread.join()

    while not q_img.empty():
        temp = q_img.get()
        filelist.append(temp[0])
        imgs.append(temp[1])

    return imgs, filelist

使い方

・関数の引数の説明

imread_multi_thread(画像パス, スレッド数)

・出力

画像numpy配列が格納されたリスト, 画像ファイル名のリスト

import queue

#マルチスレッドのためのキューの定義
q_img = queue.Queue()

#8スレッドで画像読み込み
img_list, img_path_list = imread_multi_thread(image_path, q_img, 8)

実装解説

①まずはスレッド数分だけファイル名のリストを分割します

#ファイル名のリストを作成
filelist = os.listdir(input_path)

#スレッド数で割った商計算
imgs_per_thread = len(filelist) // workers

#スレッド数で割った余りを計算
quotient = len(filelist) % workers

#余り以外のファイル名を分割して1つのリストにする
filelists = [filelist[imgs_per_thread * i:imgs_per_thread * (i + 1)] for i in range(workers)]
#余りのファイル名のリストを作成
quot_list = filelist[imgs_per_thread * workers:imgs_per_thread * workers + quotient]
#合体させて1つのリストにする
filelists.append(quot_list)

②スレッド数分だけスレッドを立てて処理を開始し、完了するまで待ちます

#スレッドを格納するリスト
threads = []
#分割したファイル名のリスト分だけfor文を回す
for fl in filelists:
    #マルチスレッド用の画像読み込み関数(imread_worker)のスレッドを立てる
    t = threading.Thread(target=imread_worker, args=(input_path, fl, q_img))
    #デーモン化
    t.setDaemon(True)
    #スレッドの開始
    t.start()
    #スレッドリストへ格納
    threads.append(t)
#全てのスレッドが終わるまで待つ
for thread in threads:
    thread.join()

スレッド処理用の画像読み込み関数です。

ファイル名のリスト分だけ画像を読み込みキューへ格納するようになっています。

def imread_worker(input_path, filelist, q_img):
    for fi1 in filelist:
        img = imageio.imread(os.path.join(input_path, fi1))
        q_img.put([fi1, img])

③キューからデータを取り出しリストへ格納

    #キューから取り出した後の格納用の出力リスト定義
    imgs = []
    filelist = []
        
    #キューが空になるまで取り出し出力リストへ格納
    while not q_img.empty():
        temp = q_img.get()
        filelist.append(temp[0])
        imgs.append(temp[1])

画像処理も追加したいとき

imread_workerに処理を適宜追加してください。

以下の例は画像リサイズを行った例です。

def imread_worker(input_path, filelist, q_img):
    for fi1 in filelist:
        img = imageio.imread(os.path.join(input_path, fi1))
        img = cv2.resize(img, (224, 224))
        q_img.put([fi1, img])

最後に

今回は画像読み込みの高速化について紹介しました。

使いどころはそこまで多くありませんが、非常に大量の画像を読み込みたいときの時間短縮として使えますので試してみてください。

コメント

タイトルとURLをコピーしました