ごはんと飲み物は紙一重

あんまり更新できてないです

opencv × multiprocessing がどうもうまくいかない【cv2.cvtColor】

画像処理を並列に動かしたい

研究関連ですね。

動画を読み込んで画像処理やパラメータ取得やらいろいろやって結果を取得するとなったとき、 while (cap.isOpened) しているわけです。このwhile回してる間の処理をできるなら並列にやっていきたいって感じです。

n_frame = 30
a1, a2 = [], []
output_path = "/path/to/img_output_path"

cap = cv2.VideoCapture("/path/to/video")
frame_all = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
frame_idx = [_ for _ in range(frame_all) if _%n_frame == 0]


for idx in tqdm(frame_idx):    
    cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
    _, frame = cap.read()

    img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    cv2.rectangle(img, (0, 0), (600, 100), (0, 0, 0), -1)

    keypoints, output_image = openpose.forward(img, True)

    a1.append(keypoints[0][[keypoints_dict[i] for i in get_parts_list]].ravel())
    a2.append(keypoints[1][[keypoints_dict[i] for i in get_parts_list]].ravel())

    cv2.imwrite(os.path.join(output_path, "img/{0:04d}.png".format(idx)), cv2.cvtColor(output_image, cv2.COLOR_RGB2BGR))

これを必要な箇所だけ関数化して

from multiprocessing import Pool, Manager
import multiprocessing as multi

n_frame = 30
a1, a2 = Manager().dict(), Manager().dict()
output_path = "/path/to/img_output_path"

# 動画のサンプリングフレームのインデックスを取得
cap = cv2.VideoCapture("/path/to/video")
frame_all = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
frame_idx = [_ for _ in range(frame_all) if _%n_frame == 0]

def get_features(idx):
    print("Processing to {}".format(idx))
    cap = cv2.VideoCapture("/path/to/video")
    
    cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
    _, frame = cap.read()

    img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    cv2.rectangle(img, (0, 0), (600, 100), (0, 0, 0), -1)
    
    keypoints, output_image = openpose.forward(img, True)
    
    if len(keypoints) == 2:
        a1.update({idx:keypoint[0][[keypoints_dict[i] for i in get_parts_list]].ravel()})
        a2.update({idx:keypoint[1][[keypoints_dict[i] for i in get_parts_list]].ravel()})

    cv2.imwrite(os.path.join(output_path, "img/{0:04d}.png".format(counter)), cv2.cvtColor(output_image, cv2.COLOR_RGB2BGR))
    
if __name__ == "__main__":
    with Pool(multi.cpu_count()) as p:
        p.map(get_features, frame_idx[:5], 1)

な感じでアウトプットするのですが、Jupyterだとこれがどうもうまくいかない・・・。と思ってスクリプトに変換してもだめ。

どうやらopencvcv2.cvtColor が並列実行の際にひっかかるらしいです。そんなん言われても、opencvで使う関数の中で1,2を争うくらいよく使う関数なのに君が引っかかると非常に困るんやが・・・

issueやstack overflowを見てみる。

github.com

見てみるのですが、解決しているらしい?issueをずらずら見ていくと、 cv2.setNumThreads(0) を設定することでopencv側のマルチスレッドを無効にして、multioprocessing側で実行できるようになるっていう感じですかね。

するとマルチプロセスで実行できました。

んで上記のコードは動くの?

動かないです(きっぱり)

というのも、opencv側の問題は解消できたのですが、openposeのAPI側もおそらく同じ理由で駄目っぽくて、諦めて普通に直列に実行しました・・・まぁopenposeの場合は仕方ないかってかんじです。

まとめ

opencvを用いた関数をmultiprocessingで並列実行するときに、cv2.cvtColor関数でエラーが起こる時は

cv2.setNumThreads(0)

を設定して実行しましょう。

この土日をこの時間に費やしたことでちょっとだけmultiprocessingさんと仲良く慣れたような気がします。はい。(ただし並列化で早くなったとは言ってない)