画像解析をUnityでやってみよう。5回目です。
前回までで、WebCamTextureを使用して、カメラ画像を全画面表示するところまでできました。
今回はいよいよOpenCVを使用して、カメラの映像を変換してみます。

OpenCVforUnityをインポートする

AssetStore
OpenCVforUnityの公式サイト


今気づいたんですが、公式サイトが綺麗になっている。。というかこんなのあったっけ??
デモとかチュートリアルが豊富になっていますね。。
僕が触っていた時はこんなのなかった!!!はず。。(4ヶ月前くらい)
まあいいや。
とりあえずインポートやセットアップの方法は、公式に詳しく載っていたので、そこを参考にインポートします。

OpenCVはオープンソースですが、OpenCVforUnityは有料です。有料といっても、幅広くサポートしてくれていて、Util機能も結構用意されています。
基本はOpenCVで用意されている関数をC#でラップしたような作りになっているので、OpenCV知っていれば、それなりに使えるようになるのは早そうです。

あと更新もかなり頻繁に行ってくれています。UnityでOpenCVを使うなら、これが一番良さそうな感じです。
OpenCVのサポート状況も書かれています。
サポート状況

早速試す

インポートできたら、早速試します。
ただ、どこにどのような機能があるのかは、正直使ってみて慣れてきた感じです。ラップされている関数を探す時(どこに定義されているとか)は、OpenCVの関数名で探すと、大体出てきます。
中でライブラリ呼んでいる事がほとんどなので。。
画像の変換や、線分検出などは、ほとんどImgprocクラスにあります。
Imageprocクラス

グレースケールに変換してみる

ではとりあえず、画像処理の前処理としてよくやるやつをやってみます。グレースケールへの変換です。
グレースケールにする時は、

Imgproc.cvtColor(src, dst, Imgproc.COLOR_RGB2GRAY);

この一行だけで、画像がグレースケールになります。OpenCVすごい。


src,dstは、変換元の画像と、変換後の画像を表します。これはOpenCVのMatという形式で渡す必要があります。(OpenCVforUnityでMatというクラスが用意されています。)
とりあえずこれを使ってやってみると。。

おおおー。いい感じ。

2値化してみる

同じように、よく使う2値化処理もしてみます。
2値化をするには、閾値処理というものを使用します。

Imgproc.threshold(mat, mat, 150, 255, Imgproc.THRESH_BINARY);


ここではTHRESH_BINARYという処理を使いました。これは、閾値を超えるピクセルを最高値にし、それ以外を0にするものです。
例では、150以上の値ピクセルをすべて255(白)にして、それ以外を0(黒)にし、2値化をしました。
閾値処理についての参考ページ
ブラーをかけたり、もっといろいろな処理ができるのですが、OpenCVについて、ちょっとだけ解説します。

Matに慣れよう

Matは、OpenCVで基本となるクラスです。何かの処理を行うと、大体Matで返してきます。
MatはMatrix、つまり行列です。OpenCVでは、画像をMatで表します。
また、チャンネルという概念があり、例えばRGB要素ごとに情報を抜き出したりもできます。
Matについて

使ってみた感想としては、正直クセが強く、コードを見ただけでわからない時も多々ありました。
例えば、画像のRGB要素からRの情報を持ってくる時に、MatがRGB情報かどうかを知る術がなく、コードを書いた側が記憶していないといけません。
さらにRの情報だけ抜き出したかったら、intでどのチャンネルを指定するとか。。。結構コード汚くなります。
2値化するときも、グレースケール画像を渡してくださいとAPIに書いてあったりとか。。コードではすべてMatなので、渡せてしまうし、実行できてしまいます。
なのでちょっと使う時に気をつける事が多いなあ。。と思います。が、使っていれば大体慣れてきます。
ラップして使いやすいようにしちゃうのもありだと思います。

MatからTextureに変換する

UnityでOpenCVで変換した画像を表示するには、MatからTextureに変換する必要があります。
そのあたりはすべてOpenCVforUnityのUtilクラスでやってくれます。
OpenCVforUnityではすでにMatからTextureまたはTexture2D,およびその逆変換をUtilとして提供してくれています。(WebCamTextureからMatもありました。)
Utilクラス

Mat mat = new Mat(texture.height, texture.width, CvType.CV_8UC3);
Utils.texture2DToMat(texture, mat);        

こんな感じで書くと、TextureからMatに変換してくれます。
余談ですが、TextureToMatなど、Texture系はduplicateされたみたいです。
ちょっと前までは使えてた気がするけど。。
その代わり、TextureToTexture2Dのような変換が用意されてました。
これを使えば内部で変換をやってくれます。
ということで、UnityのTextureからOpenCVのMatに行ったり来たりしながら解析を行う必要があります。

カメラ画像変換の流れ

シーケンス図
OpenCVを使えば、画像をMatに変換して、適切なメソッドに渡してしまえば、内部で処理して返してくれます。なんて便利なんだ。
こちらでやる事は、OpenCVで変換したMatを、Unity側に反映するだけです。
なので、WebCamTextureから画像を取得して、その画像をMatに変換してなんやかんや処理して、またTextureに戻してRawImageに反映する。をUpdateで繰り返し行えば、カメラ映像に対して変換処理ができます。
動画でもわかるとおり、グレースケールや2値化程度なら動作にそこまで影響は出なかったです。

気をつける事

カメラ映像を更新する際に気をつける事が3つありました。

MatとTextureのサイズ、チャンネル数を合わせる

MatからTextureに変換する時には、サイズを合わせる必要があります。
サイズがあっていないと、エラーになります。
また、Matのチャンネル数も重要で、カラー画像からの変換なら3チャンネルにしておいた方が良いです。

mat = new Mat(tex.height, tex.width, CvType.CV_8UC3);

こんな感じ。最後でチャンネル指定してます。
CvTypeはやたらと数があり、正直僕もまだよくわかっていない部分が多いです。
符号があるか、整数かどうか。などを指定するみたいですが、とりあえず整数でやってます。
使い分けは今のところよくわかってません。。。ここもOpenCVのわかりづらいところ。。
OpenCVの型情報まとめ

Textureのキャッシュに気をつける

TextureはUnityが内部でキャッシュするようで、どんどん過去のTextureのメモリが残り続けて落ちるという事がありました。
Textureを作りまくるので、破棄しているかどうかには気をつけなければなりません。
明示的にTextureを破棄する方法を参考に適切なタイミングで破棄するようにしました。

カメラの更新タイミングで処理をする

UnityのUpdateで上記のカメラ画像変換の流れを行うと、毎フレーム処理が走ります。
これは意図した動きではありません。
なぜかというと、WebCamTextureを初期化した際に、FPSを指定しているからです。

webCamTexture = new WebCamTexture(Screen.currentResolution.width, Screen.currentResolution.height, 30);

ここで30と指定しました。
つまり30フレームに一回更新が走るようになっているという事です。
カメラの更新が走っていないのに変換処理をするのはとても無駄なので、WebCamTextureの更新が行われたかどうかをUpdate内でみてます。
カメラが更新されていたら画像処理するようにしました。
WebCamTexture#didUpdateThisFrame






ということで、今回はカメラ映像をOpenCVを使って変換処理をかけました。
次回は写真をとって、五線譜の解析をやってみたいと思います。
それではまた次回。さようなりー