どうもー、Reveです。
先月は全然ブログを更新できなかったので、今月はちょくちょく記事を上げていきたいですね。
とりあえずUnityでOpenCVの続きです。
目指せ月2桁更新!!
【前回のおさらい】
この記事を参照。
前回は、画像をグレースケールに変換する手法を書いてました。
・グレースケールのMatを用意
・テクスチャ素材をグレースケールにしてコピー
【二値化処理】
今回は、前の記事で行った処理を元に、「二値化処理」というのを施してみましょう。
この二値化処理とは、濃淡のある画像を、一つの閾値を境目に白と黒の階調、つまりモノクロ画像に置き換える処理のことです。大体、元の画像をいったんグレースケール(灰色で濃淡だけを表した画像)にしてからこの処理が施されます。
(参照)
http://ipr20.cs.ehime-u.ac.jp/column/gazo_syori/chapter4.html
さて、なぜ二値化処理をしてモノクロ画像を取得するのかというと、輪郭の抽出や文字の判定処理など後の画像処理が容易になるからです。処理する対象とそれ以外の部分を切り離すのに使われるオーソドックスな処理と言えるでしょう。
(この辺りは、電子回路のアナログとデジタルの話にも繋がる気がします。ちなみに、白黒をどちらにするかという話は正論理か負論理かと考えると、電子回路に詳しい方はわかりやすいかも)
この処理で重要なのは、やはり白黒を分ける閾値の設定で、
様々な研究が行われていろいろな手法が提案されてはいますが、OpenCVでは理論や数式を完璧には覚えてなくてもこれらの手法が使えるよう便利なメソッドが用意されています。
【二値化処理を試そう】
Unityプロジェクトは、前回の記事で作成したものを使います。
まず前回作ったシーンを開き、シーン内にあるGlayScaleというオブジェクトを複製して隣に移動させましょう。
その際は複製したオブジェクトを別名にするとよいでしょう。
(オブジェクトは「Ctrl + D」キーで複製すると便利です)
そこで複製したオブジェクトにつけられたGlayScaleScript.csをいったんはずし、新しいC#スクリプトを作ります。
オブジェクトのインスペクタ(Inspector)のボタン「Add Component」で「BinaryThresholdScript」と名前を入力して、新しいスクリプトの生成画面に入るので、「C Sharp」スクリプトにして生成します。
続けて、スクリプトの編集に入ります。
スクリプトの中身は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
; title="BinaryThresholdScript.cs"]using UnityEngine; using OpenCVForUnity; public class BinaryThresholdScript : MonoBehaviour { /// <summary> /// リソースの読み込み先 /// </summary> public string texturePath = "test001"; /// <summary> /// 二値化の閾値 /// </summary> [SerializeField, Range(0, 256)] int ThresBinary = 128; /// <summary> /// 閾値処理の反転を行うかどうか /// </summary> [SerializeField] bool binInv = false; // Use this for initialization void Start () { //テクスチャ画像を読み込む Texture2D texture_src = Resources.Load(texturePath) as Texture2D; //テクスチャをMat画像へコピー Mat origin = new Mat(texture_src.height, texture_src.width, CvType.CV_8UC4); Utils.texture2DToMat(texture_src, origin); //画像処理用Mat画像 Mat mat_proc = new Mat(origin.rows(), origin.cols(), CvType.CV_8UC1); Imgproc.cvtColor(origin, mat_proc, Imgproc.COLOR_RGBA2GRAY); //------------------(今回追加した処理)-------------------- //2値化処理(反転する場合は Imgproc.THRESH_BINARY_INVを使う) if (ThresBinary < 0 || ThresBinary > 255) { //GaussianCによる適応的閾値処理 //adaptiveMethod: Imgproc.ADAPTIVE_THRESH_MEAN_C と分けて使う //thresholdType: Imgproc.THRESH_BINARY と分けて使う int type = (binInv) ? Imgproc.THRESH_BINARY_INV : Imgproc.THRESH_BINARY; Imgproc.adaptiveThreshold(mat_proc, mat_proc, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, type, 201, 11); //大津の手法による、単純2値化処理 //Imgproc.THRESH_OTSUを指定すると、thresの値に関係なく自動で閾値を設定する //Imgproc.threshold(mat_proc, mat_proc, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU); } else { int type = (binInv) ? Imgproc.THRESH_BINARY_INV : Imgproc.THRESH_BINARY; Imgproc.threshold(mat_proc, mat_proc, ThresBinary, 255, type); } //------------------(今回追加した処理)-------------------- //二値化処理したMatをテクスチャに変換 Texture2D texture_out = new Texture2D(mat_proc.cols(), mat_proc.rows(), TextureFormat.RGBA32, false); Utils.matToTexture2D(mat_proc, texture_out); //テクスチャの割り当て GetComponent<Renderer>().material.mainTexture = texture_out; } } |
御覧のように、前回のようなグレースケール化処理の後に二値化処理を加えていることがわかります。
今回の二値化処理をするには、Imgproc.thresholdメソッドを使います
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
; title="BinaryThresholdScript.cs"]//元画像(Mat):mat_procを使うとする //2値化処理メソッド //引数は、元画像(Mat)、出力先(Mat)、閾値(int)、濃淡の最大値(int)、オプション //オプションは、値の反転、適応的閾値処理を使うかなどを指定 Imgproc.threshold(mat_proc, mat_proc, ThresBinary, 255, Imgproc.THRESH_BINARY); //大津の手法による、単純2値化処理 //Imgproc.THRESH_OTSUを指定すると、thresの値に関係なく自動で閾値を設定する //Imgproc.threshold(mat_proc, mat_proc, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU); //2値化処理(反転する場合は Imgproc.THRESH_BINARY_INVを使う) //Imgproc.threshold(mat_proc, mat_proc, ThresBinary, 255, Imgproc.THRESH_BINARY_INV); //GaussianCによる適応的閾値処理 //引数は、元画像(Mat)、出力先(Mat)、濃淡の最大値(int)、適用する手法、オプション、ブロックサイズ(int)、定数(double) //adaptiveMethod(適用する手法)): Imgproc.ADAPTIVE_THRESH_MEAN_C と分けて使う //thresholdType(オプション): Imgproc.THRESH_BINARY_INV と分けて使う //ブロックサイズは偶数だとエラー //Imgproc.adaptiveThreshold(mat_proc, mat_proc, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 201, 11); |
そして、実行してみると下の画像のように、処理前の画像と処理後(グレースケール化)の画像が並べられます。
インスペクタから「Thres Binary」のスライダーを調整すると、処理結果が変わります(実行前に設定すること)。
また、勘の良い方ならお気づきかもしれませんが、今回作ったスクリプトは適応的閾値処理にも対応しています。
適応的閾値処理は簡単に言うと、画像のピクセルごとに明るいか暗いかを判別して、それぞれに適した閾値を取ることで抽出したい部分を綺麗に二値化する手法を指します。一定の閾値だと明るい部分や暗い部分などの差異を無視してしまい望ましい結果にならない場合も多いため、この手法が生まれました。
技術的な部分など詳細を知りたい方はこちらを参照。
(参照)
http://schima.hatenablog.com/entry/2013/10/19/085019
では、Unityのプロジェクトをもう一度操作します。
二値化処理用のオブジェクトをもう一つ複製して、今度はThresBinaryの値を最大(256)にしてみましょう。
オブジェクトを横に並べて、再びゲームを実行すると下のような結果になります。
やはり手動で二値化を設定する場合と比べて、絵の輪郭がわかりやすくなっています。
ただ、こちらの手法も内部のパラメータ調整は必須なので、場合によっては単純な二値化でもよいかもしれません。
状況に応じて使い分けるとよいでしょう。
ちなみに、UnityとOpenCVでこんなものを作ってみたので、ご覧いただけるとうれしい限りです。
コメント