LoginSignup
5
6

More than 3 years have passed since last update.

RxPYを使ってOpenCVするとき、asyncio使えば別スレッドからでもimshowできる

Last updated at Posted at 2019-06-30

RxPY+OpenCVでimshowをするとき、asyncioを使えばsubscribeがメインスレッドじゃなくてもimshowすることができたのでメモとして残しておく。

<2019年10月9日 追記>
タイトルに語弊がありました。imshow自体は別スレッドで使っているわけではないです。

サンプルコード(失敗例)

下のコードはRxPYで画像処理を適用し、最後に画面に表示するためのプログラムです。
一見、問題ないように思いますが、このコードは最後の「show」関数がメインスレッドではない、別のスレッドで実行されるため、cv2.imshow関数でエラー終了してしまいます。

import cv2
import rx
from rx import Observable

def show(image):
    """
    動画を画面に表示する
    """
    cv2.imshow('res', image)
    cv2.waitKey(1)

def imgproc(image):
    """
    画像処理するプログラム (とりあえずここではグレースケールにするだけ)
    """
    return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

video = cv2.VideoCapture(0)

# 1. 15FPSでインターバル
# 2. VideoCaptureからデータを取り出す
# 3. ちゃんと画像が取り出せた時だけ処理する
# 4. 画像だけを次のストリームに渡す
# 5. 画像処理を適用する
# 6. 結果を画面に表示する
Observable.interval(1 / 15 * 1000) \
    .map(lambda x: video.read()) \
    .filter(lambda data: data[0]) \
    .map(lambda data: data[1]) \
    .map(imgproc) \
    .subscribe(show)

# プログラムが停止しないように入力待ちする
a = input()

asyncioを使う

asyncioはPythonで非同期するためのモジュールで、いわゆる「ノンブロッキング処理」を実現できます。
このasyncioに、「run_coroutine_threadsafe」という、別スレッドで実行されているイベントループに処理を渡すことができる関数があります。

これを使って、メインスレッドでイベントループを作成し、run_coroutine_threadsafeを使ってimshowを呼び出してやれば上記のようなPGでもimshowを実行することができる。

サンプル

import cv2
import rx
from rx import Observable
import asyncio

def imgproc(image):
    """
    画像処理するプログラム (とりあえずここではグレースケールにするだけ)
    """
    return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

video = cv2.VideoCapture(0)
# イベントループを作成
loop = asyncio.get_event_loop()

async def show_coroutine(image):
    """
    asyncioで実行するために、GUI表示部分をコルーチン化
    """
    cv2.imshow('res', image)
    return cv2.waitKey(1) & 0xff

def show(image):
    """
    run_coroutine_threadsafeでコルーチン
    """
    future = asyncio.run_coroutine_threadsafe(show_coroutine(image), loop)
    key = future.result()
    if key == ord('q'): # qが押されたらループを停止する
        loop.stop()

Observable.interval(1 / 15 * 1000) \
    .map(lambda x: video.read()) \
    .filter(lambda data: data[0]) \
    .map(lambda data: data[1]) \
    .map(imgproc) \
    .subscribe(show)

# イベントループを開始する
loop.run_forever()
video.release()
loop.close()

ちなみに、RxPYにはAsyncIOSchedulerというスケジューラもあるので、
もしかするともっと上手く書くこともできるかもしれないです。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6