はじめに
カメラの露出時間を変更したときに処理時間がどう変わるか確認する方法は色々あるかと思います。本記事では私的に簡潔に書くとこうなる、というのを載せます。
条件
以下の条件に従って作成します。
- 処理時間は移動平均にする
- 移動平均の結果はコンソールに表示する
- 表示は見やすいように整形された形にする
最終的に完成するもの
露出時間を変更したときの処理時間を測定するデモです。処理時間が変化するのが分かります。
import cv2
from stopwatch import stopwatch
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_SETTINGS, 1)
while True:
with stopwatch():
_, img = cap.read()
cv2.imshow("img", cv2.resize(img, None, fx=0.5, fy=0.5)) # 動画撮影用に小さくしています
cv2.waitKey(1)
ここからstopwatchの作り方について解説していきます。
処理時間の測定
下記のように書いても良いのですが、前後にコードを書くのを避けるため今回はコンテキストマネージャを使用します。
start_time = time.time()
# 測定したい処理
print(time.time() - start_time)
from contextlib import contextmanager
@contextmanager
def stopwatch():
start_time = time.time()
yield
print(time.time() - start_time)
コンテキストマネージャを使用することで処理開始時にstart_time
が記録され、終了時にtime.time()-start_time
の計算結果がprintされます。
with stopwatch():
time.sleep(0.1) # 0.10009241104125977
with stopwatch():
time.sleep(0.2) # 0.20009565353393555
簡潔ですね。
移動平均を求める
下記のように書けば一応は撮影時間が計算できます。しかし大抵の場合、更新間隔が速すぎて読みにくくなります。
with stopwatch():
_, img = cap.read()
そこで処理時間を移動平均で表示できるようにします。ゴリゴリに書いても良いのですが、今回は簡潔に書きたいので以下のようなクラスを作成します。
- 移動平均の計算対象となるリストの長さを指定できる
- 平均値を計算できる
- 制限された長さ未満でも平均値を計算できる
- 制限された長さ以上の入力があった場合、最初の入力を削除する(FIFO)
作成したクラスはこちら。
from collections import deque
class MovingAverage:
def __init__(self, max_size):
"""MovingAverageクラスの初期化
Args:
max_size (int): リストの最大サイズ
"""
self.data = deque(maxlen=max_size)
def add(self, item):
"""要素をリストに追加します。
Args:
item: 追加する要素
"""
self.data.append(item)
def get_average(self):
"""リストの要素の平均値を計算して返します。
Returns:
float: リストの要素の平均値
"""
return sum(self.data) / len(self.data)
def get_data(self):
"""リストの要素を取得します。
Returns:
list: リストの要素を格納したリスト
"""
return list(self.data)
moving_average = MovingAverage(5) # 最大長が5のリストにする
moving_average.add(1)
moving_average.add(2)
moving_average.add(3)
# 最大長未満の動作確認
print(moving_average.get_data()) # [1, 2, 3]
print(moving_average.get_average()) # 2.0
moving_average.add(4)
moving_average.add(5)
moving_average.add(6)
# FIFOの確認
print(moving_average.get_data()) # [2, 3, 4, 5, 6]
print(moving_average.get_average()) # 4.0
ばっちりですね。
表示部分の作成
print
で表示するとコンソールに文字が流れていくので読みにくいです。putText
で画像中に表示するのも良いですが、今回は画像に手を加えたくないのでprint
で表示させます。
そんなときに便利なのがprint("\r表示させたい文字列", end="")
です。\r
はカーソルを行頭に移動させる特殊文字です。これに改行を無効化するend=""
を組み合わせてその場で表示を更新させます。
import time
while True:
print(f"\r{time.time()}", end="")
time.sleep(0.5)
最終的な処理時間表示関数
ここまでの処理を一つにまとめます。さらにprint
する際のもうひと工夫で下記も追加しています。
- 秒オーダーからミリ秒オーダーへの変換
- 少数点第一位まで表示
- 2桁ms~4桁msまでの変化するため、見やすくなるように表示文字長を指定
from contextlib import contextmanager
from limited_list import LimitedList
import time
LIST_LENGTH = 10
limited_list = LimitedList(LIST_LENGTH)
@contextmanager
def stopwatch():
start_time = time.time()
yield
limited_list.add(time.time() - start_time)
print(f"\r処理時間:{(limited_list.get_average()*1000): >6.1f}ms", end="")
これを冒頭のコードのように呼び出してあげればOKです。非常に簡潔ですね!