2020年12月1日火曜日

可変焦点カメラの焦点較正法

プログラムから焦点距離を制御できる可変焦点レンズ搭載カメラの焦点較正方法をまとめておく. 焦点をあわせたい奥行きを入力すれば,それに対応するレンズの操作値(プログラム上で指定する特に数値)を算出できれば成功.

必要なライブラリをimport

今回は画像も多くて時間かかるのでCythonを使う.これだけで速くなるなら儲けもの.

%load_ext Cython
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

鮮鋭度を定義する

画像1枚からどの程度焦点が合っているかを判定する計算法は多数存在する. 今回は自動焦点に適した評価基準として 次の論文にかかれているLAPVを選択した.

J. L. Pech-Pacheco, G. Cristobal, J. Chamorro-Martinez and J. Fernandez-Valdivia, "Diatom autofocusing in brightfield microscopy: a comparative study," Proceedings 15th International Conference on Pattern Recognition (ICPR2000), vol.3, pp. 314-317, 2000.

# https://ieeexplore.ieee.org/document/903548
def LAPV(img):
    ''''LAPV' algorithm (Pech2000)'''
    lap = cv2.Laplacian(img, ddepth=-1)
    stdev = cv2.meanStdDev(lap)[1]
    s = stdev[0]**2
    return s[0]

焦点を変えた画像列から鮮鋭度を求める

撮影対象を50 mm, 60 mm, 70 mm, 80 mm, 90 mm, 100 mm, 110 mmに順に置いて, 可変焦点レンズの操作値を1から600まで動かして撮影した画像列がdepth001dmm/lens-0001.pngに保存されているとする.

fname = "depth%03dmm/lens-%04d.png"
depth = [50, 60, 70, 80, 90, 100, 110]

鮮鋭度が最大となる操作値を取得する

各奥行きに対してすべての操作値に対応する画像を読み込んでLAPVを算出してcontrastに格納する.

sharpness = np.zeros((7, 600))
contrast = np.zeros((7, 600))
for j in range(7):
    for i in range(599):
        frame = cv2.imread(fname % (depth[j],i+1))
        print(fname % (depth[j],i+1))
        contrast[j,i] = LAPV(frame)

グラフに描画してみる.

d = np.arange(7)
f = np.arange(7)
for j in range(7):
    plt.plot(x, contrast_[j], label=str(depth[j]))
    plt.xlim([150,350])
    plt.legend()
    f[j] = np.argmax(contrast[j])

この図の横軸は可変焦点レンズの焦点を操作する操作値で,縦軸が奥行き[mm]. ピークが2つあるように見えるは,撮影対象の物体(箱)が小さすぎて物体を遠ざけたときに奥の壁まで写ってしまったことが原因だと思う.

距離から操作値を求める変換関数を求める

上の各グラフで最大になる操作値と対応する奥行きとの関係を数式で表すことができれば,奥行きから操作値が計算できることになる. 任意の関数はテーラー展開して低次の項のみで近似したものとして フィッティングするのがよくやる方法.今回は2次関数とした.

def poly2(x, a0, a1, a2):
    return a0 + a1*x + a2*x**2

初期値を[10.0, 100, 90]としたけどこれは見た目で上手くフィッティングできるような初期値を与える.

prameter_initial = np.array([10.0, 100, 90])
popt, pcov = curve_fit(poly2, depth, f, p0= prameter_initial)
p = poly2(d, *popt)
print(*popt)
param = popt[0:3]
plt.scatter(d,f)
plt.plot(d,p)

この図では,横軸が操作値で縦軸が奥行き[mm]. 例えば,奥行き85mmに対応する可変焦点レンズの操作値を求めたければこのようにすればいい.

poly2(85,*popt)
228.4910715793697