透視投影変換及び視野角/画角(FoV)の計算について

# 0. 概要
3次元空間上のものを画面に投影する際に、ゲームであっても現実のカメラであっても透視投影変換といったものを行う。これにより3次元空間上のものを2次元に射影している。今回はこれについて簡単に説明していく。

# 1. 透視投影変換
3次元空間上の点を透視投影変換を用いて2次平面に投影する。この時用いるカメラはピンホールカメラとする。

透視投影変換は簡単に以下のように定義されている。

``` math
sm'=A[R|t]M'
```

具体的には以下の行列で表現される。

``` math
s
\begin{bmatrix}
u \\
v \\
1
\end{bmatrix}=
\begin{bmatrix}
f_x & 0 & c_x \\
0 & f_y & c_y \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
r_{11} & r_{12} & r_{13} & t_{1} \\
r_{21} & r_{22} & r_{23} & t_{2} \\
r_{31} & r_{32} & r_{33} & t_{3}
\end{bmatrix}
\begin{bmatrix}
X\\
Y\\
Z\\
1
\end{bmatrix}
```

これは<b>世界座標→カメラ座標→画像座標</b>へと順に変換している。
単位系は任意である。多くの場合、ピクセル単位である。

各項を以下で説明する。

## 画像座標
画面上に投影された点座標は以下である。

``` math 
m' =\begin{bmatrix}
u \\
v \\
1
\end{bmatrix}
```

各変数は以下である。

- $u$: 画像上の横軸$x$
- $v$: 画像上の縦軸$y$

3次元モデルに2次元のテクスチャを張る際に`uvマッピング`を展開するはずである。この時の`uv`である。

## 内部パラメータ行列(カメラ行列)
カメラ座標系から画像座標系へ変換する行列を内部パラメータ行列またはカメラ行列と呼んでいる。

``` math 
A =
\begin{bmatrix}
f_x & 0 & c_x \\
0 & f_y & c_y \\
0 & 0 & 1
\end{bmatrix}
```

各変数は以下である。

- $f_x$: 横軸の焦点距離
- $f_y$: 縦軸の焦点距離
- $c_x$: 横軸の画像中心からの距離
- $c_y$: 縦軸の画像中心からの距離

カメラ画像を拡縮する場合、この全てのパラメータを同じだけ拡縮する必要がある。
この行列はビューに依存しないため、基本的に定数として扱うことができる。
また、画角を計算するときなどにも使うことが出来る。

## 外部パラメータ行列
世界座標系からカメラ座標系へ変換する行列式が$[R|t]$である。

``` math
[R|t]M'=\begin{bmatrix}
r_{11} & r_{12} & r_{13} & t_{1} \\
r_{21} & r_{22} & r_{23} & t_{2} \\
r_{31} & r_{32} & r_{33} & t_{3}
\end{bmatrix}\begin{bmatrix}
X\\
Y\\
Z\\
1
\end{bmatrix}
```

ここで$[X, Y, Z, 1]$はワールド座標系の3次元座標を表している。

ワールド座標系にある3次元の点をカメラ内の座標に変換する必要がある。
そこで、カメラの姿勢(回転)及び移動(並進)を掛けてワールド座標系をカメラ座標系に移動させる処理を行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95636/d1e5d96f-0a12-f0db-dd98-679ca8694d7b.png)
[引用元](https://mem-archive.com/2018/02/17/post-74/)


行列式の<b>回転成分が$R$、並進成分が$t$となる。</b>
換言すると、まず世界座標を回転させてカメラと同じ姿勢にした後に、位置を動かすといった処理を行う。

``` math
[R|t]M'=
\begin{bmatrix}
X_{camera}\\
Y_{camera}\\
Z_{camera}\\
\end{bmatrix}
=
\begin{bmatrix}
r_{11} & r_{12} & r_{13} \\
r_{21} & r_{22} & r_{23} \\
r_{31} & r_{32} & r_{33}
\end{bmatrix}\begin{bmatrix}
X\\
Y\\
Z\\
\end{bmatrix}
+
\begin{bmatrix}
t_{1}\\
t_{2}\\
t_{3}\\
\end{bmatrix}
```

# 2. その他
透視投影変換の一部を用いた計算を整理する。

## カメラ行列を用いた視野角(FoV)を計算
カメラの画角は`解像度`及び`焦点距離(ピクセル)`もしくは`イメージセンサのサイズ`及び`焦点距離(mm)`から得られる。

カメラ行列を再掲する。

``` math 
A =
\begin{bmatrix}
f_x & 0 & c_x \\
0 & f_y & c_y \\
0 & 0 & 1
\end{bmatrix}
```

FoVは以下で得られる。

``` math
fov_x = 2 \cdot arctan \dfrac{w}{2 \cdot f_x}\\
fov_y = 2 \cdot arctan \dfrac{h}{2 \cdot f_y}\\
fov_d = 2 \cdot arctan \dfrac{\sqrt{w^2 + h^2}}{f}
```

単位が`pixel`の時$w, h$は解像度となる。`mm`の場合は、イメージセンサーのサイズとなる。


### 単位系の変換`px`->`mm`
焦点距離が`pixel`で与えられることが多いが、`mm`の単位系に戻したいことも多い。
ずは`1 pixel`が、イメージセンサの何`mm`に対応するか考える必要がある。

```math
w^{sensor}_{per_m} = \dfrac{w^{mm}}{w^{px}}
```

純粋にセンササイズを解像度で割れば1ピクセルがイメージセンサの何mmに対応しているかが分かる。
よって、以下のように単位系を変換することができる。

``` math

f^{mm}_{x} = w^{sensor}_{per_m} \cdot f^{px}_{x} = \dfrac{w^{mm}_{x}}{w^{px}_{x}} \cdot f^{px}_{x} \\
f^{mm}_{y} = w^{sensor}_{per_m} \cdot f^{px}_{y} = \dfrac{w^{mm}_{y}}{w^{px}_{y}} \cdot f^{px}_{y} \\
```

ここで、変数は次のように定義される。

- $f^{px}$: 焦点距離[px]
- $f^{mm}$: 焦点距離[mm]
- $w^{px}$: 解像度[px]
- $w^{mm}$: イメージセンサーサイズ[mm]

### なぜ横軸、縦軸のFoVがあるのか
FoVは縦軸も横軸も直感的には同じような気がする。
しかし工学製品は製造誤差により、縦軸及び横軸の受光素子及び画素に変換する回路から多少の誤差が生じる。

よって、本来なら一致するはずの以下の式が実製品では成立しない。

```math
\dfrac{w^{n}_{x}}{w^{px}_{x}} = \dfrac{w^{n}_{y}}{w^{px}_{y}}
```

ここで、$w^{n}$は実センサの受光素子の数とする。
このため、純粋にアスペクト比と画角が一致しないことが起こる。