iPhone 動画撮影時のレンズ焦点距離と撮影対象までの距離

　カメラのピント情報から物体までの距離を測れるかどうか調べました。
　LiDAR等のARKitの機能はバッテリー消費量が大きいので、それらに頼らないで距離を測ってみたかったというのが動機です。
＜完成イメージ＞
![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/6643bc8c-217b-bbf4-9b7f-1ee9a3f37bc2.gif)

結論は**『1mくらいまでならおおよその距離測定はできそう。ただし、前提条件は多い。』**です。

以下、焦点距離の取得からレンズの公式を使った撮影対象までの距離の測定方法を解説します。

https://github.com/tamai999/CameraIntrinsicParameter

#### 1. 動画撮影時の焦点距離の取得方法

焦点距離はカメラの内部パラメーターから取得します。
内部パラメーターは `AVCaptureVideoDataOutputSampleBufferDelegate` のデリゲートを通して取得した `CMSampleBuffer` の中から取り出せます。

```swift
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        
    guard let parameterData = sampleBuffer.attachments[.cameraIntrinsicMatrix]?.value as? Data else { return }
    parameterData.withUnsafeBytes() {
        let intrinsicMatrix = $0.load(as: matrix_float3x3.self)
    }
```

取得した内部パラメータ`intrinsicMatrix` は次のような行列です。

```math
\begin{bmatrix}
fx & 0 & O_x \\
0 & fy & O_y \\
0 & 0 & 1
\end{bmatrix}
```

`fx`、`fy` が焦点距離で、単位は**ピクセル**です。
ピクセルが正方形でない場合にも対応するため縦横それぞれの方向の値があります。正方形の場合は fx = fy です。

:::note info
手持ちのiPhone12Pro,iPhone6s,iPhone8plus,iPhoneXR で試したところ全てピクセルサイズは正方形でした。
:::

なお、IntrinsicMatrixを取得するには予め次のように`AVCaptureConnection`に対して設定が必要です。

```swift    
if captureConnection.isCameraIntrinsicMatrixDeliverySupported {
     captureConnection.isCameraIntrinsicMatrixDeliveryEnabled = true
} 
```

`isCameraIntrinsicMatrixDeliverySupported`でIntrinsicMatrixの取得が可能かどうかチェックし、取得可能であれば`isCameraIntrinsicMatrixDeliveryEnabled`に`true`を設定します。


#### 2. レンズ焦点距離を計算

##### 2−1) レンズの公式

