Unityの画像の扱いがOpenCVと異なり困った話

プログラム

視線認識をしようと思いPythonで書かれたサンプルコードをunityに移植しようとしていたときに困った話です.最初は,モデルの変換が間違ってるのかなとか,カラーフォーマットが違うのかな,など見当外れな方向で迷っていました.そこで原点に帰って,インプットの画像を1ピクセルずつ比較してみると,全然値が違っていて困惑しました.結論としては自身がUnityを何も理解してなかっただけなのですが,画像の原点が左下にあるか左上かの違いでした.

問題のコード

以下のコードの出力結果が一致すると思っていまいました.しかし,結果を見る通り全然違いましたw.

import cv2


def main():
    frame = cv2.cvtColor(cv2.imread("test_face.png"), cv2.COLOR_BGR2RGB)
    s = "img pixel data\n"
    for i in range(10):
        for j in range(10):
            s += f"({i},{j}){frame[i][j]},"
        s += "\n"
    print(s)


if __name__ == "__main__":
    main()

C#の方はインスペクターで画像をtest_face_spriteに突っ込みます.

using UnityEngine;
namespace MynameSpace
{
    public class TestImageInput : MonoBehaviour
    {
        public Sprite test_face_sprite;

        void Start()
        {
            var texture = test_face_sprite.texture;
            var height = texture.height;
            var width = texture.width;
            texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0);
            texture.Apply();
            Color[] pixels = texture.GetPixels();
            string s = "img pixel data\n";
            for (int y = 0; y < 10; y++)
            {
                for (int x = 0; x < 10; x++)
                {
                    var pixel = pixels[y * width + x];
                    s += $"({y},{x})[{(int)(pixel.r * 255)},{(int)(pixel.g * 255)},{(int)(pixel.b * 255)}],";
                }
                s += "\n";
            }
            Debug.Log(s);

        }
    }
}

実行結果

OpenCVの方はこんな感じです.いい感じですね.

Unityの方はこんな感じです.はっ????

原因

原因はシンプルで,そもそも原点がOpenCVの場合画像の左上が(0,0)になります.
しかし,Unityの場合は左下が(0,0)になります.以下の画像みたいな感じです.

一見Unityはキモキモだなと思います.
ですが,自分なり解釈すると,OpenCvでは画像を単なる二次元配列だと捉え,Unityは現実のモデルをとして扱っているのだと考えました.
上に行けばプラスとういのは現実世界では当たり前なわけで,何なら一貫性があって美しいのではとも思えてきます.
また,本筋とは別になりますが,何でOpenCVはRGBの順じゃなくてBGR何でしょうね?初めて使った頃はこれで悩んだことがありますw

正しいコード

以上を踏まえると,画像を左上から読んでいけばいいわけです.
そのためy=heightから1行ずつデクリメントしていけばOpenCVと同じように扱うことができます.Unity側のコードを治すと以下のようになります.

using UnityEngine;
namespace MynameSpace
{
    public class TestImageInput : MonoBehaviour
    {
        public Sprite test_face_sprite;

        void Start()
        {
            var texture = test_face_sprite.texture;
            var height = texture.height;
            var width = texture.width;
            texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0);
            texture.Apply();
            Color[] pixels = texture.GetPixels();
            string s = "img pixel data\n";
            for (int y = height - 1; y >= height-10; y--)
            {
                for (int x = 0; x < 10; x++)
                {
                    var pixel = pixels[y * width + x];
                    s += $"({y},{x})[{(int)(pixel.r * 255)},{(int)(pixel.g * 255)},{(int)(pixel.b * 255)}],";
                }
                s += "\n";
            }
            Debug.Log(s);

        }
    }
}

結果はこんな感じです.

いい感じですね.

結論

OpenCV:左上が(0,0)
Unity:左下が(0,0)
これだけ覚えておけば大丈夫です.

コメント

タイトルとURLをコピーしました