内容
動体検知で使われるaccumulateWeighted()の処理がいまいちよくわからなかったので、簡単に検証してみた。
結論
accumulateWeighted()で行われる処理は、第二引数である移動平均の値を更新していくというもの。
第一引数には最新の値を入れる。
第三引数は過去のデータの忘れやすさに相当していて、この値が大きいほど最新のデータの影響が大きくなる。
この値の逆数が移動平均の項の数とだいたい同じ意味になる。
検証方法
まっ黒の画像とまっ白の画像を用意して、それらを処理していったときに処理後の値がどう変わっていくかを調べた。
コード
import os
import cv2
# 画像の読み込み
fl = os.getcwd()
white = cv2.imread(fl + r'\white.png')
black = cv2.imread(fl + r'\black.png')
white = cv2.cvtColor(white, cv2.COLOR_BGR2GRAY)
black = cv2.cvtColor(black, cv2.COLOR_BGR2GRAY)
# 読み込んだ画像は白と黒
print(white[0][0], black[0][0])
# 255 0
avg = None
forget = 0.1
for i in range(10):
if avg is None:
avg = black.copy().astype("float")
# 黒に白を混ぜていく
cv2.accumulateWeighted(white, avg, forget)
print(i, avg[0][0])
# forget=0.1 -> 25.5 48.45 69.105 ...
# forget=0.5 -> 127.5 191.25 223.125 ...
# forget=0.9 -> 229.5 252.45 254.745 ...
解釈
accumulateWeighted()では、
$avg = (1-forget)×avg+forget×white$
という処理をやっているらしい。
これはたぶん、k+1番目~k+n番目までのn項の移動平均をk+2番目~k+n番目の(n-1)項の移動平均とほぼ同じものと考えることで、過去のデータを記憶しないで簡単に新たな移動平均の値を計算しているんだと思う。
どういうことかというと、
k番目の数を$a_k$、k+1から始まるn項の移動平均を
A^n_{k+1} = \frac{1}{n}\sum_{i=k+1}^na_i
としたとき、
A^n_{k+2} = \sum_{i=k+2}^na_i \\
= \frac{1}{n}(\sum_{i=k+2}^{n-1}a_i +a_{k+n+1}) \\
= \frac{1}{n}((n-1)A_{k+2}^{n-1} +a_{k+n+1})
と変形できる。
先ほどの近似を使うと、
A^n_{k+2} = \frac{1}{n}((n-1)A_{k+1}^n +a_{k+n+1})
となるんで、一つ前の移動平均と最新の値から新たな移動平均を計算する式になる。
ついでに$f=1/n$とすると、
A^n_{k+2} = (1-f)\,A_{k+1}^n +f\:a_{k+n+1}
となって、第三引数の逆数が移動平均の逆数に相当することがよくわかる。
accumulateWeighted()で行われる処理がわかってめでたしめでたしという話。
参考URL
OpenCVを利用して動画(カメラ)から動体検知をする方法について
https://developers.cyberagent.co.jp/blog/archives/12666/