カメラとビデオ部品
なでしこさん、今年も色々とバージョンアップしていますが、中でも遊んで面白そうな機能と言えば、カメラでしょう📷️
PCやスマホのカメラをなでしこさんで利用出来るようになりました!
内部的にはgetUserMedia()を利用しています。
マニュアルに簡単なサンプルがあり、実際にカメラの映像を画面上に表示したり、静止画として保存したりも出来ます。
これを利用して今回は簡単な動体検知を試してみたいと思います。
カメラを起動
# まずカメラを表示するビデオ要素を作成する。
ビデオ=ビデオ作成。
# 作成したビデオ要素にカメラを起動。
ビデオにカメラ起動。
たったこれだけで、画面上にカメラの映像が表示されますよ✨️
カメラボタン=「カメラ終了」のボタン作成。
カメラボタンのクリックした時には、ビデオのカメラ終了。。。
カメラを終了させるボタンも用意しておきます。
正しくカメラ終了させずに終わらせますと、カメラが起動しっぱなしになってしまう場合があるのでご注意です。(Windowsのタブブラウザならそのタブを閉じれば大丈夫だと思うんですが、スマホで試した時、タブを閉じてもブラウザを終了させても、なぜかカメラ使用中のマークが消えなくて焦りました💧️)
とりあえず動体検知
動体検知の手法にはいろいろあると思いますが、今回はタイマーを用意して、一つ前の映像と今の映像を比較して、差異があったら動いた! ということにします。
フレーム間差分法?
連続した二つのフレームを比較してピクセルの変化を動きとして検出する方法、ということなので、フレームレートごとのタイマーを用意すればよいということになります。
フレームレートとは、1秒間の動画に何枚の画像が使われているかなので、(1/フレームレート)秒のタイマーでいいんでしょうかねー?
フレームレート取得
カメラ設定取得したカメラ設定オブジェクトの中にframeRateがあります。
カメラ設定=ビデオのカメラ設定取得。
フレームレート=カメラ設定.frameRate。
ウチではPCでもスマホでも30fpsのようでした。
フレームレートはカメラ起動時にカメラオプションに指定することもできます。
カメラオプション.video={ frameRate: 10 }
これでふつーに通ったけどMDNを見るとなんかむじゅかしいコト書いてあって、こんな感じ?
カメラオプション.video={ frameRate: { ideal: 10, max: 15 } }
設定したい値をidealにして、minやmaxで範囲を指定するってこと?
ビデオの幅と高さ
フレームレートを取得した「カメラ設定」から取得してもいいんですが、カメラ起動時にビデオに反映されていますから、ビデオから直接取るとオブジェクトプロパティ構文の和属性が使えるのでオススメです✨️
ビデオ幅=ビデオ.幅属性。
ビデオ高さ=ビデオ.高さ属性。
ビデオもキャンバス同様、属性値のwidthとheightなので、オブジェクトプロパティ構文を使う場合は「幅」「高さ」ではなく「幅属性」「高さ属性」ですのでご注意です。
動体検知の設定項目
後には値指定バーで変更したりできるようにしたいため、わかりやすいよう1~100って感じで設定できるっぽくしています。
精度=30。精度=精度/100。
感度=80。感度=255/100*(100-感度)+1を切り捨て。
閾値=20。閾値=ビデオ幅*ビデオ高さ*精度^2/1000*閾値を切り捨て。
-
精度
ミリ秒単位で映像の全てのピクセルを走査すると大変なので、縮小した画像を使って比較します。
ビデオのサイズを100として、比較用画像を何パーセントに縮小するか、です。 -
感度
画面のちらつきやノイズまで拾ってしまわないよう、色差がどの程度あったら動いたとするか。 -
閾値
最終的に動いたピクセルがどの程度あったら動いたとするか。
キャンバス作成
フレームごと交互に描画し前後を比較する2枚のキャンバスと、動いたフレームを表示するキャンバスを作ります。
# 動いたフレームを表示するキャンバス
撮影用=[ビデオ幅,ビデオ高さ]のキャンバス作成。
# フレームごと交互に描画し前後を比較する2枚のキャンバス
比較用=空配列。
2回:
比較用[回数-1]=[ビデオ幅*精度,ビデオ高さ*精度]のキャンバス作成。
比較用[回数-1]の「visibility」に「hidden」をDOMスタイル設定。# 可視はオフ
比較用のキャンバスは見えないように可視をオフにしておきたいですが、配列に入れたキャンバスにオブジェクトプロパティ構文が使えなくてツライ💧️
サンプルなので、あえて見えるようにしておくとゆうことも考えられますが🤣️
音を鳴らす
ピ=「https://n3s.nadesi.com/image.php?f=213.mp3」をオーディオ開く。
動いたら音でお知らせされるようにしたいと思います。
オーディオ開いてからオーディオ再生です。
音を鳴らす命令は他にもありますが、スマホなどでも使うかもしれないことを考えた場合にはオーディオ命令が間違いないですね。
メイン
あとはもう画像処理ですね。
C=-1。比較データ=空配列。
(1/フレームレート)秒タイマー開始した時には(タイマーID)
Cを1増やす。
比較データ[C%2]=空配列。
比較用[C%2]へ描画開始。
ビデオの[0,0,ビデオ幅,ビデオ高さ]を[0,0,ビデオ幅*精度,ビデオ高さ*精度]へ画像部分描画。
色データ=[0,0,ビデオ幅*精度,ビデオ高さ*精度]の色データ取得。
I=0。
Nを0から色データの要素数-1まで4ずつ増やし繰り返す。
色平均=(色データ[N] + 色データ[N+1] + 色データ[N+2])/3。
比較データ[C%2]に色平均を配列追加。
もし、C>0ならば: #初回は前データがないので比較しない
もし、(((比較データ[0][N/4])-(比較データ[1][N/4]))の絶対値)>感度ならば、Iを1増やす。# ピクセルが動いたと判定
ここまで。
もし、C>10ならば: # 起動時のちらつき回避で少し待ってみる
もしI>閾値ならば: # 最終的に動いたと判定
ピをオーディオ再生。
撮影用へ描画開始。
[0,0]へビデオを画像描画。
ここまで。
比較用のキャンバスに、ビデオから縮小して画像部分描画をして、それの色データを取得します。
色データの取得については一日目の電光掲示板でもやりました。
四つ飛びにアクセスしてってRGBの値の平均を取ります。この平均値をRGBそれぞれに戻してやればグレースケールになるんですが、今回は比較データ配列に保存していって一個前の時のデータと比較。
感度や閾値は、もしもしで反映させて最終的に動いたかどうか判定しています。
これで大体動くようにはなりました。
カメラの前で大きく手を振ったりすると、ピッピ鳴ります。
でも、イマイチよく分かりませんので、動いたピクセルをモニターしてみます。
モニター
とりあえず簡単に今まで使っていなかった標準キャンバスへモニターすることにします。
モニターなので、ビデオの半分のサイズとしておきます。
モニター=描画中キャンバス。
モニター.幅属性=ビデオ幅/2。
モニター.高さ属性=ビデオ高さ/2。
それから比較用のキャンバスと同じサイズで動いたピクセルを描き出す処理用キャンバスを作成します。
処理用=[ビデオ幅*精度,ビデオ高さ*精度]のキャンバス作成。
処理用.可視=オフ。
1フレーム毎にモニターにはビデオの映像をコピーし、処理用キャンバスをクリア。
ピクセルが動いたと判定された場合には、処理用キャンバスの同じ場所に赤い点を描画。
そして、比較用キャンバスの走査が終わったら、処理用をモニターに拡大描画。
モニターへ描画開始。
ビデオの[0,0,ビデオ幅,ビデオ高さ]を[0,0,ビデオ幅/2,ビデオ高さ/2]へ画像部分描画。
処理用へ描画開始。全描画クリア。赤色に塗り色設定。空に線色設定。
I=0。
Nを0から色データの要素数-1まで4ずつ増やし繰り返す。
色平均=(色データ[N] + 色データ[N+1] + 色データ[N+2])/3。
比較データ[C%2]に色平均を配列追加。
もし、C>0ならば: #初回は前データがないので比較しない
もし、(((比較データ[0][N/4])-(比較データ[1][N/4]))の絶対値)>感度ならば:# ピクセルが動いたと判定
Iを1増やす。
# 処理用に点を打つ
x=N/4%(処理用.幅属性)。
y=N/4/(処理用.幅属性)を切り捨て。
[x,y,1,1]へ四角描画。
ここまで。
もし、C>10ならば: # 起動時のちらつき回避で少し待ってみる
モニターへ描画開始。
[0,0,ビデオ幅/2,ビデオ高さ/2]へ処理用の[0,0,ビデオ幅*精度,ビデオ高さ*精度]を画像部分描画。
できました!
バッチリ動いたところがモニターできるようになりました。
やっぱり目に見えて動いているところが分かると面白いですね。
設定変更できるようにして完成(?)
#----- 設定 ---------------
# 動体検知の設定項目
精度=30。
感度=80。
閾値=20。
# 開閉できる設定画面
母艦=DOM親要素。
設定画面=「DETAILS」のDOM部品作成。
それ.open=オン。
それにDOM親要素設定。
「SUMMARY」のDOM部品作成。
それに「設定」をテキスト設定。
母艦にDOM親要素設定。
# カメラ終了のボタン
改行作成。
カメラボタン=「カメラ終了」のボタン作成。
改行作成。
#----- カメラを起動 ---------------
# まずカメラを表示するビデオ要素を作成する。
ビデオ=ビデオ作成。
# 作成したビデオ要素にカメラを起動。
ビデオにカメラ起動。
# カメラ終了ボタンのイベント
カメラボタンのクリックした時には、ビデオのカメラ終了。。。
#----- フレームレートごとに画像比較 ---------------
# フレームレート取得
カメラ設定=ビデオのカメラ設定取得。
フレームレート=カメラ設定.frameRate。
# ビデオの幅と高さ取得
ビデオ幅=ビデオ.幅属性。
ビデオ高さ=ビデオ.高さ属性。
# 標準キャンバスに動いた点をモニターする
モニター=描画中キャンバス。
モニター.幅属性=ビデオ幅/2。
モニター.高さ属性=ビデオ高さ/2。
# 動いたフレームを表示するキャンバス
撮影用=[ビデオ幅,ビデオ高さ]のキャンバス作成。
# フレームごと交互に描画し前後を比較する2枚のキャンバス
比較用=空配列。
2回:
比較用[回数-1]=[1,1]のキャンバス作成。
比較用[回数-1]の「visibility」に「hidden」をDOMスタイル設定。
# 比較した情報を描き出すキャンバス
処理用=[1,1]のキャンバス作成。
処理用.可視=オフ。
# 音を鳴らす
ピ=「https://n3s.nadesi.com/image.php?f=213.mp3」をオーディオ開く。
#--- メイン ---
C=-1。比較データ=空配列。
(1/フレームレート)秒タイマー開始した時には(タイマーID)
Cを1増やす。
比較データ[C%2]=空配列。
比較用[C%2]へ描画開始。
ビデオの[0,0,ビデオ幅,ビデオ高さ]を[0,0,ビデオ幅*精度,ビデオ高さ*精度]へ画像部分描画。
色データ=[0,0,ビデオ幅*精度,ビデオ高さ*精度]の色データ取得。
モニターへ描画開始。
ビデオの[0,0,ビデオ幅,ビデオ高さ]を[0,0,ビデオ幅/2,ビデオ高さ/2]へ画像部分描画。
処理用へ描画開始。全描画クリア。赤色に塗り色設定。空に線色設定。
I=0。
Nを0から色データの要素数-1まで4ずつ増やし繰り返す。
色平均=(色データ[N] + 色データ[N+1] + 色データ[N+2])/3。
比較データ[C%2]に色平均を配列追加。
もし、C>0ならば: #初回は前データがないので比較しない
もし、(((比較データ[0][N/4])-(比較データ[1][N/4]))の絶対値)>感度ならば:# ピクセルが動いたと判定
Iを1増やす。
# 処理用に点を打つ
x=N/4%(処理用.幅属性)。
y=N/4/(処理用.幅属性)を切り捨て。
[x,y,1,1]へ四角描画。
ここまで。
もし、C>10ならば: # 起動時のちらつき回避で少し待ってみる
モニターへ描画開始。
[0,0,ビデオ幅/2,ビデオ高さ/2]へ処理用の[0,0,ビデオ幅*精度,ビデオ高さ*精度]を画像部分描画。
もしI>閾値ならば:
ピをオーディオ再生。
撮影用へ描画開始。
[0,0]へビデオを画像描画。
ここまで。
#----- 設定変更用のUIを設定画面に追加する ---------------
改行作成。
設定画面にDOM親部品設定。
設定項目={精度:精度,感度:感度,閾値:閾値}
設定タイトル=空辞書。設定バー=空辞書。設定確認は空辞書。
設定項目を反復:
設定タイトル[対象キー]=対象キー&「:」のラベル作成。
設定バー[対象キー]=[0,100,対象]の値指定バー作成。
設定確認[対象キー]=対象のエディタ作成。
改行作成。
設定バー[対象キー]の「幅」に「300px」をDOMスタイル設定。
設定バー[対象キー]に対象キーをポケット設定。
設定確認[対象キー]の「幅」に「50px」をDOMスタイル設定。
設定確認[対象キー]の「行揃え」に「右」をDOMスタイル設定。
設定確認[対象キー]の「無効化」にオンをDOM属性設定。
設定バー[対象キー]を変更した時には:
No=対象のポケット取得。
対象のテキスト取得。
設定確認[No]にそれをテキスト設定。
設定反映。
設定反映。
●設定反映
精度=(設定バー.精度のテキスト取得)/100。
感度=255/100*(100-(設定バー.感度のテキスト取得))+1を切り捨て。
閾値=ビデオ幅*ビデオ高さ*精度^2/1000*(設定バー.閾値のテキスト取得)を切り捨て。
2回:
比較用[回数-1]の「幅」にビデオ幅*精度をDOM属性設定。
比較用[回数-1]の「高さ」にビデオ高さ*精度をDOM属性設定。
処理用の「幅」にビデオ幅*精度をDOM属性設定。
処理用の「高さ」にビデオ高さ*精度をDOM属性設定。
C=-1
ここまで。
#-----------------------------------------------
●(xywhの|xywhを)色データ取得
(描画中コンテキストの「getImageData」をxywhでJSメソッド実行).dataを戻す。
ここまで。
#-----------------------------------------------
👇️動作確認
反省または課題あるいは目標
撮影用の画面があまり用をなしてないですね。
モニター画面と並べて、感度と閾値の可視化としたほうが、サンプルとしては良かったかも?
そして、知ってたけどフレーム間の差分だと、サッと速く動いたものは検知しやすいけど、ゆ~っくり動いた場合は、よほど閾値を低く設定しないと検知しない。
抜き足差し足忍び足の泥棒さんが検知できなかったら防犯カメラの意味ないですよね~💧️
設置式の防犯カメラで背景が変わらないのであれば、背景との差分のほうがいいのかもしれないですが~、むしろ今どきはAIが人間だ車だと判定して検知してくれちゃったりするようです!
でもですね、ウチの安物のペットカメラも明らかにこのフレーム間差分法のようで、野良ちゃんを保護するためにお外のえさ場に向けておいたら、カラスやスズメはめっちゃ検知するけど、警戒しながらこしょーっと近づいてくる野良ちゃんを検知せず、食べ終わってサッと逃げてくところでやっと検知するという無能っぷり😹️(普段は動体検知オフで家のねこどもを出先から愛でているだけなので問題ないです)
野良にゃんが来たのをお知らせしてくれるだけで良いので、次の機会? までに家のカメラよりもうちょっとマシにニャン検知できるようなのを考えたい。
