オートフォーカスの実装

オートフォーカス

被写界深度表現を実装したので, 現実のカメラとかスマホにあるオートフォーカス機能を実装してみようと思います。
Depthテクスチャから中央付近のピクセルをサンプリングすれば,カメラから画面中心の物体までの距離が取得できるので, この距離に向かって滑らかにフォーカス距離を変化させれば実現できます。

PID制御

いろいろやり方はあると思うんですが,今回はPID制御を使ってみたいと思います。

https://ja.wikipedia.org/wiki/PID%E5%88%B6%E5%BE%A1

あるフレームでのフォーカス距離を{d_0(t)}であるとき, 測距された目標とするフォーカス距離を{d_{\mathrm{target}}(t)}とします。 この時1フレーム後,{\Delta t}だけ経過したときにどれだけ変化させれば良いかということを, PID制御の表式から以下のように定めます。

{ \displaystyle
\begin{eqnarray}
e(t) &=& d_{\mathrm{target}}(t) - d_0(t) \\\
\Delta d_0(t) &=& K_p e(t) + K_i \int^t_0 e(\tau) d\tau + K_d \frac{d}{dt}e(t) \\\
d_0(t+\Delta t) &=& d_0(t) + \Delta d_0(t)
\end{eqnarray}
}

要は,目標値までのフォーカス速度を,目標値との差{e(t)}の比例項{K_p},積分{K_i},微分{K_d}で定めようということになります。

これをコード上で表すとこんな感じです。

float PIDController::GetNextOperationValue(const float& value, const float& targetValue, const float& deltaFrameSec) noexcept
{
    const float e = targetValue - value;
    const float dedt = ((e - m_e0) / deltaFrameSec);
    m_Integral += e*deltaFrameSec;
    m_e0 = e;
    return (m_Kp * e + m_Ki * m_Integral + m_Kd*dedt);
}

対数空間でのPID制御

何も考えずにPID制御をそのまま実装すると,目標値やフォーカス距離によっては,一瞬だけフォーカス距離{d_0(t)}が0以下の負の値となってしまう場合があります。 負のフォーカス距離は当然未定義なので,その瞬間だけ被写界深度処理はバグってしまいます。 これを防ぐために,対数距離をを利用する修正を行いました。

{ \displaystyle
\begin{eqnarray}
e(t) &=& \log{\big(d_{\mathrm{target}}(t) \big)} - \log{\big(d_0(t)\big)} \\\
\Delta d_0(t) &=& K_p e(t) + K_i \int^t_0 e(\tau) d\tau + K_d \frac{d}{dt}e(t) \\\
d_0(t+\Delta t) &=& \exp{\bigr\{ \log{\big( d_0(t)\big)} + \Delta d_0(t)  \bigr\}}
\end{eqnarray}
}

対数空間なら,制御結果がいくら負になっても,0に漸近するだけとなります。 あと,対数空間でオートフォーカスを行ったほうが,なんとなく滑らかにフォーカスしていくような気がします。(気のせいかもしれません)

結果

f:id:hikita12312:20170905011832g:plain

まあまあ良いんじゃないでしょうか。 {K_p=4.0, K_i=6.0, K_d=0.01}の値を利用しています。この辺の値の決め方もいろいろあるらしいんですが,とりあえず勘です。

ちなみに,オートフォーカス処理だけでなく,露光処理を行うときにも, 距離の代わりに現在の露光値と平均輝度から計算される目標露光値を利用すれば,人間の目の明順応や暗順応のような表現を実現できます。