反面教師あり学習

*/

(旧)反面教師あり学習

Negative Supervised Learning

OpenCV for Pythonの動画入力をThreadingで高速化する

概要

  • Pythonのループ文でOpenCVVideoCapture使ってたらめちゃ遅かったのでthreadingで速くした.

モチベーション

通常のVideoCaptureは動画の読み込み時にメインスレッドが止まってしまうので速度があまり出ない. そこでthreadingを使うことで高速化を試みた.

既に同じことを行ってる英語記事があるが, この実装だとカメラからの入力が遅くてキューが空っぽになってしまったらメインスレッドが変なところで終了するのと, OpenCVVideoCaptureの関数と戻り値の形式が異なったりして扱いにくかったので, そのあたりの問題を解消しつつ書いてみた.

www.pyimagesearch.com

つくったもの

import threading
import queue

import cv2


class ThreadingVideoCapture:
    def __init__(self, src, max_queue_size=256):
        self.video = cv2.VideoCapture(src)
        self.q = queue.Queue(maxsize=max_queue_size)
        self.stopped = False

    def start(self):
        thread = threading.Thread(target=self.update, daemon=True)
        thread.start()
        return self

    def update(self):
        while True:

            if self.stopped:
                return

            if not self.q.full():

                ok, frame = self.video.read()
                self.q.put((ok, frame))

                if not ok:
                    self.stop()
                    return

    def read(self):
        return self.q.get()

    def stop(self):
        self.stopped = True

    def release(self):
        self.stopped = True
        self.video.release()

    def isOpened(self):
        return self.video.isOpened()

    def get(self, i):
        return self.video.get(i)

使い方

なるべくOpenCVVideoCaptureと同じ仕様になるように設計してあるので, 下記記事のコードがほぼそのまま使える (video.start()を追加していることに注意.__init__の中にこれも含めてしまってもよかったかもしれん).

eqseqs.hatenablog.com

ここでThreadingVideoCapture本体と依存パッケージの読み込みは省略してある.

import numpy as np
import cv2


# 動画ファイルを開く
video = ThreadingVideoCapture('/path/to/video')

# キューのサイズを変えるとき
# video = ThreadingVideoCapture('/path/to/video', max_queue_size=1024)

# PCに接続されたカメラの映像を表示
# video = ThreadingVideoCapture(0)

if not video.isOpened():
    raise RuntimeError

video.start()

cv2.namedWindow('frame', cv2.WINDOW_AUTOSIZE)

while True:

    ok, frame = video.read()

    if not ok:
        break

    cv2.imshow('frame', frame)

    key = cv2.waitKey(int(1000 / 30))

    if key == ord('q'):
        break

video.release()
cv2.destroyAllWindows()

threadingを行うことで,例えば,もしcv2.imshowの直前に1秒ぐらい時間のかかる画像処理をいれていたとしても, その処理中にもカメラや動画のフレームを読み込んでキューに積んでおいてくれる. そのためI/O待ちが発生しなくなってその分速くなる.

追記 (2021/9/14):

「1秒ぐらい時間のかかる」と書いてるけど実際はそんなに時間かかってるとキューがパンクするし,代わりにキューをでかくするとメモリがパンクするので注意.時間のかからない画像処理でもI/Oと並列化するだけでかなり速くなる. あと,もっと速度上げたいならcv2.imshowはかなり遅いのでデバッグ時以外は切った方がいい.