ねののお庭。

かりかりもふもふ。

2つの平面(領域)間の対応をとる方法。(C#で射影変換)

お品書き

  • 2平面間の対応
  • 射影変換ってなんぞ
  • ライブラリ作った。(C# / .NET Standard2.0)
  • 可視化してみる
  • まとめ

2平面間の対応

はい。よくあると思います。

  • Canvas座標値と、そこの表示している地図画像の実世界上の値の対応(画素でnピクセルのところは実世界でmメートルなど)
  • 実世界平面上の値をUnityの空間に落とし込んだり

とか。かといって対応させたい領域が必ずしも綺麗な矩形ではなかったり、とか。 こういうタスクってたくさんあると思うのです。(みなさまどうしていらっしゃるのでしょう...) そこで2平面間の対応を考えるのですが、射影変換とかいうのがあります。 画像処理だとよく使うのですが、画像処理以外にもいろいろと使えるので便利しています。

射影変換ってなんぞ。

射影変換の見た目は非常に簡単で、式はこんな感じです。 Hは3x3の行列で、9個のパラメータを持っています。 Pのx,yがもともとの平面上の座標、X,Yが変換後の座標です。Pに変換行列を書けると、もともとの平面上の座標を入れてかけると、出力の平面上の座標が得られる、というだけ。で、問題はこのHをどうやって求めるかなのかですが、これは入力平面と出力平面の対応点がそれぞれ4つあればHを推定することができます。この行列を推定する理屈の詳細はここでは省きますが、詳しくはこちらこちらが分かりやすいと思います。

ライブラリ作った。(C# / .NET Standard2.0)

この変換行列を求める実装、C#からだとOpenCVSharpとかにあるにはあるんですけど、たかだか変換行列もとめるためだけに大きめのバイナリ突っ込まれるのもあれだし、ネイティブのdllが必要だったりするので良くないです。なので変換行列求めるためだけのクラスライブラリ作りました。nugetに登録してあるので

dotnet add package HomographySharp

とか叩くと使えるようになるはず。 nuget->HomographySharp GitHub->HomographySharp 行列計算はC#の行列演算ライブラリを使っています。 使い方は簡単で、入力平面と出力平面の対応が4点以上あれば良くて、

var srcList = new List<Point2<double>>(4); // or Point2<float>
var dstList = new List<Point2<double>>(4);

srcList.Add(new Point2<double>(10, 10));
srcList.Add(new Point2<double>(100, 10));
srcList.Add(new Point2<double>(100, 150));
srcList.Add(new Point2<double>(10, 150));

dstList.Add(new Point2<double>(11, 11));
dstList.Add(new Point2<double>(500, 11));
dstList.Add(new Point2<double>(500, 200));
dstList.Add(new Point2<double>(11, 200));


//射影変換行列を求めて
var homo = HomographyHelper.FindHomography(srcList, dstList);

//入力平面から出力平面上の座標に変換
Point2<double> result = homo.Translate(homo, 100, 10);
Assert.IsTrue(Math.Abs(result.X - 500) < 0.001);//true
Assert.IsTrue(Math.Abs(result.Y - 11) < 0.001); //true

といった具合です。class HomographyHelperが二つのstatic関数、

  • 変換行列の推定を行う関数
  • 入力平面から出力平面上の座標に変換する関数

を持っている感じです。

可視化してみる。

といろいろ御託をなべても、実際にどういう風な変換をされるのか見てみないと信頼できないと思うので、可視化するプログラムも組みました。 GitHub->HomographySharp/HomographyVisualizer 4点の対応を取ると、動かしている点が、入力平面(最初に選んだ領域)の点が出力平面(あとに選んだ領域)と対応している様子が分かると思います。

まとめ

  • 2次元と2次元の座標の対応とるなら射影変換がそれなりに便利。
  • ライブラリっぽいものを作ってみた。
  • nugetにアップロードする実績を解放した。

関係ない独り言

可視化するプログラム、Rx版と普通(?)の書き方の両パターンで書いてみたけどRx素晴らしい。