カメラの一画素あたりのサイズを推定

# 目的
[カメラのセンサーサイズを紙と定規とEXIFで計算してみよう](https://pota-run.blogspot.com/2016/10/measuring-camera-sensor-size.html)を見ていて、
スマホならともかく、カメラだと長さ測るの無理じゃないと思って考えてみた。

# やったこと
1. 印刷したArUcoマーカーを10cm前後ずらして撮影
1. 下記値を使って1Pixelあたりのサイズを算出
   1. 印刷したマーカーのサイズ[cm]
   1. それぞれのマーカーのPixelサイズ[px]
   1. 使用したレンズの焦点距離


# 結果
カタログで計算した値より小さくなってしまいました。
マーカーの位置が適当だったので、その影響かもです。
（マーカーが正面向いていないとか、レンズ中心じゃないと式が成り立たないとか）
あと、近いので実効焦点距離も影響しているかもです。

* カタログから算出: 0.003756946[mm](サイズ: 17.3mm×13.0mm, 画素数: 4608×3456より)
* 測定値から算出: 0.00363212270430662[mm]

# 詳細
## マーカーの準備
適当にマーカを生成し印刷。サイズを測る。ここでは対角線の長さを測った(特に意図はない)。
角は測りにくかったので、辺の長さ(5.075cm)を測って計算。

```python
import cv2
from cv2 import aruco
dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
marker = aruco.drawMarker(dictionary, 4, 112)
dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
img_mark = aruco.drawMarker(dict_aruco, 4, 180)
cv2.imwrite("aruco_maker.png", img_mark)
```

![aruco_maker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455423/e3fd7d94-89d0-3f0c-8673-a6f9ce2b1e61.png)


## マーカーを撮影
適当にマーカーを配置し複数枚撮影。その後10cm移動させて再度撮影。

### サンプル

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455423/0e36c41f-1877-eb46-3152-ddf732aee8a1.jpeg">
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455423/e842e9a6-6397-e143-3ded-6028b9a8bdc3.jpeg">

### Pixelサイズを算出
撮影した画像を適当に配置。対角線のピクセル数を算出。何となく対角線2つを算出して平均。

```python
import cv2
from cv2 import aruco

import matplotlib.pyplot as plt
from glob import glob
import os
import os.path
import numpy as np

dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
parameters = aruco.DetectorParameters_create()


for fn in glob("image_sensor/*.jpg"):
    # print(fn)
    img = cv2.imread(fn)
    # plt.imshow(img[...,::-1])
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)
    if ids is None:
        continue        
    
    cornerUL = corners[0][0][0]
    cornerUR = corners[0][0][1]
    cornerBR = corners[0][0][2]
    cornerBL = corners[0][0][3]

    center = [ (cornerUL[0]+cornerBR[0])/2 , (cornerUL[1]+cornerBR[1])/2 ]
    l1 = np.sqrt(np.sum((cornerUL - cornerBR) ** 2))
    l2 = np.sqrt(np.sum((cornerUR - cornerBL) ** 2))
    print(fn, center, (l1 + l2) / 2)
    
    img_markers = aruco.drawDetectedMarkers(img.copy(), corners, ids)
    # plt.imshow(img_markers[...,::-1])
    
    dn, bn = os.path.split(fn)
    bn, ext = os.path.splitext(bn)
    ofn = os.path.join(dn, "marker", f"{bn}_marker{ext}")
    os.makedirs(os.path.dirname(ofn), exist_ok=True)
    cv2.imwrite(ofn, img_markers)
```

複数撮った対角線の長さを平均。
近い距離の写真複数撮ったつもりだけど、結局2枚しかなかった・・・

 * 近いとき```p1: 1797.411194```
 * 遠いとき```p2: 1089.5224```


```
image_sensor\P5260067.JPG [2260.5, 2003.0] 1797.4017333984375
image_sensor\P5260068.JPG [2260.5, 2002.0] 1797.420654296875
image_sensor\P5260071.JPG [2271.5, 1961.5] 1090.2025146484375
image_sensor\P5260072.JPG [2273.0, 1960.0] 1089.5416259765625
image_sensor\P5260076.JPG [2272.0, 1960.5] 1089.177001953125
image_sensor\P5260077.JPG [2272.5, 1960.5] 1089.5181884765625
image_sensor\P5260078.JPG [2272.0, 1960.5] 1089.5185546875
image_sensor\P5260079.JPG [2272.5, 1960.5] 1089.176513671875
```

### 撮像時の関係
<dl>
  <dt>L</dt><dd>撮像した物体。今回はマーカーのサイズ</dd>
  <dt>d</dt><dd>マーカーを移動させた距離。今回はカメラを10cm移動させたけど</dd>
  <dt>f</dt><dd>焦点距離。今回は14mm</dd>
  <dt>$a_1$, $a_2$</dt><dd>レンズから物体までの距離</dd>
  <dt>$b_1$, $b_2$</dt><dd>レンズから像(=イメージセンサー)までの距離。<br>
この絵ではレンズ位置は固定だけど、実際はレンズが動いてイメージセンサ上に像ができているはず</dd>
  <dt>$l_1$, $l_2$</dt><dd>像の大きさ。$p_1, p_2$と比例するはず</dd>

</dl>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455423/3baf5ada-e669-7b7d-6a8d-21877f0059d8.png)

### 図から下記関係を導出
変数は6個なので、1つ式余分だけど。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455423/5d3b4675-fd1a-61ca-11ed-8479a1e93ed2.png)



### sympyで解く

```python
import sympy as sy
sy.init_printing(use_unicode=False, wrap_line=True)

l1, l2, a1, a2, b1, b2, p1, p2, L, f, d = sy.symbols('l_1 l_2 a_1 a_2 b_1 b_2 p_1 p_2 L f d')


eq1 = sy.Eq(l1/l2, (b1-f)/(b2-f))
eq2 = sy.Eq(1/f, 1/a1 + 1/b1)
eq3 = sy.Eq(1/f, 1/a2 + 1/b2)
eq4 = sy.Eq(l2/L, b2/a2)
eq5 = sy.Eq(l1/L, b1/a1)
eq6 = sy.Eq(p1/p2, l1/l2)
eq7 = sy.Eq(a2, a1 + d)



dec = {f: 1.4, p1: 1797.411194, p2: 1089.5224, d: 10, L: sy.sqrt(2 * 5.075 ** 2)}
eqs = [eq1, eq2, eq3, eq4, eq5, eq6, eq7]
eqs = [e.subs(dec) for e in eqs]

sol = sy.solve(eqs)
for n,v in sol[0].items():
    display(sy.Eq(n, v))
print(f"1Pixelあたりのサイズ: {(l1/p1).subs(dec).subs(sol[0])}mm")

```

```
𝑎1=16.7911519610804 
𝑎2=26.7911519610804 
𝑏1=1.52734589359888 
𝑏2=1.4771922440937 
𝑙1=0.652841800670228 
𝑙2=0.395727904589064 
1Pixelあたりのサイズ: 0.000363212270430662mm
```