OpenCV for Unityの使い方講座２　Texture2DとMatの相互変換編

Unityの便利Assetである「OpenCV for Unity」を利用するにあたり初学者が躓きそうなポイントを解説していくシリーズ記事の第２弾です。

前回の記事は
[OpenCV for Unityの使い方講座１　エラーハンドリング編](https://qiita.com/utibenkei/items/83c172f91c9c2b2be19d)
です。

Assetの基本的なセットアップ方法などは[UnityでOpenCVを利用した顔検出・画像処理アプリ事始め](http://qiita.com/utibenkei/items/fb93623a7ccaabb342f2)を見てください。

##Unity Texture2DとOpenCV Matを相互変換する##

はじめに、Unityで画像処理を行う際のよくある処理の流れの一例を示します。

1. 処理対象の画像をTexture2D形式でUnity取り込む
2. **Texture2DをMatに変換**（OpenCVで処理できる形式にするため）
3. Matに対してOpenCVを使って画像処理をする
4. **MatをTexture2Dに変換**（Unityで表示できる形式に戻すため）
5. Texture2Dの画像を画面に表示

このようにUnityのTexture2D（もしくはWebCamTextureやRenderTextureの場合も共通）を一旦、OpenCVのMatに変換（正確には画素データ値のコピー）してからOpenCVのメソッドで画像処理をし、またTexture2Dに戻すという作業が必要になるわけですが、**OpenCV for Unityには付属のユーティリティクラスの中にTextureとMatを相互変換するためのメソッドが用意されています。**

記事の後半では、そのユーティリティメソッドの具体的な使用方法を説明しますが、その前に最低限理解しておいたほうが良いと思われる知識を説明します。

###UnityのTexture2Dクラスとは###

Texture2DはUnityにおける画像をハンドリングするためのクラスです。
Texture2D.GetPixels32()やTexture2D.GetRawTextureData()メソッドによって画素データの配列を取得することが可能です。（Texture2D.GetPixels32メソッドはテクスチャインポート設定でread/writeフラグを有効にしていないと機能しません）

###OpenCVのMatクラスとは###

OpenCVは画像処理や機械学習の機能を持つライブラリです。
OpenCVにはMatという基本となるクラスが存在します。これはその名前の通り、行列(Matrix)を扱うためのクラスになります。
計算したいデータをMat（行列）に格納することで、行列同士の演算や線形代数の問題を解くことができます。
同じように画像処理もMatを使用して計算を行うのですが、**OpenCVでは画像データを二次元の行列として扱います。**
たとえば512×512ピクセルの画像データなら、縦512×横512のMat（行列）を作成し各要素にはピクセルごとの色データが格納されているというイメージです。

**つまりOpenCVを使用して画像処理をするためには、画像の画素値データをOpenCVのMatクラスという入れ物に格納する必要があるというわけです。**

###画像の色空間とチャンネル数を合わせる###

Texture2DにもMatにも画像フォーマットによって色空間とチャンネル数の違いが存在します。

**色空間とは、カラースペースやカラーモデルとも呼ばれますが、色を数値などのパラメーターで表すための表現方式です。**例えばRGB形式では、赤（R）、緑（G）、青（B）の3色の諧調（0-255）により色を表現します。


**チャンネル数とは、色を構成する要素の数です。**例えばRGB形式ならR+G+Bで3チャンネル、RGBA形式ならR+G+B+A（Aはアルファチャンネル情報）で4チャンネル、グレー画像なら1チャンネルということになります。

**このように色空間とチャンネル数によってメモリ上の画像データの色の順番や総データサイズに違いが生じます。**

- サイズ512x512pxのRGB画像(符号なし8ビット整数型、3チャンネル)の総データサイズ = **512 * 512 * 3** = **786432byte**
- サイズ512x512pxのRGBA画像(符号なし8ビット整数型、4チャンネル)の総データサイズ = **512 * 512 * 4** = **1048576byte**
- サイズ512x512pxのgray画像(符号なし8ビット整数型、1チャンネル)の総データサイズ = **512 * 512 * 1** = **262144byte**
![opencv_tips2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28558/a53bf75c-8cee-317e-e71f-83a94848bdc4.jpeg)


たいていの場合は、OpenCV for Unityのユーティリティメソッドによってチャンネル数の違いを吸収して変換してくれますので問題は起こりにくいのですが、画像クラスのメモリ上のデータ配列を直接コピーするなどの際には総データサイズの違いが重要になりますので知識として知っておいたほうが良いでしょう。

また、変換する同士のTexture2DとMatで色空間が違うと意図しない色に変換されてしまうこともあるので注意が必要です。（たとえばBGRA形式のMatのデータをRGBA形式Texture2Dに入れてしまった場合はRとBの数値が入れ替わった変な色彩の画像になってしまう）
**画像の扱い方に慣れないうちは基本的にRGBA（符号なし8ビット整数型、4チャンネル）で統一するのが良いと思います。**

###Unity Texture2DとOpenCV Matの座標系の違い###

Texture2DとMatの座標系の違いも理解しておく必要があります。


**Texture2Dの座標系は左下が原点となります。**（メモリ上のデータは画像の左下のピクセルから順番に格納されている）
![opencv_tips2-UnityCoordinateSystem.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28558/a4f09eae-dc97-b1f3-ef6e-cf36cb4dbb42.jpeg)


一方、**Matの座標系は左上が原点となります。**（メモリ上のデータは画像の右上のピクセルから順番に格納されている）
![opencv_tips2-OpenCVCoordinateSystem.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28558/bd4dc255-2fa4-5806-ff4f-8b29bdc59445.jpeg)



このような座標系の違いがあるため、**Texture2DとMatの間で相互にデータをコピーする際には座標系を変換する（Y軸を反転する）必要があります。**





###OpenCV for Unityのユーティリティクラスを使用して相互変換する###

では実際にTexture2DとMatを相互変換するサンプルコードを例にOpenCV for Unity付属のユーティリティクラスの具体的な利用方法を解説します。
ユーテイリティクラスには様々な変換用のメソッドが用意されていますが、ここでは一番使用頻度が多いと思われるUtils.texture2DToMatメソッドとUtils.matToTexture2Dメソッドを使用しています。

```
// 元となる画像をTexture2D形式でロード
Texture2D srcTexture = Resources.Load ("lena") as Texture2D;

// Texture2Dと同じ大きさのMatを生成する（符号なし8ビット整数型、4チャンネル）
Mat imgMat = new Mat (srcTexture.height, srcTexture.width, CvType.CV_8UC4);

// Texture2DをMatに変換（第3引数がtureなので変換の後にMatのY軸反転され、座標系の違いを吸収する）
Utils.texture2DToMat (srcTexture, imgMat, true);

//
// このタイミングでMatに対して何らかの画像処理を行うことができる
// ここでは一例として文字を描画する
Imgproc.putText (imgMat, "W:" + imgMat.width () + " H:" + imgMat.height () + " SO:" + Screen.orientation, new Point (5, imgMat.rows () - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar (255, 255, 255, 255), 2, Imgproc.LINE_AA, false);
//

// Matと同じ大きさの表示用のTexture2Dを生成する（符号なし8ビット整数型、RGBA/4チャンネル）
Texture2D imgTexture = new Texture2D (imgMat.cols (), imgMat.rows (), TextureFormat.RGBA32, false);

// MatをTexture2Dに変換（第3引数がtureなので変換の前にMatのY軸反転され、座標系の違いを吸収する）
Utils.matToTexture2D (imgMat, imgTexture, true);

// Texture2Dの画像を画面上に表示
gameObject.GetComponent<Renderer> ().material.mainTexture = imgTexture;
```

####texture2DToMat系とfastTexture2DToMat系の違いについて####
二つのメソッドの実装を比較してみると、fast系のメソッドは画像クラスの生のデータ配列を直接コピーするという方法をとっているため、より高速に処理が可能という事のようです。加えて、画像同士のチャンネル数やサイズのチェックなども省略されています。
そのため、入力する画像の形式を十分に理解して使用する必要があるようです。
しかしtexture2DToMat系の方の実装でも、画像同士のフォーマットをチェックした上で可能ならば同様の処理を行うようになっているので、多くの場面では処理速度はほとんど変わらなと思います。
**画像クラスの扱いに慣れないうちは、ある程度のフォーマットの違いを吸収してくれるのでtexture2DToMat系を使用するほうが安全でしょう。**

#まとめ#

**OpenCV for Unityを使用して画像処理をするためには、Texture2D（またはWebCamTexture） -> Mat -> Texture2Dのように、一旦Mat形式にデータを変換する必要がある。
OpenCV for Unityにはその変換処理を補助するためのユーティリティクラスが用意されている。
画像同士の色空間とチャンネル数の違いに注意する。
Unity Texture2DとOpenCV Matでは座標系が違うので注意する。**