269.ラズパイ無双[14_opencvストリーミング4]
初回:2022/8/10
Raspberry Pi (ラズベリーパイ、通称"ラズパイ")で何か作ってみようという新シリーズです。カメラ画像をストリーミング出来るサーバーは完成したので、そこで使用するフィルターのサンプルを解説したいと思います。
P子「一応、使えるサンプルよね」※1
応用は利くと思います。
1.概要説明
今回のサンプルは、ストリーミングサーバーに動的に組み込む filter と subapp オブジェクトを使用します。filterは、指定の領域を二値化するサンプルで、ブラウザに表示されている動画をマウスで範囲指定した箇所だけを二値化します。マウスで選択した範囲は、複数指定でき、セーブボタンでファイルとして書き込みを行い、次回システム起動時にはデータを読み込んで、選択範囲を再現することができます。
また、二値化方法として、通常のしきい値を用いた単純二値化と、大津の二値化法の切り替えボタンを用意しました。しきい値の変更は、マウスホイールで、上下に回転することで指定できます。
≪参考≫
https://ja.wikipedia.org/wiki/%E5%A4%A7%E6%B4%A5%E3%81%AE%E4%BA%8C%E5%80%A4%E5%8C%96%E6%B3%95
大津の二値化法
出典: フリー百科事典『ウィキペディア(Wikipedia)』
このサンプルでは、openCVで生成された画像とブラウザで表示されている画像の縮尺違いの調整と、マウスによるエリア指定のAJAX処理、その値をJSON形式でサーバーに送信し、ストリーミングサーバーに動的に組み込まれた subapp で受け取って値を設定するという一連の流れのサンプルになっています。
また、ボタンでの値の設定と、マウスホイールでの値の設定のサンプルにもなっています。
filter処理は、判りやすい様に、二値化処理にしていますが、どのような処理を組み込んでも問題ありません。subapp は、ブラウザからの値の受け取りに利用します。module は、今回のサンプルにはありませんが、python で処理を行った結果をブラウザに返す処理を記述できます。これらは、ストリーミングサーバーと切り離して作成できるというのが、特徴です。
P子「ややこしそうね」
少しややこしいのですが、先のテストサンプルなど、簡単な例も参考にしながら、自分なりのフィルター処理などを作ってみるのが、一番理解が早いと思います。
まずは、ソースをzip圧縮しましたので、ダウンロードします。
≪ソース≫
≪OSDN の作業部屋≫
https://ja.osdn.net/users/chatrun/pf/eu63/files/
OSDN の作業部屋という所に、@IT を作成しました。この中の No269.zip を取得してください。
展開すると、中には、www_mark というフォルダには、今回のプログラムのみ格納されています。ストリーミングサーバーなどは、前回の www フォルダを使用してください。
2.フォルダ構成説明
www 以下の最終構成は、このようになります。
├── static 静的ファイル(flaskとの共通化)
│ ├── CAMERA.png
│ ├── custom.css イメージ表示関連などのカスタマイズ系のCSS
│ ├── default.css 一般的なスタイルシートの設定
│ └── mark.js ★ 今回の画面からの情報をサーバーに送信するAJAX
├── views テンプレート
│ ├── header.html include評価用
│ ├── index.html 開始点
│ └── mark.html ★ 今回の画面
├── waitress マルチスレッドサーバー(ファイル展開した場合)
│ └── ・・・・
├── bottle.py アプリケーションサーバー
├── camera.py app.py に組み込まれるマルチストリーミング処理本体
├── ogServer.desktop ディスクトップにショートカットを作成する場合のサンプル
├── ogServer.py サーバー本体
├── ogServer.service サービス設定ファイル
├── ogServer.sh サービス/アプリケーション起動用シェルスクリプト
└── mark2_filter.py ★ 今回のfilter と subappの実装
起動例
$ cd www
$ python ./ogServer.py
ブラウザから、http://xxxx:8088/mark.html で、今回の画面が見えるはずです。
3.mark2_filter.py 説明
Mark2Filter が、本体のクラスになります。
コンストラクタで、選択された矩形の配列、それのセーブファイル(./mark.dump固定)、通常の二値化のしきい値、二値化方式の切り替えスイッチ のインスタンス変数を定義しており、初回にセーブされている矩形リストの取込を行っています。
def wheel_action(self, actDic): は、マウスホイール時の処理を行っています。一応、0から255の範囲チェックは入れています。マウスホイールの回転方向で。プラスマイナスの値が来ますが、数値とステップ数の関係は保証できないそうなので、とりあえずプラスかマイナスの値であれば、+1,ー1するという仕様にしています。
def action(self, actDic): は、subapp からルーティングされてきたJSON形式を辞書に変換後に呼び出される関数です。ここで少しだけ変な仕様を入れています。通常の矩形指定は、左上から右下に行う事とし、逆方向に指定するとX-Yの差分がマイナスになります。その場合は、矩形を削除することにしました。
def filter(self, image): は、フィルター実行の関数で、引数の画像を加工後、return します。画像の左上に、処理時刻を入れるための self._putTime(image) と、self.rect がマウスで指定した矩形の配列です。
何となくややこしそうな個所は、ブラウザで表示されている画像のサイズに基づいて、マウスで矩形を指定していますので、実イメージのサイズに変換しています。
その次の cv2.rectangle で、矩形を赤色で指定しています。
その次は、イメージの範囲指定と、二値化処理と、処理後のイメージを元の矩形範囲に戻しています。このあたりは少し非効率になっていますが、判りやすさ優先という事で、ご勘弁を。
def _putTime(self,img,color=BLACK): は、日時文字列書き込み処理です。以前にも少し説明したかもしれませんが、日付文字数からサイズを決め打ちで設定しているので、本来はきちんと計算で比率を求めるべきでしょう。
def _gray(self, img): が、二値化処理の本体で、cv2.threshold(img_gry,0,255,cv2.THRESH_OTSU) が大津の二値化で、cv2.threshold(img_gry, self.thresh, 255, cv2.THRESH_BINARY) が通常のしきい値を指定しての二値化です。最後にカラーに戻さないと、元の画像に部分置換できません。
最後に、cv2.putText で、しきい値を部分画像に書き込んでいます。決め打ちです。
大津の二値化は、しきい値処理を自動計算しているので、その結果を表示しているという感じです。
def read(self): def write(self): が、矩形の配列の読み書きです。
def change(self): はOTSUの二値化への設定On/Off(トグル処理)です。
def close(self): は、終了処理で、一応内部メモリをクリアしています。
P子「最後の方は、ちょっと手抜きしてない?」
まあ、ソースを読めばわかるでしょう。
4.static/mark.js 説明
これは、画面からの情報を、filterに返すためのJavaScriptです。正確に言うと、subapp オブジェクトで追加したルーティング処理で、データを受け取ります。
処理的には、マウスクリックで、4点を指定した段階で、JSONで値を送ります。writebtnボタンと、changebtnボタンの処理もここで受けています。
ここでも、ブラウザ上の画像サイズとマウスクリックによる矩形のサイズの両方を送信しています。
JavaScript系はあまり得意ではありませんので、解説もソースもこんな程度ですみません。
5.views/mark.html
マウス選択対象のイメージのstyleで、select と drag を OFF にしています。これは、マウスで矩形選択する時に、画像がドラッグされようと動いたりするのを避けるためです。
そして、jquery と、jquery.mousewheel と、/static/mark.js のスクリプトを取り込んでいます。
body 部分は、イメージ表示領域の作成と、ボタンを配置しているだけなので、判るでしょう。
6.まとめ
ストリーミングサーバーの解説は、今回で最後です。今後、このサーバーに組み込む filter,subapp,module のプログラムで、色々な事を行いたいと思います。
ほな、さいなら
======= <<注釈>>=======
※1 P子「一応、使えるサンプルよね」
P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。