【Unity】OpenCVを用いて残像を描画する(背景切り抜き)
ご無沙汰しております。 Unityの内容かつ、あまり記事が見当たらないものをブログにと思っていたので、期間が開いてしまいました。UnityとXcode半々ぐらいなので空きがちです。
今回は、定点カメラの動画から、スポーツの動き(スイング)を残像表示する必要があったのでその内容を記事にします。 また今回なんですが、OpenCVForUnityのアセットが必須になります。アセットがなくてもOpenCVは使用できた気もするのですが、知らないのでこのアセットでということにします。
概要
OpenCVの背景差分で実装します。この手法で実現できるのは背景切り抜きになります。イメージでいうと、Web会議の映像の背景を宇宙とかにして人物だけ映していると思うのですが、その人物切り出しみたいなものです。 この方法では背景画像と動体画像から動体だけの画像が作成可能で、スイング残像とかだとこれを動画だと毎フレーム切り出して画像に張り付けを繰り返すことで残像を1枚の画像に表示させることが可能です。
実装方法
OpenCVUnityのBackgroundSubtractorMOG2のクラスを使います。 今回実装した関数の引数となる入力画像は、
背景となる画像
動体が映っている画像
切り取り画像の貼り付け先画像 の3つです。
public Mat MaskDraw(Mat currentMat, Mat preMat, Mat backMat) { using (Mat fgMaskMat = new Mat(backMat.rows(), backMat.cols(), CvType.CV_8UC1)) using (Mat bgMaskMat = new Mat(backMat.rows(), backMat.cols(), CvType.CV_8UC1)) // 背景差分法 using (BackgroundSubtractorMOG2 mog2 = Video.createBackgroundSubtractorMOG2()) { // 背景とする画像 mog2.apply(backMat, fgMaskMat); // マスクさせるものが移っている画像、ここでは過去画像でマスク画像を作成 mog2.apply(currentMat, fgMaskMat); // マスク画像を閾値を設定して2値化、閾値は調整可能 // これをしないとジャギジャギのマスクになる Imgproc.threshold(fgMaskMat, fgMaskMat, 80, 255, Imgproc.THRESH_BINARY); // マスク画像をつかって画像を反転させる(現状だと人部分が切り抜かれたMatデータ) Core.bitwise_not(fgMaskMat, bgMaskMat); // 結果画像のベースを現在画像に設定 var result = new Mat(preMat.rows(), preMat.cols(), CvType.CV_8UC1); preMat.copyTo(result); // 切り抜いた画像を貼り付け先の画像に加算描画 currentMat.copyTo(result, fgMaskMat); return result; } }
参考記事
http://blog.livedoor.jp/maikka_gogo/archives/1051597401.html
背景差分 — OpenCV-Python Tutorials 1 documentation
株式投資年間成績(2020.5~2021.5)
完全に個人用のメモですが、今年も一応書いておきますか。
まずは結果
+74439→+126575(+52136)→+153838(+27263)
今株初めて3年経ったんですが利益だけだとこんな感じですね。()の中が一年の評価です。
まぁ今年もプラスで終われたから良かったんですが、どんどん減ってるなぁって感じですね。ただお金出し入れして最初よりも資金減ってたりはしてるのでまぁそこまで本気で思っていないです。入れてるお金的にこんなもんかな、ぶち当たらなかったらってかんじですよね。
ただコロナで結構日経とか押せ押せだったのに利益をあまり出せていないことに関しては悲しい。
PRTIMESとか持ってたのにコロナで戻って安心して分割前に売ってしまってるし・・・
今は資金最初ぐらい入れているのではじめての年ぐらいの利益出したいなぁって思っています。
【Unity】VideoPlayerを使った動画操作入門
お久しぶりです。 またUnityを使う機会があったので、記事を更新します。最近はもっぱらswiftでiOS開発をしていました。 今回取り扱うVideoPlayerなんですが、これを使った記事はちょくちょく見かけてはいたのですが、大体再生までの流れぐらいでした。ただいざ実装すると往々にしていろいろタイミングが知りたくものですよね。あまりそこらへんの記事がなかったので今回記事にします。最後にサンプルのGitリポジトリおいておくので必要であれば見てみてください。
再生までの流れ
ここらへんは記事も多かったのであまり詳しくは書きません。 とりあえずの流れとしては
VideoPlayerのアタッチ(動画はVideoPlayerのインスペクタから設定しておく)
RawImageの配置 を行ったうえで以下のように設定すれば再生までの流れは概ねオッケーではないでしょうか?
private void SetupVideoPlayer() { // 動画表示用のレンダーテクスチャーを作成 var renderTexture = new RenderTexture(1920, 1080,0); videoPlayer.renderMode = VideoRenderMode.RenderTexture; videoPlayer.targetTexture = renderTexture; displayRawImage.texture = renderTexture; videoPlayer.Prepare(); }
再生する動画をスクリプトで設定する
こちらは動画ファイルのURLを設定するだけで大丈夫です。 なので上のSetupVideoPlayer()の前に
videoPlayer.url = "動画のファイルパス";
を記述するだけでできます。
再生・停止・シーク機能
こちらに関してもほとんどVideoPlayerの機能として持っています。
関数化していますが基本的にはVideoPlayerの機能だけでできます。
シーク機能の関数はないのですがVideoPlayer.frameを指定するだけでシークすることが出来るのでSliderとの紐付けも簡単です。
以下にサンプルを載せます。
/// <summary> /// 動画再生 /// </summary> public void Play() { videoPlayer.Play(); } /// <summary> /// 一時停止 /// </summary> public void Pause() { if (videoPlayer.isPlaying) { videoPlayer.Pause(); } } /// <summary> /// シーク /// </summary> /// <param name="frameIndex">飛ばしたいフレーム</param> public void Seek(int frameIndex) { if (frameIndex > 0 && frameIndex <= (int)videoPlayer.frameCount) { videoPlayer.frame = frameIndex; } }
コマ送り・コマ戻し(おまけ)
こちらは先ほどのVideoPlayer.frameでシークする機能を応用すればコマ送りなどの機能も簡単に実装できるということです。
(ちなみにコマ送りは専用の関数が用意されています)
/// <summary> /// コマ送り /// </summary> public void ForwardFrame() { // 念のため一時停止 Pause(); var currentFrame = (int)videoPlayer.frame; // フレームの整合性はシークの関数で行う Seek(currentFrame + 1); } /// <summary> /// コマ戻し /// </summary> public void BackFrame() { // 念のため一時停止 Pause(); var currentFrame = (int)videoPlayer.frame; // フレームの整合性はシークの関数で行う Seek(currentFrame - 1); }
サムネイルの設定
スクリプトから再生する動画を選択してRenderTexureもスクリプトで作成している場合は、表示するRawImageのTextureが再生されるまで更新されないためサムネイルが表示できない問題があります。
サムネイルの設定はデフォルトで入っているものはないです。そして設定方法も割りと強引というかスマートな方法が私の調べた限りではありませんでした。
以下の強引な方法でサムネイルを表示させます。
public void SetThumNail() { Play(); Pause(); // サムネイルにしたいフレームも設定可能(ここでは100フレーム目をサムネイルに設定) Seek(100); }
ほんと強引ですよね。でもこれしか思いつきませんでした。笑
各種イベントの設定
やっと本題なのですが、私がVideoPlayerを用いたときに動画のフレームが更新されるたびに何かしらの処理を行う必要がありました。そのためupdate()などを駆使してすごいめんどくさいことを行っていたんですが、実はVideoPlayerにはもともと最低限必要そうなイベントがちゃんとあります。
ここではサンプルとして3つ紹介します。イベント登録などがわからない方は参考にしていただければと思います。
書き方としては登録したいイベントに+=で関数を追加してあげることでできます。登録した場合はOndestoryなどのタイミングでキチッと-=をします。
動画が最終フレームに到達したとき
videoPlayer.loopPointReached += OnLoopPointReached;
動画の再生準備が出来たとき
videoPlayer.prepareCompleted += OnPrepareCompleted;
フレームが更新したとき
videoPlayer.frameReady += OnFrameReady; // この設定をしないとFrameReadyのイベントは発火されないので注意 videoPlayer.sendFrameReadyEvents = true;
こちらなんですがsendFrameReadyEvents を設定しないとイベントが飛びませんので注意です。 またこれはフレームのインデックスも取得できるので、動画のシークバーを連動させたい場合などはこれをつかうと実装ができます。
private void Awake() { videoPlayer.loopPointReached += OnLoopPointReached; videoPlayer.prepareCompleted += OnPrepareCompleted; videoPlayer.frameReady += OnFrameReady; // この設定をしないとFrameReadyのイベントは発火されないので注意 videoPlayer.sendFrameReadyEvents = true; } /// <summary> /// 動画が最終フレームに到達したとき /// </summary> /// <param name="vp"></param> private void OnLoopPointReached(VideoPlayer vp) { // 例として、自動で一時停止させる videoPlayer.Pause(); } /// <summary> /// 再生準備が完了したとき /// </summary> /// <param name="vp"></param> private void OnPrepareCompleted(VideoPlayer vp) { SetThumNail(); } /// <summary> /// 表示するフレームの準備が完了したとき /// </summary> /// <param name="vp"></param> /// <param name="frameIndex"></param> private void OnFrameReady(VideoPlayer vp,long frameIndex) { // 再生・シークなど更新が入るたびに叩かれる関数 // たとえばスライダーだと(slider.maxValueが1の場合) // slider.value = (float)frameIndex / (float)vp.frameCount } private void OnDestroy() { videoPlayer.loopPointReached -= OnLoopPointReached; videoPlayer.prepareCompleted -= OnPrepareCompleted; videoPlayer.frameReady -= OnFrameReady; }
こんな感じで最低限動かせるサンプルを作ったのでUnityProjectが見たい場合は見てみてください。
【Unity】iOSのCollectionViewみたいなUIを作成
更新からまた結構あきました。 Unityから始めたはずが、近頃はSwiftでの開発ばっかりでほとんどUnity触っていませんでした。 iOSアプリをXCode使って開発していたんですがUIめちゃくちゃきれいに作れて感動しています。 そこでiOSであったけどUnityのUIにはないものを作成しようかと思います。
その名はCollectionViewです。 コレクションビューとは要はスマホのアルバムアプリの写真一覧のような配置をしてくれるUIです。 要素が多くなっても等間隔で配置してスクロール可能なあれです。
プロジェクト
作成したものは以下のURLにあるのでダウンロードして使ってみてください。 ※動作にUniRXというアセット使っています。無料ですので使う場合はインポートしてください。
使い方は今後ReadMeを拡充して行こうかなと思います。 とりあえず 表示するものなんですが、それはCellPrefでプレハブを作っているのでそこを編集すればカスタマイズ可能です。 あと初期機能としてはハイライトしているかどうかのフラグもつけています。
バグとかあれば直す予定です。
キャプチャー画像
下の画像のように設定すると こんな感じで出力してくれます。
iOSだったら普通にあるんですけどUnityだとないみたいなものが多いのでUIはほんまUnity困りますよね。
【Unity】3Dキャラクターの表情を変更して操る
投資の記事を更新して、全然ブログ更新できていないなぁと思ったので、久しぶりに更新します。
本日は3Dキャラクターの表情を変化させるような仕事を行ったので、備忘録的に書いておこうと思います。
で、これは設定されているとめちゃくちゃ簡単に操ることが出来ます。
で、最初にざっくりと説明すると、3Dキャラクターには基本的にはSkinnedMeshRendererというコンポーネントが入っています。そのSkinnedMeshRendererのBlendShapesの設定値をいじることで表情を変化させることが出来ます。
SkinnedMeshRendererについてざっくり
SkinnedMeshRendererは3Dキャラクターをアニメーションさせたときなどに3Dのメッシュ崩れなどがおきないように、いい感じに変形させるために必要なコンポーネントです。
たとえば、腕をひねったときにメッシュをいい感じにねじってくれます。腕のある地点から急にメッシュが変形するとかを防いでより人っぽく肌っぽく変形してくれます。
で、このSkinnedMeshRendererは体のパーツごとにコンポーネントがアタッチされています(多くの場合)。今回の表情を動かすためにはおそらく顔などにアタッチされているSkinnedMeshRendererを使います。
BlendShapeについてざっくり
今回の表情変更はこれがないと始まらないんですが、BlendShapesっていうのはSkinnedMeshRendererの中の1項目になります。BlendShapeは3Dモデルを作る際に設定されていないと使えないので、これが設定されていないモデルは今回表情を動かせません。
で、BlendShapeはたとえば口の形の「あ」・「い」・「う」・「え」・「お」などをプリセットで設定されていて、その値たちをうまくいじることで口の形をいろいろ変えていきます。たとえば
「あ」の成分の口の形50%
「い」の成分の口の形20%
「お」の成分の口の形80%
みたないな感じで
この画像はユニティちゃんの口のSkinnedMeshRendererです。
これはSMILE1・SMILE2みたいな感じで設定できます。
これの顔のSkinnedMeshRendererを見つけて、BlendShapesが設定されていることが確認できれば以下のサンプルのように動かすことが可能です。
サンプル
これはSetBlendShapeWeight(0, 100.0f)で即時反映されますが、この値をじょじょに変化させると自然に表情を変化させることが可能になります。
今回は我ながら言葉足らずな記事だと思うけど一旦これで終わります。
株式投資年間成績(2019.5~2020.5)
1年間のまとめ
これで株式投資をはじめて2年経ったということか。
まぁチャートとかみても去年1年とは見え方も違ってきたような気がするし、感情のコントロールもすこしできるようになったかなといった感じです。
まずは結果
280,000(現時点入金総額)→406,575(+126575)
去年が+74439だから、この一年単体だと+52136ということですね。
少し引き出してしまって元手が280,000になってしまいました。
そのため利益の率ではなくて額で見たほうが良いです。
額で言うと1万ほど落ちていますが、退場せずに利益を出せているということで大勝利なのではないでしょうか。この地合いで。
この一年はいろいろありました。でかいので言うと
米中貿易摩擦
アメリカ・イランの国交問題
コロナ
正直このイベントたちで私はパフォーマンスを出すことが出来なかったです。
この一年の教訓は
・ダブル印旛はさっさと処理する
・損切りはきちんとする
こんなもんですかね。ただコロナは損切りしなかったら戻って含み益まで行ったので運だけは良かったようです。ほんとにいろいろと株はわけのわからない値動きをしますね。いまもコロナガン無視でぶち上げています。ほんとよくわからないです。
去年はなんか利益でて調子乗ったことを書いていたけど、正直退場していないだけで上々ですとつくづく思うようになってきました。
来年も退場せずに踏ん張っていられるように、ブログを更新できるようにこの一年もがんばります。
【Unity】スクリプトのpublic・privateって何?使い分けは?
最近初心者の頃に疑問に思ったことが常識になり始めてブログの本来の目的を見失いかけている気がします。
ですので今回は記事のボリュームはありませんが、私が最初に結構疑問に思ったスクリプトの最初にあるprivateとかpublicについて最低限Unityで使う上で必要な情報を説明したいと思います。
はじめたばかりのときにスクリプトの最初の変数定義は全部publicで私は行っていました。これは非常に危うさを含んだことということに時が進むにつれて知ることになって行きました。
でははじめにpublic以外に何が使えるのかということを説明します。
だいたいUnityでは以下の3つを覚えておけばよいです。
では1つずつ説明したいと思います。
- Public
UnityでInspectorで欄が出てきて設定できる。
他のスクリプトからアクセス可能となる、参照できる。
- Private
UnityでInspectorで欄が出てきて設定できない。
他のスクリプトからアクセス不可となる、参照できない。
- [SerializedField] Private(変数のみ)
UnityでInspectorで欄が出てきて設定できる。
他のスクリプトからアクセス不可となる、参照できない。
では
UnityでのInspectorの欄で設定とはなんでしょうか?
それはこういうことです
ここに欄が出来て数値やオブジェクトを設定できるようになるということです。
そう、あると便利な奴です。 [SerializedField]を知らなかった頃の私は、インスペクタに表示して設定したいがためにPublicを乱用していました。
ただPublicは便利なのですが他のスクリプトからもアクセス可能なので非常に危うさを含みます!
他のスクリプトからもアクセス可能となる、参照できるってなに?
これの何が怖いって他のスクリプトから書き換えることが出来ちゃうということです。
Unityに慣れてくるとスクリプトの数が多くなります。それは機能ごとにスクリプトを別々に作り始めることが理由に挙げられます。たとえばRPGならば、現在のプレイヤーのステータスだけを管理するスクリプト、レベルアップ処理だけを管理するスクリプト、所持金だけを管理するスクリプトなどと分けていきます。これはわかりやすさや、修正のしやすさが増すなど様々なメリットがあるためです。
しかしこうなってくると無考慮のPublicは危うさが増してきます。
例えばRPGとかのレベルっていろんな要素で使われますよね。ステータス管理したり、進行可能場所の制限をしたり、購入可能アイテムを制限したり・・・
なのでレベルというのは様々なスクリプトで参照したくなるわけです。ただこれをPublicにしておくと、どこかで書き換えられてしまう危険性が出てくるわけです。
レベルが予期せぬところで書き換わるってとんでもないバグですよね・・・
ということでPulicは使い分けをしっかりしないといけないということです。
複数人で開発なんてしている場合とかだともっと危険度は増しますからね。
どう使い分けるのか?
いろいろ書いてきましたが変数においての使い分けは簡単です。
public
- 他で参照しないなら絶対に使わない。
- 理由がない場合極力避ける
private
- とりあえずこれ
[SerializedField] Private
- Inspectorで直接オブジェクトや数値などを入れ込みたい
(おまけ)他のスクリプトで参照しないとだめな重要な変数があるんですけど・・・
たとえば先ほどの例にあったレベルに応じて進行可能場所の制限したいからレベルは重要な変数だけどpublicしないとだめなんだよ!とかなる可能性ってありますよね。
こんなときはpublicにしてもいいか?私の答えはpublicはつかっちゃダメです!レベルは重要すぎます!どうにかして回避する方法を考えましょう。関数が増えたとしても!
じゃあどうすれば良いのか、そういうときはレベル管理しているスクリプトのレベルはprivateにしたままで、進行可能か判断するスクリプトから、レベルのスクリプトに対して現在のレベルが進行可能なレベルに達しているかどうか聞く関数をレベル管理側に作ってOKかNGかbool型で進行スクリプトに返してあげることで回避することができますよね!
こんな感じで重要な変数は絶対にpublic使うのを避けることを心がけましょう!