レンズの組み合わせと光学系設計 — 合成焦点距離・主点位置の導出とPython光線追跡

単一のレンズでは実現できない結像特性も、複数のレンズを組み合わせることで達成できます。カメラレンズ、顕微鏡、望遠鏡など、実用的な光学系はすべて複数レンズの組み合わせで構成されています。

レンズの組み合わせを理論的に扱うことで、所望の焦点距離やバックフォーカスを持つ光学系を設計できるようになります。本記事では、薄肉レンズの近軸理論に基づいて合成焦点距離と主点位置を導出し、光線追跡で検証します。

本記事の内容

  • 薄肉レンズの近軸結像公式の復習
  • 2枚レンズ系の合成焦点距離の導出
  • 主点位置の計算
  • 光学系設計の具体例
  • Pythonでの光線追跡の実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

薄肉レンズの近軸結像公式の復習

薄肉レンズの結像公式は、物体距離 $s$、像距離 $s’$、焦点距離 $f$ の間の関係を記述します:

$$ \boxed{\frac{1}{s’} – \frac{1}{s} = \frac{1}{f}} $$

ここで、光の進行方向を正として、$s$ は物体からレンズまでの距離(物体側は負)、$s’$ はレンズから像までの距離です。

レンズの屈折力(パワー)$\phi$ は焦点距離の逆数で定義されます:

$$ \phi = \frac{1}{f} $$

単位はディオプター [D] = [m$^{-1}$] です。屈折力の概念を使うと、レンズの組み合わせがシンプルに記述できます。

2枚レンズ系の合成焦点距離

問題設定

焦点距離 $f_1$ の第1レンズと焦点距離 $f_2$ の第2レンズが、間隔 $d$ を空けて光軸上に配置されている場合を考えます。この系全体の合成焦点距離 $f$ を求めます。

光線行列による導出

光線の状態を(光軸からの高さ $y$, 光軸となす角度 $\theta$)の列ベクトルで表すと、薄肉レンズの屈折と自由空間の伝搬はそれぞれ行列で表せます。

薄肉レンズの屈折行列:

$$ \bm{R}(f) = \begin{pmatrix} 1 & 0 \\ -1/f & 1 \end{pmatrix} $$

自由空間の伝搬行列:

$$ \bm{T}(d) = \begin{pmatrix} 1 & d \\ 0 & 1 \end{pmatrix} $$

2枚レンズ系の全体行列は、光が第1レンズ → 間隔 $d$ → 第2レンズの順に通過するので:

$$ \bm{M} = \bm{R}(f_2) \cdot \bm{T}(d) \cdot \bm{R}(f_1) $$

各行列を代入して計算します:

$$ \bm{T}(d) \cdot \bm{R}(f_1) = \begin{pmatrix} 1 & d \\ 0 & 1 \end{pmatrix} \begin{pmatrix} 1 & 0 \\ -1/f_1 & 1 \end{pmatrix} $$

$$ = \begin{pmatrix} 1 – d/f_1 & d \\ -1/f_1 & 1 \end{pmatrix} $$

さらに第2レンズの屈折行列を掛けます:

$$ \bm{M} = \begin{pmatrix} 1 & 0 \\ -1/f_2 & 1 \end{pmatrix} \begin{pmatrix} 1 – d/f_1 & d \\ -1/f_1 & 1 \end{pmatrix} $$

$$ = \begin{pmatrix} 1 – d/f_1 & d \\ -1/f_2(1-d/f_1) – 1/f_1 & -d/f_2 + 1 \end{pmatrix} $$

行列の $(2,1)$ 成分は合成系の屈折力 $-1/f$ に相当します。$(2,1)$ 成分を整理すると:

$$ M_{21} = -\frac{1}{f_2}\left(1 – \frac{d}{f_1}\right) – \frac{1}{f_1} $$

$$ = -\frac{1}{f_2} + \frac{d}{f_1 f_2} – \frac{1}{f_1} $$

$$ = -\left(\frac{1}{f_1} + \frac{1}{f_2} – \frac{d}{f_1 f_2}\right) $$

$M_{21} = -1/f$ なので、合成焦点距離の公式が得られます:

$$ \boxed{\frac{1}{f} = \frac{1}{f_1} + \frac{1}{f_2} – \frac{d}{f_1 f_2}} $$

屈折力で書くと:

$$ \boxed{\phi = \phi_1 + \phi_2 – d\,\phi_1\,\phi_2} $$

特殊な場合

レンズが密着している場合($d = 0$):

