Pythonマルチプロセスで画像読み込みを高速化(mp.Queue)
マルチプロセス、画像読み取りの高速化、マルチプロセスでの画像の順序付け読み取り、Python、マルチプロセッシング、multiprocessing.Queue、opencv-python
-
記事の構成
- 高速使用、マルチプロセス読み取り画像 (簡易バージョン)
- 読み取り速度に影響を与えるボトルネック (CPU とディスク)
- 多工程画像読取(完全版):整然とした読取、画像検査
1.素早く使える、マルチプロセスで画像を読み取る(簡易版)
太字の黒の場所はフォルダー パスです。ご自身で変更してください。画像は jpg 形式です。直接コピーして実行し、100% のディスク使用率を体験してください。
ここでは、python3 に付属する multiprocessing.Queue を使用して、マルチプロセスの実現を完了しています。マルチプロセスについてある程度理解したい場合は、最初に私の他の記事を読んでください (まだ書いていませんが、誰かが書いています)書くように促されました)——使いやすいマルチプロセッシング
import os
import multiprocessing as mp
import cv2
import numpy as np
'''
2018-07-05 Yonv1943 show file images, via multiprocessing
2018-09-04 use multiprocessing for loading images
2018-09-05 add simplify
'''
def img_load(queue, queue_idx__img_paths):
while True:
idx, img_path = queue_idx__img_paths.get()
img = cv2.imread(img_path) # Disk IO
queue.put((img, idx, img_path))
def img_show(queue, window_name=''): # img_show_simplify
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
while True:
img, idx, img_path = queue.get()
cv2.imshow(window_name, img)
cv2.waitKey(1)
def run():
src_path = 'F:/url_get_image/ftp.nnvl.noaa.gov_GER_2018'
img_paths = [os.path.join(src_path, f) for f in os.listdir(src_path)]
mp.set_start_method('spawn')
queue_img = mp.Queue(8)
queue_idx__img_path = mp.Queue(len(img_paths))
[queue_idx__img_path.put(idx__img_path) for idx__img_path in enumerate(img_paths)]
processes = list()
processes.append(mp.Process(target=img_show, args=(queue_img,)), )
processes.extend([mp.Process(target=img_load, args=(queue_img, queue_idx__img_path))
for _ in range(3)])
[setattr(process, "daemon", True) for process in processes]
[process.start() for process in processes]
[process.join() for process in processes]
if __name__ == '__main__':
run()
2.読み取り速度に影響を与えるボトルネック (CPU とディスク)
複数のプロセスを開いてディスクからファイルを読み取り、 CPU が画像形式を解析し、画像を numpy ndarray に変換してメモリに保存します。
読み込みたい画像が空きメモリに見つからない場合、プロセスはディスクからの読み込みを開始しますが、このときディスクがボトルネックとなって読み込みが制限されます。
オペレーティング システムに Win10 と同様の Superfetch サービスがある場合、読み取りの初期段階で、読み取るファイルは実際にはすでにメモリ内にありますが、この時点でシステムのボトルネックは CPU にあり、CPU は変換する必要があります。対応する画像形式を ndarray に変換
以下の図の CPU 使用率とディスク使用率を見ると、初期段階では CPU がフルロードされ、後半ではディスクがフルロードされていることがわかります。
メモリに読み込んだndarrayをメモリに格納し続けなかったので、メモリ使用量は増加しませんでした。
3.画像のマルチプロセス読み取り (完全版)
コードの完全版をGitHub に置きました: DEMO_images_load_order_mp_cv2.py (GitHub のコードは時々変更されます。ファイル名が変更されるため、リンクが間違ったものを指す場合があります。変更するには私に連絡してください。もちろん、以下のコードを直接見ることもできます)
完全版では以下が追加されます。
- マルチプロセスでの順序付き読み取り: 順序付けされた配列を維持し、画像を順番に読み取ります。
- 画像種類チェック:画像が正しく読み取れるか、画像が完成しているかチェック
- 画像拡張子チェック: jpg などの一致するファイル タイプのみを読み取ります
import os
import multiprocessing as mp
import cv2
import numpy as np
'''
2018-07-05 Yonv1943 show file images, via multiprocessing
2018-09-04 use multiprocessing for loading images
2018-09-05 add simplify
'''
def img_load(queue, queue_idx__img_paths):
while True:
idx, img_path = queue_idx__img_paths.get()
img = cv2.imread(img_path) # Disk IO
queue.put((img, idx, img_path))
def img_show(queue, window_name=''): # check images and keep order
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
import bisect
idx_previous = -1
idxs = list()
queue_gets = list()
while True:
queue_get = queue.get()
idx = queue_get[1]
insert = bisect.bisect(idxs, idx) # keep order
idxs.insert(insert, idx)
queue_gets.insert(insert, queue_get)
# print(idx_previous, idxs)
while idxs and idxs[0] == idx_previous + 1:
idx_previous = idxs.pop(0)
img, idx, img_path = queue_gets.pop(0)
if not isinstance(img, np.ndarray): # check images
os.remove(img_path)
print("| Remove no image:", idx, img_path)
elif not (img[-4:, -4:] - 128).any(): # download incomplete
os.remove(img_path)
print("| Remove incomplete image:", idx, img_path)
else:
try:
cv2.imshow(window_name, img)
cv2.waitKey(1)
except error as e:
print("|Error:", e, idx, img_path)
def run():
src_path = 'F:/url_get_image/ftp.nnvl.noaa.gov_GER_2018'
img_paths = [os.path.join(src_path, f) for f in os.listdir(src_path) if f[-4:] == '.jpg']
print("|Directory perpare to load:", src_path)
print("|Number of images:", len(img_paths), img_paths[0])
mp.set_start_method('spawn')
queue_img = mp.Queue(8)
queue_idx__img_path = mp.Queue(len(img_paths))
[queue_idx__img_path.put(idx__img_path) for idx__img_path in enumerate(img_paths)]
processes = list()
processes.append(mp.Process(target=img_show, args=(queue_img,)), )
processes.extend([mp.Process(target=img_load, args=(queue_img, queue_idx__img_path))
for _ in range(3)])
[setattr(process, "daemon", True) for process in processes]
[process.start() for process in processes]
[process.join() for process in processes]
if __name__ == '__main__':
run()
4. マルチプロセスでの順序付けされた読み取り:(強調)
読み込んだタスクリストを各CPUに分配する際、CPUの読み込み時間の違いにより画像の順番が若干崩れるため、画像をソートする必要がある。
画像を出力する CPU_0 がソートを担当し、最後に出力した画像が 01 の場合、次の出力画像は 02 になります。他の CPU から画像 04 と 05 を受け取った場合は、出力せずに一時的に保存します。
画像02を受信した後、画像を出力し、次の画像がなくなるまで待ってから、CPUから新しい画像を受け取ります。
4.1 画像タイプのチェック:
大量の画像を読み取る場合、エラーによるプログラムの中断を避けるために型チェックが必要です。以下に 2 つのチェックを示します。
- 画像が正しく読み込まれたかどうかを確認します (Python の組み込み関数 ininstance(object, object) を使用)
- 画像が完全にダウンロードされているかどうかを確認します(画像が完全にダウンロードされていない場合、画像の下のRGB値は固定値です)
- 不明なエラーをキャッチする
実際、開けない写真の場合、削除するのではなく移動し、間違った写真を他のフォルダーに移動するのがより良い対処方法です。os.remove() の代わりに shutil.rmtree() を使用してください
if not isinstance(img, np.ndarray): # check images
os.remove(img_path)
print("| Remove no image:", idx, img_path)
elif not (img[-4:, -4:] - 128).any(): # download incomplete
os.remove(img_path)
print("| Remove incomplete image:", idx, img_path)
else:
try:
cv2.imshow(window_name, img)
cv2.waitKey(1)
except error as e:
print("|Error:", e, idx, img_path)
4.2 イメージのサフィックス名を確認します。
サフィックスを確認することでフォルダ内の他のファイルを回避できます 読み込む画像形式は状況に合わせて変更してください 除外されないように「jpg」を読み込みました opencv-python の cv2.imread() は jpg をサポートしていますjpeg、png、bmp、その他の形式の読み取り
src_path = 'F:\\url_get_image\\ftp.nnvl.noaa.gov_GER_2018' # better in winOS
img_paths = [os.path.join(src_path, f) for f in os.listdir(src_path) if f[-4:] == '.jpg']
私が読んだ画像は、米国海洋大気局からダウンロードされています。私の他の記事 (畳み込みネットワークを使用して衛星画像の雲を削除する) では、ディスク イメージ ファイルを高速化するために複数のプロセスを使用する必要もあります。読んでください。ちなみに、マルチプロセスで画像を読み取るコードをインターネットに送信して、誰もが通信できるようにしたからです。