OpenCVのremapで変換後の画像の座標に対して元の画像での座標を得る方法について
前置き
OpenCVのremap関数について、たまに使うのですが使うたびに思い出せなくなる処理があるので、自分の頭の整理も兼ねて分かりやすい形にして置いておきます。また、今回はC++の文法で書いてますが、Pythonでも全く同じです。
問題設定
設定として、image画像をmap_xとmap_yを用いてtarget画像に変換するとします。そこからtarget内の点(例えば特徴点)をいくつか取得したとして、そのx座標の列をtarget_x、y座標の列target_yとします。今回の目標は、target_xおよびtarget_yに対応するimage側の座標であるところのimage_xとimage_yの取得です。
結論と数学的根拠
結論としては以下になります(些末な宣言、型変換、フラグの処理の記述などは省略しています)。
cv::remap(map_x, image_x, target_x, target_y);
cv::remap(map_y, image_y, target_x, target_y);
まず、image画像をmap_xとmap_yを用いてtarget画像に変換するという処理はcv::remap(image, target, map_x, map_y)と書けます。
これを配列を「座標を入れると値を返す関数」として捉えて数学的に記述すると、
\mathrm{target}(i, j) := \mathrm{image}(\mathrm{map\_y}(i, j), \mathrm{map\_x}(i, j))
としてtargetを定義していると考えられます。なお、正しくは線形補間などの処理が入っているため厳密には異なるのですが、今回は抽象的に捉えるためにその辺りは捨象します。
先程の結論も同様に数学的に記述すると、
\begin{align}
\mathrm{image\_x}(i) := \mathrm{map\_x}(\mathrm{target\_y}(i), \mathrm{target\_x}(i)) \\
\mathrm{image\_y}(i) := \mathrm{map\_y}(\mathrm{target\_y}(i), \mathrm{target\_x}(i))
\end{align}
になります。
一般にtarget上の座標(x1, y1)とimage上の座標(x2, y2)が対応していることは$x_2=\mathrm{map_x}(y_1, x_1)$かつ$y_2=\mathrm{map_y}(y_1, x_1)$と記述できるので、この時点で既に(target_y[i], target_x[i])が(image_y[i], image_x[i])と対応していると分かるのですが、念の為に以下の様な計算もできます。
\begin{align}
& \mathrm{image}(\mathrm{image\_y}(i), \mathrm{image\_x}(i)) && \\
=& \mathrm{image}(\mathrm{map\_y}(\mathrm{target\_y}(i), \mathrm{target\_x}(i)), \mathrm{map\_x}(\mathrm{target\_y}(i), \mathrm{target\_x}(i))) && (\because \mathrm{image\_x}と\mathrm{image\_y}の定義) \\
=& \mathrm{target}(\mathrm{target\_y}(i), \mathrm{target\_x}(i)) && (\because \mathrm{target}の定義)
\end{align}
これはtarget上の座標(target_y[i], target_x[i])に対応するimage上の座標が(image_y[i], image_x[i])であることを表しています。
直感的な解説
先ほど配列は「座標を入れると値を返す関数」だと言いましたが、map_xとmap_yについては他の考え方として「変換後座標を参照すると変換前座標値が得られるテーブル」とも考えられます。そのイメージでremap関数の処理を言語化すると「変換テーブル(x座標)と変換テーブル(y座標)をテーブルとして変換前画像の画素値を求める処理」であると言えます。変換手続きは以下のようなイメージです。
これがremap関数による画像変換の基本となります。逆に言えば、この様な処理であればremap関数を用いて実装可能であるということです。
また、target_xとtarget_yは整数値に対して変換後座標値を返す関数ですね。
そして求めたいのはimage_xとimage_y、つまり整数値に対して変換前座標値を返す関数です。
これは以下のような方法で求めることができます。
この処理は、「変換後x座標列と変換後y座標列をテーブルとして変換テーブルの画素値を求めている」と言えます。これは先述したremap関数の処理ほぼそのままですよね。図で見ても同じ様な処理を行っていることが分かると思います。そのままこの処理をremap関数を使って書いたものが以下の結論となる訳です。
cv::remap(map_x, image_x, target_x, target_y);
最後に
実際は型変換だとか、リマップしたら座標が画像の外に行っただとか細かい調整が必要なようです。その辺りは自分はちゃんと確認できていないので、自分の裁量で変なことにならないように注意して実装してください。