前述のIntrinsicMatrixで焦点距離を取得できますが、これは撮影時に変動します。
ここで[レンズの公式](https://ja.wikipedia.org/wiki/レンズの公式)を引用します。

```math
\frac{1}{a} + \frac{1}{b} = \frac{1}{f} 
```

対象までの距離が`a`、結像する距離が`b`、レンズ焦点距離が`f`です。
IntrinsicMatrixで取得できる値は`b`の「結像する距離」でピントを合わせる過程で変わります。
例えば、50cm先の床をiPhone12Proの広角レンズでFull HD(1920x1080)撮影すると次のような値になります。

|撮影対象までの距離[m]|結像する距離b[px]|
|:-:|:-:|
|0.5|1397.00|

##### 2-2) 無限遠にフォーカス

知りたいのは物体までの距離`a`ですが、そのためにはまず固定値であるレンズ焦点距離`f`を得る必要があります。ただ、この`f`を直接APIから取得する方法は見つかりませんでした。

そこで、撮影対象までの距離`a`が無限遠の場合の`b`の値を求めることで（レンズの公式の左辺第1項がゼロとなるので） b = f として`f`を求めます。

iOSには`レンズポジション`を設定するAPIがあり、これを使うことで無限遠にできます。

```swift
var device: AVCaptureDevice
// 焦点を無限遠に設定。0.0~1.0。
device.setFocusModeLocked(lensPosition: 1.0) { _ in
}
```

レンズポジションが1.0のときが無限遠です。
サンプルプログラムの実行結果は次のとおりです。

|撮影対象までの距離[m]|焦点距離f[px]|
|:-:|:-:|
|∞|1362.42|

#### 3. ピクセルの物理サイズを計算

レンズ焦点距離`f`、結像する距離`b`は得られるので、レンズの公式により対象までの距離`a`が得られます。ただ、単位がピクセルなので、単位をメートルにします。

すでに、0.5m先の対象を撮影した時の`b`があるので、ここから`a`を計算し、その値の２倍（１ｍ換算）して、逆数をとれば1pxあたりのサイズ[m]が求められます。

```math
\displaylines{
\frac{1}{a} + \frac{1}{1397.00} = \frac{1}{1362.42}\\
\\
a = 55,040.51\\

\frac{1}{2a} = 0.00000908422
}
```

結果はおよそ 9μmです。この値を求めた`a`にかければ対象までの距離が得られます。
同様にiPhone12Proの広角レンズで4K(3840x2160)で撮影すると f = 2724.43、0.5m離れた時の b = 2793.70 でピクセルサイズは 4.6 μm でした。
撮影する解像度によりピクセルサイズが変わり、反比例の傾向があるようです。

#### 4. 実測

そではどのくらい正確に距離を把握できるか試します。

##### 4−1) 対象にフォーカスを当てる

測定ではカメラが画像のどの部分にフォーカスを当てているのか知る必要があるので、ここでは強制的に画面の中央にフォーカスを合わせるようにします。

次のコードで画面中央にフォーカスします。

```swift
var device: AVCaptureDevice

try device.lockForConfiguration()
            
device.focusMode = .autoFocus
// 画面左上が(0,0) 右下が(1.0,1.0)なので中央を指定
device.focusPointOfInterest = CGPoint(x: 0.5, y: 0.5)
            
device.unlockForConfiguration()
```

##### 4−2) 測定結果

|実際の距離[m]|測定結果[m]|
|:-:|:-:|
|0.1|0.18|
|0.3|0.37|
|0.5|0.48|
|1.0|0.63|

かなりずれていておおよその距離、、としても使えないですね。

#### 5. レンズ焦点距離の見直しと測定

##### 5-1) 無限遠の計測のし直し

レンズポジション=1.0の焦点距離の確らしさを確認するため、試しに遠くの山に焦点を合わせてみました。

![far.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/856d06f5-01bf-e606-c070-56f4133332e6.png)

14kmくらいの距離にある山ですがレンズポジションが小さいです(もっと1.0に近くなると思った)。焦点距離も1362.42→1383.95と異なります。

ここで得られた値をレンズ焦点距離`f`としてピクセルサイズを計算すると3.37μmとなり、この値で測定し直します。

##### 5-2) 測定結果（見直し後）

|実際の距離[m]|測定結果[m]||
|:-:|:-:|:-:|
|0.1|0.09|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/a67b0ff8-fab1-668c-604c-bfecff0ce4e3.png" width=300px>
|0.3|0.27|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/d97b6879-472a-3372-d58f-a118220e9d93.png" width=300px>
|0.5|0.45|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/79b7f63b-b402-0514-b71d-542748f48ea3.png" width=300px>
|1.0|1.03|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/9878a11f-8f1b-5e78-c2c1-e1de1fe5b636.png" width=300px>

１割程度のズレにまで値が改善されました。
レンズポジションを設定した無限遠の焦点距離よりも、遠くを撮影した時の焦点距離の方が**レンズの公式を前提とした場合**、より正確と言えそうです。

ちなみに、**何度か測定していると徐々に値が変動していき結果は安定しません（温度か？）。**
また、1mを超えると値が小さい方にずれていくような現象もみられました。

#### 6. 最後に

以下、焦点距離から対象の距離を測定する上での前提条件のまとめです。

1. キャリブレーションをするという前提（対象の測定前に遠距離と既知の近距離を測定しておくというステップを踏む）
1. 不安定な動作でも許容するという前提。２割程度のズレは許容する必要がある。

・・・せっかく調べたので記事にしましたが使う局面は。。。