$$ \frac{1}{f} = \frac{1}{f_1} + \frac{1}{f_2} $$

屈折力が単純に加算されます。これは密着レンズの公式として知られています。

無焦点系($1/f = 0$)の条件:

$$ d = f_1 + f_2 $$

ケプラー式望遠鏡($f_1, f_2 > 0$)やガリレオ式望遠鏡($f_1 > 0, f_2 < 0$)はこの条件で設計されます。

主点位置の計算

合成焦点距離だけでは光学系の記述は不完全です。系全体を1枚の等価レンズとして扱うには、主点(principal point)の位置が必要です。

前側主点と後側主点

前側主点 $H$ は第1レンズから距離 $\delta_H$ の位置、後側主点 $H’$ は第2レンズから距離 $\delta_{H’}$ の位置にあります。

光線行列 $\bm{M}$ の成分から主点位置を求めます。系全体の行列は:

$$ \bm{M} = \begin{pmatrix} A & B \\ C & D \end{pmatrix} $$

ここで:

$$ A = 1 – \frac{d}{f_1}, \quad B = d, \quad C = -\frac{1}{f}, \quad D = 1 – \frac{d}{f_2} $$

前側主点の第1レンズからのずれ $\delta_H$ と、後側主点の第2レンズからのずれ $\delta_{H’}$ は:

$$ \boxed{\delta_{H’} = -\frac{A – 1}{C} = -\frac{d}{f_1} \cdot f = -\frac{d \, f}{f_1}} $$

$$ \boxed{\delta_H = \frac{D – 1}{C} = \frac{d}{f_2} \cdot f = \frac{d \, f}{f_2}} $$

$\delta_{H’}$ は第2レンズから後側主点までの距離(正が光の進行方向)、$\delta_H$ は第1レンズから前側主点までの距離(正が光の入射方向の逆)です。

バックフォーカルディスタンス(BFD)

後側焦点は後側主点 $H’$ から距離 $f$ の位置にありますが、実用上は第2レンズの後面から後側焦点までの距離(バックフォーカルディスタンス)が重要です:

$$ \text{BFD} = f + \delta_{H’} = f\left(1 – \frac{d}{f_1}\right) = f \cdot A $$

同様に、前側焦点から第1レンズまでの距離(フロントフォーカルディスタンス)は:

$$ \text{FFD} = -f + \delta_H = -f\left(1 – \frac{d}{f_2}\right) = -f \cdot D $$

具体的な設計例

例1:カメラ用テレフォトレンズ

実際の焦点距離よりも光学系の全長を短くするのがテレフォトレンズの目的です。

仕様: 合成焦点距離 $f = 200\,\text{mm}$、全長(第1レンズから後側焦点まで)を $150\,\text{mm}$ 以下にする。

テレフォトレンズでは $f_1 > 0$(正レンズ)、$f_2 < 0$(負レンズ)の組み合わせを使います。

合成焦点距離の条件:

$$ \frac{1}{200} = \frac{1}{f_1} + \frac{1}{f_2} – \frac{d}{f_1 f_2} $$

全長の条件は $d + \text{BFD} \leq 150$ です。BFD を代入すると:

$$ d + f\left(1 – \frac{d}{f_1}\right) \leq 150 $$

$f_1 = 100\,\text{mm}$、$d = 70\,\text{mm}$ と仮定すると:

$$ \frac{1}{200} = \frac{1}{100} + \frac{1}{f_2} – \frac{70}{100 f_2} $$

$$ \frac{1}{f_2}\left(1 – \frac{70}{100}\right) = \frac{1}{200} – \frac{1}{100} = -\frac{1}{200} $$

$$ \frac{1}{f_2} \cdot 0.3 = -\frac{1}{200} $$

$$ f_2 = -60\,\text{mm} $$

BFD を計算します:

$$ \text{BFD} = 200\left(1 – \frac{70}{100}\right) = 200 \times 0.3 = 60\,\text{mm} $$

全長 = $d + \text{BFD} = 70 + 60 = 130\,\text{mm}$。合成焦点距離 $200\,\text{mm}$ に対して全長 $130\,\text{mm}$ と、大幅な短縮が実現できています。テレフォト比は $130/200 = 0.65$ です。

Pythonでの光線追跡

2枚レンズ系の光線追跡をPythonで実装します。光線行列を用いた近軸光線追跡と、光学系の可視化を行います。

import numpy as np
import matplotlib.pyplot as plt

