歪曲収差の実装

歪曲収差

レンズを通した光が,像となるときに歪んでしまう収差のことです。 大雑把に言えば魚眼レンズみたいな歪みです。

https://en.wikipedia.org/wiki/Distortion_(optics)
http://www.nikon-instruments.jp/jpn/learn-know/microscope-abc/learn-more-microscope/about-aberration

格子状のグリッドを撮影したときに,樽型に歪んだり(樽型),中央に収縮するように歪んだり(糸巻き型)します。

半径方向歪み

Brownのレンズ歪みモデルなるものがレンズ歪みを表すモデルとして使われることが多いようです。

https://www.asprs.org/wp-content/uploads/pers/1971journal/aug/1971_aug_855-866.pdf
http://www.roboken.iit.tsukuba.ac.jp/~ohya/pdf/Robomech2014-KNS.pdf

Brownのレンズ歪みモデルでは,レンズの光軸中心からの半径方向の歪みと,円周方向の歪みをわけて表現されますが, まずは半径方向歪みについて考えます。

光軸中心を{(c_x,c_y)}として,光軸中心からの像の位置を{{\bf \bar{r}}=(\bar{x},\bar{y}) = (x-c_x,y-y_c)}とします。 この時,歪んだ像の位置{{\bf r}_d = (x_d,y_d)}は,距離の2乗の多項式の展開で表せるとしたものが,多項式モデルと言うそうです。

{ \displaystyle
\begin{eqnarray}
{\bf r}_d = \Big(1 + k_1 r^2 + k_2 r^4 + k_3 r^6+ \cdots \Big) {\bf \bar{r}}
\end{eqnarray}
}

一方,OpenCV等の画像処理ライブラリでは,カメラキャリブレーションに,有理関数モデルを利用しているようです。

http://opencv.jp/opencv-2svn/cpp/calib3d_camera_calibration_and_3d_reconstruction.html
https://www.robots.ox.ac.uk/~vgg/publications/2005/Claus05a/claus05a.pdf

{ \displaystyle
\begin{eqnarray}
{\bf r}_d = \frac{1 + k_1 r^2 + k_2 r^4 + k_3 r^6+ \cdots}{1 + k_4 r^2 + k_5 r^4 + k_6 r^6+ \cdots} {\bf \bar{r}}
\end{eqnarray}
}

他にも半径方向歪みのモデルには,有理関数モデルの特殊化として{k_4}以外0としたDivisionModel )http://www.robots.ox.ac.uk/~vgg/publications/papers/fitzgibbon01b.pdf) とか, 魚眼モデルとかFOVモデルといった多項式で表せないものもあります。
ちなみに,UnityのPostProcessingStackの歪曲収差は,樽型のときにFOVモデルを利用しているようなコードでした。

https://pdfs.semanticscholar.org/260d/bcca2edddeb7f743d186cc99a7d586023234.pdf
http://www.close-range.com/docs/Straight_Lines_Have_To_Be_Straight-Automatic_Calibration_and_Removal_of_Distortion--Devernay-Faugeras2001.pdf

今回はOpenCVのカメラキャリブレーションにも使われている実績や,計算の簡単さから4次までの有理関数モデルをレンズ歪みに利用することにします。

{ \displaystyle
\begin{eqnarray}
{\bf r}_d = \frac{1 + k_1 r^2 + k_2 r^4}{1 + k_3 r^2 + k_4 r^4} {\bf \bar{r}}
\end{eqnarray}
}

円周方向歪み

レンズによる光学的な歪みではなく,レンズとフィルムが完全に並行ではない場合に生じうる幾何学的な歪みが円周方向の歪みの項です。

{ \displaystyle
\begin{eqnarray}
\Delta x_t = \Big( 2p_1\bar{x} \bar{y} +p2(r^2 + 2\bar{x}^2)\Big) \Big(1+ p_3r^2 + \cdots\Big) \\
\Delta y_t = \Big( p1(r^2 + 2\bar{y}^2) + 2p_2\bar{x} \bar{y}\Big) \Big(1+ p_3r^2 + \cdots\Big) 
\end{eqnarray}
}

この項の{p_3r^2}以降を無視して,先程のレンズ歪みモデルに組み込みます。

{ \displaystyle
\begin{eqnarray}
{\bf r}_d = \frac{1 + k_1 r^2 + k_2 r^4}{1 + k_3 r^2 + k_4 r^4} {\bf \bar{r}} + 
\left[
\begin{array}{cc}
2p_1\bar{x} \bar{y} +p2(r^2 + 2\bar{x}^2) \\ 
p1(r^2 + 2\bar{y}^2) + 2p_2\bar{x} \bar{y} \\
\end{array}
\right]
\end{eqnarray}
}

結果

歪曲収差の式をUVに適用してテクスチャのフェッチする位置を歪めることで実装します。

半径方向歪みが正の値の場合(UVフェッチ時逆転なので,本来の計算式的には負),歪みが外に膨らんで行くので樽型の歪曲収差を作れます。

f:id:hikita12312:20180407140007p:plain:w300
歪曲収差 OFF
f:id:hikita12312:20180407140049p:plain:w300
歪曲収差 ON (樽型)

ただし,樽型の場合,画面内に元のテクスチャよりも外側のUVもフェッチされてしまうので,画面端の方が不正確なものになります。

f:id:hikita12312:20180407140123p:plain:w300 f:id:hikita12312:20180407140049p:plain:w300

そのため,歪曲収差を適用した後のUVを画面中心から拡大することで,見せたくない画面外側の範囲をクリップしても良いでしょう。

f:id:hikita12312:20180407140654p:plain:w300 f:id:hikita12312:20180407140631p:plain:w300

半径方向歪みが負の値の場合は,糸巻き型となります。

f:id:hikita12312:20180407140007p:plain:w300
歪曲収差 OFF
f:id:hikita12312:20180407140815p:plain:w300
歪曲収差 ON (糸巻き型)
f:id:hikita12312:20180407140854p:plain:w300 f:id:hikita12312:20180407140815p:plain:w300

樽型と糸巻き型の歪みが組み合わさったものは陣笠型と呼びますが, これもパラメータの組み合わせで半径方向の歪みが距離によって正負入れ変えるような関数系をつくって再現できます。

f:id:hikita12312:20180407141119p:plain:w300 f:id:hikita12312:20180407141052p:plain:w300

円周方向の歪みのパラメータを与えると,画面自体が奥に傾いたような歪みを作り出すこともできます。

f:id:hikita12312:20180407141454p:plain:w300 f:id:hikita12312:20180407141433p:plain:w300

実装してみると,思っていたよりも画面端の違和感が強いので, 周辺減光や, 倍率色収差をかけると, よりレンズっぽく画面端をごまかせるかと思います。

f:id:hikita12312:20180407141747p:plain:w600