VideoCaptureのgrab()とretrieve()で最新画像を描画する

2 min read読了の目安(約2500字

0.概要

スレッドを利用して取り込みと描画を分けているコードはいくつか見つかったのですが、所望の機能のものがなかったため、以下のようなコードを考えました。

1.コード

#! /usr/bin/python3

import cv2, threading, time

class Measure():
  def __init__(self, regular):
    self._regular = regular
    self._number = 1
    self._start = time.time()

  def elapsed(self):
    return (time.time() - self._start)

  def difference(self):
    diff = (self._regular * self._number) - self.elapsed()
    return diff if diff > 0 else 0

  def fps(self):
    return self._number / self.elapsed()

  def next(self):
    self._number += 1

class VideoThread():
  def __init__(self, index, name):
    self._index = index
    self._camera = cv2.VideoCapture(index)
    self._name = name
    
    number = 0
    while number != 10: # Wait for the transfer to stabilize.
      if self._camera.grab():
        number += 1
      
    self._alive = True
    self._thread = threading.Thread(target=self.grabber)
    self._thread.daemon = True
    self._thread.start()

  def name(self):
    return self._name + "[" + str(self._index) + "]"

  def grabber(self):
    measure = Measure(regular=1/120)
    while self._alive and self._camera.grab():
      #time.sleep(measure.difference())
      print("fps(" + self.name() + ") {0:.3f}".format(measure.fps()))
      measure.next()

  def retrieve(self):
    _, frame = self._camera.retrieve()
    return frame

  def close(self):
    self._alive = False
    self._thread.join()
    self._camera.release()

def main():
  video_thread_0 = VideoThread(0, "camera")
  video_thread_1 = VideoThread(1, "camera")
  
  # Wait for the window to stabilize.
  cv2.imshow(video_thread_0.name(), video_thread_0.retrieve())
  cv2.imshow(video_thread_1.name(), video_thread_1.retrieve())
  cv2.waitKey(1)

  measure = Measure(regular=1/120)
  while chr(cv2.waitKey(1) & 255) != 'q':
    cv2.imshow(video_thread_0.name(), video_thread_0.retrieve())
    cv2.imshow(video_thread_1.name(), video_thread_1.retrieve())
    time.sleep(measure.difference())
    print("fps(draw) {0:.3f}".format(measure.fps()))
    measure.next()

  video_thread_0.close()
  video_thread_1.close()
    
if __name__ == "__main__":
  main()

2.説明

cv::VideoCapture Class Reference
grab()
retrieve()

# Wait for ~」の部分については、「カメラの転送開始時」や「Windowの生成直後」に負荷が大きく、正確な時間計測ができないため、安定するまで待たせる処理を適宜挟んでいます。