C#
OpenCV
Unity
OpenCVforUnity

[OpenCV for Unity]MatToTexture周りが沼だった話

概要

OpenCVは神(挨拶)
そのOpenCV様と我らがUnityをつないでくれるのがこちら OpenCV for Unityです。

自分の周囲ではわりと使っている人が多く、
安定しているんだなーと思っていたのですが、
いきなり沼にはまってしまいました。

具体的には、
WebCamTextureから画像情報を取得⇒表示しようとしたら
以下のように画像が青く染まったり、そもそも正しく表示されなかったりしました。

image.png image.png

この記事を書いた理由

厄介なことに、この現象はエラーが出ないため、
どこで発生しているのかパッと見でわかりません。

そのため、同じような問題で悩む人が減るように、
ネットの海に情報を残しておくことにしました。

結論から

MatToTextureでは、
・Textureに対して指定した型
・Matに対して指定した型
・Matが実際に取り込んだデータの型
(+MatからTextureへの変換で指定した関数の実装)
これらの3要素+1が複雑に絡み合って、
エラーの出ない問題を発生させていました。
この周辺をうまく調整してあげる必要があります。

沼の一歩目:fastMatToTexture2D

上記の中で+1としておまけ扱いしていますが、
MatからTextureへの変換で指定した関数として、
以下の2つに関わる問題を最初に取り上げます。

public static void fastMatToTexture2D (
    Mat mat,
    Texture2D texture2D,
    bool flip = true,
    int flipCode = 0,
    bool flipAfter = false)
public static void matToTexture2D (
    Mat mat, 
    Texture2D texture2D, 
    Color32[] bufferColors)

とりあえずどちらかを使うとなれば、
「Fast(早い)」の方を使うと思います。
もともとリアルタイム処理専用というわけではないOpenCVなので、
Unityで実行するための夢と工夫が詰まっていると期待して。。。。

ええ、これが沼の入り口です

それぞれの関数で同じ画像の同じピクセルを変換するとわかりますが、
以下のような結果になります。

R G B A
Mat 12 17 13 255
fastMatToTexture2D 0.067 0.051 1.000 0.047
matToTexture2D 0.173 0.204 0.216 1.000

色要素をbyteからfloatに変換するときはbyte/255=floatで計算するわけですが、
どういうわけかfastMatToTexture2Dの計算が合いません。

実装を読みに行くとわかりますが、
matToTexture2Dは内部でmatの中身の型とTexture型を照合し、
ある程度の範囲で変換の問題を解決するように実装しています。

しかし、fastMatToTexture2Dではそうした照合を行わず、
ざっくりtexture2D.LoadRawTextureDataにぶち込んでいます。

最初にお見せした青い画像がその時のものですが、
「OpenCV 青い」とかで検索すると、
直接的に関係のない問題の解決策とつながったりして非常に危険です。

image.png

問題がややこしくなる原因の一つはこれで、
fastMatToTexture2Dで表示できないからと色々試して、
BGRAとRGBA、ビッグエンディアンとリトルエンディアンの違いなんかを疑いだし、
気が付けば沼の中に踏み込むわけです。

とりあえず、fastMatToTexture2Dで表示されない場合は、
以下の「フォーマットの要素単位の確認」を加えたうえで、
matToTexture2Dに書き換えて表示されるかどうかを確かめた方がよいでしょう。

沼の二歩目:TextureFormatと色空間変更コード

問題の概要

色空間変更コードとは、cvtColor()関数で以下のように変換前と後を指定する定数です。
Imgproc.cvtColor(rgba_mat, bgra_mat, Imgproc.COLOR_BGRA2RGBA);
そして「結論」の方でも言っていますがこれも沼につながっていて、
OpenCVのMatとUnityのTextureFormatが一致していないと正しい画像が出ません。

あまり多くの組み合わせは試していませんが、
こちらのような画像が出たりして、像を結ばないこともあります。
image.png

で、前述していますがmatToTexture2Dは「ある程度の範囲で変換の問題を解決する」実装になっています。
つまり、ある程度以上の問題は解決してくれません。

これが厄介で絡み合った二つの問題、
・フォーマットの要素順序(RGBA/BGRA/HLS...)
・フォーマットの要素単位 (8bit4color/8bit3color/2bit3color....)
これらを自力で切り分けて解決する必要があります。

フォーマットの要素単位の確認

フォーマットの要素単位の問題についてはわりと希望があって、
matToTexture2Dの実装を見ると、
TextureFormatとCvTypeを見てビット数の検証を行ってくれています。

ただ面倒なのは、
「一致しない場合は読み取り処理をしない」という回避だけの実装なので
エラーで教えてくれたりはしないところです。

個人的には、Utilの371行目から始まる形式確認のif文にelseを追加し、
「一致しない場合は例外を投げる」ように、ちょっと改造するのがおすすめです。
こうすると要素単位が間違っていた時はわかるようにできます。

フォーマットの要素順序の確認

こちらに関してはOpenCV for Unityの方でもたぶん対処が難しくて、
「事前に安全な変換を確定させる相互変換のサンプルを作っておく」
「そのうえで取得した画像が正しく表示されるまで列挙」とか、
改めて問題の切り出しと確認を行えるように準備しておく必要があると思います。

このあたり、自分の手元のデータではいくつかモンキーテストしたらうまく変換できてしまったので、
これ以上掘り下げるつもりは(今のところ)ありません。
誰か適切な対処法とかを知っていたらコメントしてもらえると助かります。

まとめに

OpenCVは神。
ただし、人間とコンピュータと神が正しく意思疎通ができるようになるのは、
まだもうちょっと時間が必要みたいだなーと思いました。
(うまいこと言ったつもり)