# --- 光線行列の定義 ---
def refraction_matrix(f):
    """薄肉レンズの屈折行列"""
    return np.array([[1, 0], [-1/f, 1]])

def translation_matrix(d):
    """自由空間の伝搬行列"""
    return np.array([[1, d], [0, 1]])

def composite_focal_length(f1, f2, d):
    """合成焦点距離"""
    return 1 / (1/f1 + 1/f2 - d/(f1*f2))

def principal_points(f1, f2, d):
    """主点位置 (delta_H, delta_H')"""
    f = composite_focal_length(f1, f2, d)
    dH = d * f / f2      # 第1レンズからの前側主点
    dHp = -d * f / f1     # 第2レンズからの後側主点
    return dH, dHp

# --- 光線追跡 ---
def trace_ray_two_lens(y0, theta0, f1, f2, d, z_start, z_end, n_points=500):
    """2枚レンズ系の光線追跡"""
    z1 = 0.0   # 第1レンズの位置
    z2 = d      # 第2レンズの位置

    z_list = []
    y_list = []

    # 第1レンズまでの伝搬
    z_pre = np.linspace(z_start, z1, n_points // 3)
    for z in z_pre:
        y = y0 + theta0 * (z - z_start)
        z_list.append(z)
        y_list.append(y)

    # 第1レンズでの屈折
    y_at_L1 = y0 + theta0 * (z1 - z_start)
    state = np.array([y_at_L1, theta0])
    state = refraction_matrix(f1) @ state

    # 第1レンズから第2レンズまでの伝搬
    z_mid = np.linspace(z1, z2, n_points // 3)
    for z in z_mid:
        y = state[0] + state[1] * (z - z1)
        z_list.append(z)
        y_list.append(y)

    # 第2レンズでの屈折
    y_at_L2 = state[0] + state[1] * (z2 - z1)
    state2 = np.array([y_at_L2, state[1]])
    state2 = refraction_matrix(f2) @ state2

    # 第2レンズ以降の伝搬
    z_post = np.linspace(z2, z_end, n_points // 3)
    for z in z_post:
        y = state2[0] + state2[1] * (z - z2)
        z_list.append(z)
        y_list.append(y)

    return np.array(z_list), np.array(y_list)

# === 可視化 ===
fig, axes = plt.subplots(2, 2, figsize=(16, 12))

# --- (1) テレフォトレンズ ---
f1, f2, d = 100, -60, 70
f_comp = composite_focal_length(f1, f2, d)
dH, dHp = principal_points(f1, f2, d)
bfd = f_comp + dHp

ax = axes[0, 0]
z_start, z_end = -150, 200
colors = plt.cm.viridis(np.linspace(0.2, 0.8, 5))

for i, y0 in enumerate(np.linspace(-20, 20, 5)):
    z, y = trace_ray_two_lens(y0, 0, f1, f2, d, z_start, z_end)
    ax.plot(z, y, color=colors[i], lw=1.5)

# レンズの描画
for z_pos, fl, col in [(0, f1, 'blue'), (d, f2, 'red')]:
    lens_h = 30
    if fl > 0:
        ax.annotate('', xy=(z_pos, lens_h), xytext=(z_pos, -lens_h),
                     arrowprops=dict(arrowstyle='<->', color=col, lw=2))
    else:
        ax.plot([z_pos-2, z_pos, z_pos+2], [lens_h, lens_h-3, lens_h], color=col, lw=2)
        ax.plot([z_pos, z_pos], [-lens_h+3, lens_h-3], color=col, lw=2)
        ax.plot([z_pos-2, z_pos, z_pos+2], [-lens_h, -lens_h+3, -lens_h], color=col, lw=2)

ax.axhline(0, color='gray', ls='-', lw=0.5)
ax.axvline(d + bfd, color='green', ls='--', lw=1.5, label=f'Back focal point')
ax.set_xlabel('z [mm]')
ax.set_ylabel('y [mm]')
ax.set_title(f'Telephoto lens: $f_1$={f1}, $f_2$={f2}, d={d}, f={f_comp:.1f} mm', fontsize=12)
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
ax.set_ylim(-40, 40)

# --- (2) 合成焦点距離のdへの依存性 ---
ax = axes[0, 1]
f1_val, f2_val = 100, 50
d_range = np.linspace(0, f1_val + f2_val - 1, 300)
f_range = np.array([composite_focal_length(f1_val, f2_val, dd) for dd in d_range])

ax.plot(d_range, f_range, 'b-', lw=2)
ax.axhline(0, color='gray', ls='-', lw=0.5)
ax.axvline(f1_val + f2_val, color='r', ls='--', lw=1.5, label=f'd = $f_1+f_2$ = {f1_val+f2_val}')
ax.set_xlabel('Lens separation d [mm]')
ax.set_ylabel('Composite focal length f [mm]')
ax.set_title(f'Composite f vs d ($f_1$={f1_val}, $f_2$={f2_val})', fontsize=12)
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
ax.set_ylim(-500, 500)

# --- (3) ケプラー望遠鏡(無焦点系) ---
f1, f2, d = 200, 50, 250  # d = f1 + f2
ax = axes[1, 0]
z_start, z_end = -100, 400

for theta0 in np.linspace(-0.02, 0.02, 5):
    z, y = trace_ray_two_lens(0, theta0, f1, f2, d, z_start, z_end)
    ax.plot(z, y, lw=1.5)

for z_pos, fl, col in [(0, f1, 'blue'), (d, f2, 'red')]:
    lens_h = 25
    ax.annotate('', xy=(z_pos, lens_h), xytext=(z_pos, -lens_h),
                 arrowprops=dict(arrowstyle='<->', color=col, lw=2))

ax.axhline(0, color='gray', ls='-', lw=0.5)
ax.set_xlabel('z [mm]')
ax.set_ylabel('y [mm]')
magnification = -f1 / f2
ax.set_title(f'Keplerian telescope: $f_1$={f1}, $f_2$={f2}, M={magnification:.0f}x', fontsize=12)
ax.grid(True, alpha=0.3)

# --- (4) 主点位置の可視化 ---
ax = axes[1, 1]
f1, f2 = 80, 120
d_vals = np.linspace(1, 180, 200)
dH_vals = []
dHp_vals = []
for dd in d_vals:
    dH_val, dHp_val = principal_points(f1, f2, dd)
    dH_vals.append(dH_val)
    dHp_vals.append(dHp_val)

ax.plot(d_vals, dH_vals, 'b-', lw=2, label="$\\delta_H$ (front principal point from L1)")
ax.plot(d_vals, dHp_vals, 'r-', lw=2, label="$\\delta_{H'}$ (rear principal point from L2)")
ax.axhline(0, color='gray', ls='-', lw=0.5)
ax.set_xlabel('Lens separation d [mm]')
ax.set_ylabel('Principal point offset [mm]')
ax.set_title(f'Principal point positions ($f_1$={f1}, $f_2$={f2})', fontsize=12)
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

左上のテレフォトレンズでは、平行光線が正レンズと負レンズを通過して焦点に集まる様子が見えます。正レンズだけの焦点距離 $100\,\text{mm}$ に対して、合成系の焦点距離は $200\,\text{mm}$ と長くなりますが、全長は $130\,\text{mm}$ に抑えられています。

右上のグラフは、レンズ間隔 $d$ に対する合成焦点距離の変化を示しています。$d = f_1 + f_2$ で合成焦点距離が発散(無焦点系)することが確認できます。

左下はケプラー式望遠鏡の光線追跡です。$d = f_1 + f_2 = 250\,\text{mm}$ で無焦点系となり、入射平行光が出射でも平行光のまま角度が変わります。角倍率は $M = -f_1/f_2 = -4$ 倍です。

右下は、レンズ間隔の変化に対する前側主点と後側主点の位置を示しています。

収差への展望

本記事では近軸光線(パラキシャル光線)の理論に基づいて議論しました。実際の光学設計では、レンズの厚みや球面収差、色収差などの影響を考慮する必要があります。2枚レンズの組み合わせは色消しレンズ(アクロマート)の設計にも使われ、正レンズ(クラウンガラス)と負レンズ(フリントガラス)を組み合わせて色収差を補正します。

まとめ

本記事では、薄肉レンズの組み合わせにおける合成焦点距離と主点位置の理論を解説しました。

  • 合成焦点距離: $\dfrac{1}{f} = \dfrac{1}{f_1} + \dfrac{1}{f_2} – \dfrac{d}{f_1 f_2}$
  • 主点位置: $\delta_H = \dfrac{d\,f}{f_2}$, $\delta_{H’} = -\dfrac{d\,f}{f_1}$
  • 密着レンズ($d=0$): 屈折力が単純に加算 $\phi = \phi_1 + \phi_2$
  • 無焦点系(望遠鏡): $d = f_1 + f_2$ で角倍率 $M = -f_1/f_2$
  • テレフォトレンズ: 正+負レンズで全長を短縮

次のステップとして、以下の記事も参考にしてください。