カメラのピント情報から物体までの距離を測れるかどうか調べました。
LiDAR等のARKitの機能はバッテリー消費量が大きいので、それらに頼らないで距離を測ってみたかったというのが動機です。
<完成イメージ>
結論は**『1mくらいまでならおおよその距離測定はできそう。ただし、前提条件は多い。』**です。
以下、焦点距離の取得からレンズの公式を使った撮影対象までの距離の測定方法を解説します。
1. 動画撮影時の焦点距離の取得方法
焦点距離はカメラの内部パラメーターから取得します。
内部パラメーターは AVCaptureVideoDataOutputSampleBufferDelegate
のデリゲートを通して取得した CMSampleBuffer
の中から取り出せます。
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
は次のような行列です。
\begin{bmatrix}
fx & 0 & O_x \\
0 & fy & O_y \\
0 & 0 & 1
\end{bmatrix}
fx
、fy
が焦点距離で、単位はピクセルです。
ピクセルが正方形でない場合にも対応するため縦横それぞれの方向の値があります。正方形の場合は fx = fy です。
手持ちのiPhone12Pro,iPhone6s,iPhone8plus,iPhoneXR で試したところ全てピクセルサイズは正方形でした。
なお、IntrinsicMatrixを取得するには予め次のようにAVCaptureConnection
に対して設定が必要です。
if captureConnection.isCameraIntrinsicMatrixDeliverySupported {
captureConnection.isCameraIntrinsicMatrixDeliveryEnabled = true
}
isCameraIntrinsicMatrixDeliverySupported
でIntrinsicMatrixの取得が可能かどうかチェックし、取得可能であればisCameraIntrinsicMatrixDeliveryEnabled
にtrue
を設定します。
2. レンズ焦点距離を計算
2−1) レンズの公式
前述のIntrinsicMatrixで焦点距離を取得できますが、これは撮影時に変動します。
ここでレンズの公式を引用します。
\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があり、これを使うことで無限遠にできます。
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
を計算し、その値の2倍(1m換算)して、逆数をとれば1pxあたりのサイズ[m]が求められます。
\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) 対象にフォーカスを当てる
測定ではカメラが画像のどの部分にフォーカスを当てているのか知る必要があるので、ここでは強制的に画面の中央にフォーカスを合わせるようにします。
次のコードで画面中央にフォーカスします。
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の焦点距離の確らしさを確認するため、試しに遠くの山に焦点を合わせてみました。
14kmくらいの距離にある山ですがレンズポジションが小さいです(もっと1.0に近くなると思った)。焦点距離も1362.42→1383.95と異なります。
ここで得られた値をレンズ焦点距離f
としてピクセルサイズを計算すると3.37μmとなり、この値で測定し直します。
5-2) 測定結果(見直し後)
実際の距離[m] | 測定結果[m] | |
---|---|---|
0.1 | 0.09 | ![]() |
0.3 | 0.27 | ![]() |
0.5 | 0.45 | ![]() |
1.0 | 1.03 | ![]() |
1割程度のズレにまで値が改善されました。
レンズポジションを設定した無限遠の焦点距離よりも、遠くを撮影した時の焦点距離の方がレンズの公式を前提とした場合、より正確と言えそうです。
ちなみに、何度か測定していると徐々に値が変動していき結果は安定しません(温度か?)。
また、1mを超えると値が小さい方にずれていくような現象もみられました。
6. 最後に
以下、焦点距離から対象の距離を測定する上での前提条件のまとめです。
- キャリブレーションをするという前提(対象の測定前に遠距離と既知の近距離を測定しておくというステップを踏む)
- 不安定な動作でも許容するという前提。2割程度のズレは許容する必要がある。
・・・せっかく調べたので記事にしましたが使う局面は。。。