ISAR(Inverse Synthetic Aperture Radar、逆合成開口レーダー)は、目標自身の運動(回転や姿勢変化)を利用して、目標の2次元画像を生成するレーダー技術です。SAR(合成開口レーダー)がレーダーの移動によって合成開口を形成するのに対し、ISARは目標の回転運動によって等価的な合成開口を実現します。
ISARは、船舶の識別・分類、航空機のイメージング、弾道ミサイルの弾頭識別、宇宙デブリの形状推定など、非協力目標(レーダーに協力しない目標)のイメージングにおいて極めて重要な技術です。
本記事の内容
- ISARとSARの違い
- ISARの基本原理(目標の回転運動による合成開口)
- レンジプロファイルの取得
- クロスレンジ分解能の導出
- 運動補償(並進運動の除去)
- レンジ-ドップラーイメージングアルゴリズム
- ISARの応用
- Pythonによるシミュレーション
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
ISARとSARの違い
SARとISARは、ともに合成開口の原理を利用して目標の高分解能画像を得る技術ですが、合成開口を実現するメカニズムが根本的に異なります。
SAR(合成開口レーダー)
- レーダーが移動する(航空機や衛星に搭載)
- 目標は静止(地表面など)
- レーダーの移動に伴い、目標からの反射信号のドップラー履歴が変化
- 画像の幾何学(座標系)がレーダーの運動から正確にわかる
- 主に地表面のマッピングに使用
ISAR(逆合成開口レーダー)
- レーダーは静止(または移動するが、相対回転が本質)
- 目標が移動・回転する(船舶、航空機、宇宙デブリなど)
- 目標の回転に伴い、各散乱点のドップラー周波数が異なる
- 画像の幾何学は目標の回転運動に依存(事前にはわからない)
- 主に非協力目標のイメージング・識別に使用
数学的には、SARとISARは完全に等価です。相対運動が同じであれば、レーダーが動いても目標が動いても同じ結果が得られます。ただし、ISARでは目標の運動が非協力的で、かつ複雑(並進運動+回転運動)であるため、運動補償が最大の技術課題となります。
ISARの基本原理
目標の回転運動モデル
ISARの基本原理を理解するために、簡単化したモデルを考えます。レーダーに対して目標が角速度 $\omega$ で回転している場合を考えます。
目標上の散乱点 $P$ が、目標の回転中心から距離 $r_p$ の位置にあり、初期角度 $\alpha_p$ を持つとします。回転中心からの散乱点の位置は、
$$ \begin{align} x_p(t) &= r_p \cos(\alpha_p + \omega t) \\ y_p(t) &= r_p \sin(\alpha_p + \omega t) \end{align} $$
ここで、$x$ はレンジ方向(レーダーの視線方向)、$y$ はクロスレンジ方向(レーダーの視線に垂直な方向)です。
レンジ変化とドップラー周波数
散乱点 $P$ のレーダーからの距離は、回転中心までの距離 $R_0$ を用いて、
$$ R_p(t) \approx R_0 + x_p(t) = R_0 + r_p \cos(\alpha_p + \omega t) $$
ここで、遠方界近似($R_0 \gg r_p$)を使いました。
散乱点のドップラー周波数は、距離の時間変化率から得られます。
$$ \begin{align} f_{D,p} &= -\frac{2}{\lambda}\frac{dR_p}{dt} \\ &= -\frac{2}{\lambda} \cdot \left(-r_p \omega \sin(\alpha_p + \omega t)\right) \\ &= \frac{2 r_p \omega \sin(\alpha_p + \omega t)}{\lambda} \end{align} $$
短い積分時間($\omega t \ll 1$)では、$\sin(\alpha_p + \omega t) \approx \sin\alpha_p$ として、
$$ f_{D,p} \approx \frac{2 \omega}{\lambda} r_p \sin\alpha_p = \frac{2\omega}{\lambda} y_p $$
ここで、$y_p = r_p \sin\alpha_p$ は散乱点のクロスレンジ位置です。
$$ \boxed{f_{D,p} = \frac{2\omega}{\lambda} y_p} $$
これがISARの核心です。各散乱点のドップラー周波数は、そのクロスレンジ位置に比例するのです。したがって、ドップラー周波数を分析することで、クロスレンジ方向の位置情報を得ることができます。
レンジプロファイルの取得
パルス圧縮によるレンジ分解
ISARでは、まずパルス圧縮により目標のレンジプロファイル(距離方向の散乱強度分布)を取得します。
帯域幅 $B$ のチャープパルスを送信し、マッチドフィルタ処理を行うと、レンジ分解能は次のように与えられます。
$$ \Delta R = \frac{c}{2B} $$
各パルスの受信信号をパルス圧縮処理すると、1本のレンジプロファイルが得られます。これを $N$ パルスにわたって蓄積すると、$N$ 本のレンジプロファイルの時系列データが得られます。
このデータを行列形式で表すと、$S[m, k]$($m = 0, 1, \ldots, N-1$ はパルス番号、$k = 0, 1, \ldots, K-1$ はレンジビン番号)となります。各行がパルス圧縮後の1パルスのレンジプロファイル、各列が特定のレンジビンの時系列です。
クロスレンジ分解能の導出
分解能の式
ISARのクロスレンジ分解能は、ドップラー分解能とドップラー-クロスレンジ関係から導出されます。
まず、ドップラー周波数分解能は、積分時間 $T$ のFFTから、
$$ \Delta f_D = \frac{1}{T} $$
先ほど導出した関係 $f_D = (2\omega / \lambda) y$ を微分すると、
$$ \Delta f_D = \frac{2\omega}{\lambda} \Delta y $$
ここで、$\Delta y$ はクロスレンジ分解能です。$\Delta f_D = 1/T$ を代入すると、
$$ \frac{1}{T} = \frac{2\omega}{\lambda} \Delta y $$
$$ \Delta y = \frac{\lambda}{2\omega T} $$
積分時間 $T$ 中に目標が回転した角度を $\Delta\theta = \omega T$ とすると、
$$ \boxed{\Delta y = \frac{\lambda}{2\Delta\theta}} $$
これがISARのクロスレンジ分解能です。
物理的解釈
クロスレンジ分解能は、波長 $\lambda$ が短いほど、また積分時間中の回転角度 $\Delta\theta$ が大きいほど向上します。
これはSARの方位分解能 $\Delta_{az} = \lambda / (2\Delta\theta_{\text{SAR}})$($\Delta\theta_{\text{SAR}}$ はSARの合成開口角)と完全に同じ形をしています。ISARでは、目標の回転が合成開口角の役割を果たしているのです。
具体例
$\lambda = 3\,\text{cm}$(Xバンド、10GHz)、積分時間中の回転角度 $\Delta\theta = 3°= 0.0524\,\text{rad}$ の場合、
$$ \Delta y = \frac{0.03}{2 \times 0.0524} \approx 0.286\,\text{m} $$
約29cmのクロスレンジ分解能が得られます。
しかし、回転角度を大きくしすぎると($\Delta\theta > 5°$–$10°$程度)、散乱点のマイグレーション(レンジセル間の移動)が発生し、画像がぼやけます。この問題に対処するために、より高度なイメージングアルゴリズム(Polar Format Algorithm等)が必要になります。
運動補償
ISARイメージングにおける最大の技術課題は運動補償です。目標の運動は一般に並進運動と回転運動の両方を含みますが、ISARイメージングに必要なのは回転運動のみです。並進運動は画像のぼやけの原因となるため、除去する必要があります。
並進運動の影響
目標が並進速度 $v$ でレーダーに接近(または離脱)している場合、すべての散乱点に共通のドップラー偏移が加わります。
$$ R(t) = R_0 – vt + \frac{1}{2}at^2 + \ldots $$
- 1次の項($-vt$): 一定のドップラー偏移 → レンジプロファイルの線形シフト
- 2次の項($at^2/2$): ドップラーの時間変化 → レンジプロファイルの移動(レンジウォーク)と位相誤差
包絡線アライメント(Range Alignment)
包絡線アライメントは、パルスごとのレンジプロファイルの位置ずれを補正する処理です。
目標の並進運動により、レンジプロファイルはパルスごとにシフトします。この位置ずれを相互相関により推定・補正します。
$m$ 番目のパルスのレンジプロファイルを $s_m(k)$、基準パルス(通常は最初のパルス)のレンジプロファイルを $s_0(k)$ とすると、シフト量 $\delta_m$ は相互相関のピーク位置から求められます。
$$ \delta_m = \arg\max_{\delta} \left|\sum_k s_0(k) s_m^*(k – \delta)\right| $$
実際にはサブピクセル精度が必要なため、相互相関のピーク周辺を補間して精密な位置合わせを行います。
位相補償(Phase Correction)
包絡線アライメントは粗い位置合わせ(レンジビン単位の整数シフト)を行いますが、サブレンジビンの位相誤差は残ります。この残留位相誤差を補正するのが位相補償です。
代表的な手法は支配散乱点法(Dominant Scatterer Algorithm: DSA)です。
- レンジプロファイル中で最も強い散乱点(支配散乱点)を選ぶ
- 支配散乱点の位相履歴 $\phi_m$ を抽出する
- 全パルスの位相を $\exp(-j\phi_m)$ で補正する
これにより、支配散乱点の位相は完全に補正され、他の散乱点は支配散乱点を基準とした相対位相のみが残ります。
支配散乱点の位相は目標の並進運動に起因するため、この補正は等価的に並進運動の除去に対応します。
レンジ-ドップラーイメージングアルゴリズム
ISARの最も基本的なイメージングアルゴリズムは、レンジ-ドップラー(Range-Doppler: RD)アルゴリズムです。
アルゴリズムの手順
- パルス圧縮: 各パルスの受信信号にマッチドフィルタを適用し、レンジプロファイルを得る
- 包絡線アライメント: パルス間のレンジシフトを相互相関で推定・補正
- 位相補償: 残留位相誤差を支配散乱点法等で補正
- クロスレンジFFT: 各レンジビンについて、パルス方向(slow-time方向)のFFTを実行
- ISAR画像の生成: 結果は2D行列(レンジ vs クロスレンジ)のISAR画像
数学的記述
運動補償後のデータを $S_c[m, k]$ とします。$m$ はパルス番号(slow-time)、$k$ はレンジビン番号(fast-time)です。
クロスレンジ方向のFFTは、各レンジビン $k$ に対して、
$$ I[l, k] = \sum_{m=0}^{N-1} S_c[m, k] \cdot w[m] \cdot e^{-j2\pi ml/N} $$
ここで、$w[m]$ はサイドローブ抑圧のための窓関数(Hamming窓、Hanning窓など)、$l$ はドップラービン(クロスレンジビン)番号です。
結果として得られる $|I[l, k]|^2$ がISAR画像のパワーマップとなります。$k$ 軸がレンジ方向、$l$ 軸がクロスレンジ(ドップラー)方向に対応します。
窓関数の効果
ISARイメージングではサイドローブが隣接する散乱点のイメージを劣化させるため、窓関数の適用が重要です。
| 窓関数 | メインローブ幅 | 最大サイドローブ [dB] |
|---|---|---|
| 矩形窓 | $1.0 \times$ | $-13.3$ |
| Hamming窓 | $1.5 \times$ | $-42.7$ |
| Hanning窓 | $1.5 \times$ | $-31.5$ |
| Blackman窓 | $1.7 \times$ | $-58.1$ |
窓関数を適用するとサイドローブは抑制されますが、メインローブが広がるため、クロスレンジ分解能がやや低下するトレードオフがあります。
ISARの応用
船舶識別
海上監視レーダーにおいて、ISARは船舶の種類や大きさの識別に使われます。船舶は海面の波浪により常にピッチング(前後の揺れ)やローリング(左右の揺れ)を行っており、この回転運動がISARの合成開口を実現します。
船体の上部構造、マスト、煙突などの散乱点分布から船種を識別できます。
航空機分類
空対空または地対空レーダーにおいて、飛行中の航空機のISAR画像から機種を識別します。航空機のヨーイング、ピッチング、ローリングによる回転運動を利用します。
宇宙デブリ観測
地上レーダーから宇宙デブリのISAR画像を取得し、デブリの形状や回転状態を推定します。デブリは一般にタンブリング運動(不規則な回転)をしており、十分な回転角度が得られやすいため、ISARイメージングに適しています。
弾道ミサイル弾頭識別
弾道ミサイルの再突入体から弾頭とデコイを識別するために、ISARイメージングが使われることがあります。
Pythonによるシミュレーション
回転する複数散乱点からのISAR画像生成
複数の散乱点で構成される目標が回転する場合のISAR画像を生成します。
import numpy as np
import matplotlib.pyplot as plt
# レーダーパラメータ
c = 3e8 # 光速 [m/s]
fc = 10e9 # キャリア周波数 [Hz] (Xバンド)
lam = c / fc # 波長 [m]
B = 500e6 # 帯域幅 [Hz]
delta_R = c / (2 * B) # レンジ分解能 [m]
# パルスパラメータ
PRF = 500 # パルス繰り返し周波数 [Hz]
N_pulses = 256 # パルス数
T_obs = N_pulses / PRF # 観測時間 [s]
# 目標の回転
omega = np.deg2rad(3) / T_obs # 観測時間中に3°回転
delta_theta = omega * T_obs # 総回転角度 [rad]
print(f"波長: {lam*100:.1f} cm")
print(f"レンジ分解能: {delta_R:.2f} m")
print(f"クロスレンジ分解能: {lam/(2*delta_theta):.2f} m")
print(f"観測時間: {T_obs:.2f} s")
print(f"回転角度: {np.rad2deg(delta_theta):.1f}°")
# 目標モデル(船舶状の散乱点配置)
# 各散乱点: (x, y, amplitude)
# x: レンジ方向 [m], y: クロスレンジ方向 [m]
scatterers = [
# 船体中心線
(-30, 0, 1.0),
(-20, 0, 0.8),
(-10, 0, 0.6),
(0, 0, 1.5), # 船橋
(10, 0, 0.7),
(20, 0, 0.5),
(30, 0, 0.4),
# 上部構造
(-5, 3, 0.9),
(5, 3, 0.8),
(-5, -3, 0.9),
(5, -3, 0.8),
(0, 5, 1.2), # マスト
(0, -5, 1.2),
# 煙突
(15, 2, 1.0),
(15, -2, 1.0),
]
scatterers = np.array(scatterers)
x_s = scatterers[:, 0] # レンジ方向位置
y_s = scatterers[:, 1] # クロスレンジ方向位置
amp_s = scatterers[:, 2] # 散乱振幅
# 基準距離
R0 = 50e3 # 50 km
# レンジビンの設定
N_range = 256
range_bins = np.arange(N_range) * delta_R - N_range / 2 * delta_R
# 信号シミュレーション
# 各パルス時刻
t_slow = np.arange(N_pulses) / PRF # slow-time
# データ行列 (パルス × レンジビン)
signal = np.zeros((N_pulses, N_range), dtype=complex)
for m in range(N_pulses):
t = t_slow[m]
theta = omega * t # 現在の回転角度
for i in range(len(x_s)):
# 回転後の散乱点位置
x_rot = x_s[i] * np.cos(theta) - y_s[i] * np.sin(theta)
y_rot = x_s[i] * np.sin(theta) + y_s[i] * np.cos(theta)
# レーダーからの距離(レンジ方向の射影)
R_p = R0 + x_rot
# パルス圧縮後の信号(sinc関数で近似)
range_diff = range_bins - x_rot
# パルス圧縮応答
pc_response = amp_s[i] * np.sinc(range_diff / delta_R)
# 位相項(ドップラー情報を含む)
phase = -4 * np.pi * R_p / lam
signal[m, :] += pc_response * np.exp(1j * phase)
# 雑音追加
noise_level = 0.05
signal += noise_level * (np.random.randn(N_pulses, N_range) +
1j * np.random.randn(N_pulses, N_range))
print(f"\n信号行列サイズ: {signal.shape}")
運動補償とISAR画像の生成
import numpy as np
import matplotlib.pyplot as plt
# (上のコードの続き — パラメータ再掲)
c = 3e8
fc = 10e9
lam = c / fc
B = 500e6
delta_R = c / (2 * B)
PRF = 500
N_pulses = 256
T_obs = N_pulses / PRF
omega = np.deg2rad(3) / T_obs
delta_theta = omega * T_obs
N_range = 256
range_bins = np.arange(N_range) * delta_R - N_range / 2 * delta_R
t_slow = np.arange(N_pulses) / PRF
# 目標散乱点(船舶モデル)
scatterers_data = [
(-30, 0, 1.0), (-20, 0, 0.8), (-10, 0, 0.6),
(0, 0, 1.5), (10, 0, 0.7), (20, 0, 0.5), (30, 0, 0.4),
(-5, 3, 0.9), (5, 3, 0.8), (-5, -3, 0.9), (5, -3, 0.8),
(0, 5, 1.2), (0, -5, 1.2), (15, 2, 1.0), (15, -2, 1.0),
]
scatterers_arr = np.array(scatterers_data)
x_s, y_s, amp_s = scatterers_arr[:, 0], scatterers_arr[:, 1], scatterers_arr[:, 2]
R0 = 50e3
signal = np.zeros((N_pulses, N_range), dtype=complex)
for m in range(N_pulses):
t = t_slow[m]
theta = omega * t
for i in range(len(x_s)):
x_rot = x_s[i] * np.cos(theta) - y_s[i] * np.sin(theta)
y_rot = x_s[i] * np.sin(theta) + y_s[i] * np.cos(theta)
R_p = R0 + x_rot
range_diff = range_bins - x_rot
pc_response = amp_s[i] * np.sinc(range_diff / delta_R)
phase = -4 * np.pi * R_p / lam
signal[m, :] += pc_response * np.exp(1j * phase)
noise_level = 0.05
np.random.seed(0)
signal += noise_level * (np.random.randn(N_pulses, N_range) +
1j * np.random.randn(N_pulses, N_range))
# ---- 運動補償 ----
# 1. 包絡線アライメント(相互相関によるレンジシフト補正)
reference = signal[0, :] # 基準パルス
aligned_signal = np.zeros_like(signal)
aligned_signal[0, :] = signal[0, :]
for m in range(1, N_pulses):
# 相互相関(FFTベース)
corr = np.fft.ifft(np.fft.fft(reference) * np.conj(np.fft.fft(signal[m, :])))
shift = np.argmax(np.abs(corr))
if shift > N_range // 2:
shift -= N_range
aligned_signal[m, :] = np.roll(signal[m, :], -shift)
# 2. 位相補償(支配散乱点法)
# 最も強い散乱点のレンジビンを特定
range_profile_mean = np.mean(np.abs(aligned_signal), axis=0)
dominant_bin = np.argmax(range_profile_mean)
# 支配散乱点の位相履歴を抽出
phase_history = np.angle(aligned_signal[:, dominant_bin])
# 位相補正
for m in range(N_pulses):
aligned_signal[m, :] *= np.exp(-1j * phase_history[m])
# ---- ISAR画像生成(レンジ-ドップラー処理) ----
# Hamming窓の適用(クロスレンジ方向)
window = np.hamming(N_pulses)
windowed_signal = aligned_signal * window[:, np.newaxis]
# クロスレンジFFT
N_cross = 512 # ゼロパディング
isar_image = np.fft.fftshift(
np.fft.fft(windowed_signal, n=N_cross, axis=0), axes=0
)
# 画像のパワー(dBスケール)
isar_power = 20 * np.log10(np.abs(isar_image) + 1e-12)
isar_power -= isar_power.max()
# 軸の計算
cross_range_res = lam / (2 * delta_theta)
doppler_axis = np.fft.fftshift(np.fft.fftfreq(N_cross, d=1/PRF))
cross_range_axis = doppler_axis * lam / (2 * omega)
# 表示範囲の設定
range_display = range_bins
cross_display = cross_range_axis
# ISAR画像の表示
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# 1. 散乱点の配置(真値)
axes[0].scatter(x_s, y_s, s=amp_s * 100, c='red', edgecolors='black', zorder=5)
for i in range(len(x_s)):
axes[0].annotate(f'{amp_s[i]:.1f}', (x_s[i] + 1, y_s[i] + 0.5), fontsize=8)
axes[0].set_xlabel('Range [m]', fontsize=12)
axes[0].set_ylabel('Cross-Range [m]', fontsize=12)
axes[0].set_title('True Scatterer Locations', fontsize=13)
axes[0].set_xlim([-40, 40])
axes[0].set_ylim([-15, 15])
axes[0].set_aspect('equal')
axes[0].grid(True, alpha=0.3)
# 2. ISAR画像
extent = [range_bins[0], range_bins[-1],
cross_range_axis[0], cross_range_axis[-1]]
im = axes[1].imshow(
isar_power, aspect='auto', extent=extent,
origin='lower', cmap='jet', vmin=-30, vmax=0
)
axes[1].set_xlabel('Range [m]', fontsize=12)
axes[1].set_ylabel('Cross-Range [m]', fontsize=12)
axes[1].set_title('ISAR Image', fontsize=13)
axes[1].set_xlim([-40, 40])
axes[1].set_ylim([-15, 15])
plt.colorbar(im, ax=axes[1], label='Power [dB]')
# 3. レンジプロファイル
range_profile = 20 * np.log10(np.mean(np.abs(aligned_signal), axis=0) + 1e-12)
range_profile -= range_profile.max()
axes[2].plot(range_bins, range_profile, 'b-', linewidth=1.5)
axes[2].set_xlabel('Range [m]', fontsize=12)
axes[2].set_ylabel('Power [dB]', fontsize=12)
axes[2].set_title('Average Range Profile', fontsize=13)
axes[2].set_xlim([-40, 40])
axes[2].set_ylim([-30, 5])
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print(f"レンジ分解能: {delta_R:.2f} m")
print(f"クロスレンジ分解能: {cross_range_res:.2f} m")
左図が真の散乱点配置、中央がISAR画像、右がレンジプロファイルです。ISAR画像において、船体に沿った散乱点の分布が2次元で再現されていることが確認できます。
回転角度とクロスレンジ分解能の関係
回転角度を変化させた場合のISAR画像の品質変化を確認します。
import numpy as np
import matplotlib.pyplot as plt
c = 3e8
fc = 10e9
lam = c / fc
B = 500e6
delta_R = c / (2 * B)
PRF = 500
N_range = 256
range_bins = np.arange(N_range) * delta_R - N_range / 2 * delta_R
R0 = 50e3
# 散乱点(簡略化した目標)
scat = np.array([
(-15, 0, 1.0), (0, 0, 1.5), (15, 0, 1.0),
(0, 5, 1.2), (0, -5, 1.2),
(-10, 3, 0.8), (10, 3, 0.8),
(-10, -3, 0.8), (10, -3, 0.8),
])
x_s, y_s, amp_s = scat[:, 0], scat[:, 1], scat[:, 2]
# 異なる回転角度で比較
rotation_angles = [1.0, 3.0, 6.0, 10.0] # [deg]
N_pulses = 256
fig, axes = plt.subplots(1, 4, figsize=(20, 5))
for idx, rot_deg in enumerate(rotation_angles):
rot_rad = np.deg2rad(rot_deg)
T_obs = N_pulses / PRF
omega_val = rot_rad / T_obs
t_slow = np.arange(N_pulses) / PRF
# 信号生成
sig = np.zeros((N_pulses, N_range), dtype=complex)
for m in range(N_pulses):
theta = omega_val * t_slow[m]
for i in range(len(x_s)):
x_rot = x_s[i] * np.cos(theta) - y_s[i] * np.sin(theta)
R_p = R0 + x_rot
range_diff = range_bins - x_rot
pc_resp = amp_s[i] * np.sinc(range_diff / delta_R)
phase = -4 * np.pi * R_p / lam
sig[m, :] += pc_resp * np.exp(1j * phase)
# 雑音
np.random.seed(42)
sig += 0.05 * (np.random.randn(N_pulses, N_range) +
1j * np.random.randn(N_pulses, N_range))
# 運動補償(簡略版 - 位相補償のみ)
rp_mean = np.mean(np.abs(sig), axis=0)
dom_bin = np.argmax(rp_mean)
ph = np.angle(sig[:, dom_bin])
for m in range(N_pulses):
sig[m, :] *= np.exp(-1j * ph[m])
# ISAR画像
window = np.hamming(N_pulses)
sig_w = sig * window[:, np.newaxis]
N_cross = 512
isar = np.fft.fftshift(np.fft.fft(sig_w, n=N_cross, axis=0), axes=0)
isar_dB = 20 * np.log10(np.abs(isar) + 1e-12)
isar_dB -= isar_dB.max()
# クロスレンジ軸
doppler = np.fft.fftshift(np.fft.fftfreq(N_cross, d=1/PRF))
cr_axis = doppler * lam / (2 * omega_val)
extent = [range_bins[0], range_bins[-1], cr_axis[0], cr_axis[-1]]
axes[idx].imshow(isar_dB, aspect='auto', extent=extent,
origin='lower', cmap='jet', vmin=-25, vmax=0)
cr_res = lam / (2 * rot_rad)
axes[idx].set_title(f'Δθ={rot_deg}°, Δy={cr_res:.2f}m', fontsize=12)
axes[idx].set_xlabel('Range [m]', fontsize=11)
axes[idx].set_ylabel('Cross-Range [m]', fontsize=11)
axes[idx].set_xlim([-25, 25])
axes[idx].set_ylim([-15, 15])
plt.suptitle('ISAR Image Quality vs Rotation Angle', fontsize=14)
plt.tight_layout()
plt.show()
回転角度が大きくなるほどクロスレンジ分解能が向上し、散乱点がより明瞭に分離されることが確認できます。ただし、回転角度が大きすぎると($\Delta\theta = 10°$)、レンジ方向のマイグレーションにより一部の散乱点がぼやけ始めることも観察できます。
窓関数の効果の比較
異なる窓関数を適用した場合のISAR画像のサイドローブ特性を比較します。
import numpy as np
import matplotlib.pyplot as plt
c = 3e8
fc = 10e9
lam = c / fc
B = 500e6
delta_R = c / (2 * B)
PRF = 500
N_pulses = 256
N_range = 256
range_bins = np.arange(N_range) * delta_R - N_range / 2 * delta_R
R0 = 50e3
# 2つの散乱点(クロスレンジ方向に接近)
scat = np.array([
(0, 0, 1.0),
(0, 2, 0.3), # 近くに弱い散乱点
])
x_s, y_s, amp_s = scat[:, 0], scat[:, 1], scat[:, 2]
rot_deg = 3.0
rot_rad = np.deg2rad(rot_deg)
T_obs = N_pulses / PRF
omega_val = rot_rad / T_obs
t_slow = np.arange(N_pulses) / PRF
# 信号生成
sig = np.zeros((N_pulses, N_range), dtype=complex)
for m in range(N_pulses):
theta = omega_val * t_slow[m]
for i in range(len(x_s)):
x_rot = x_s[i] * np.cos(theta) - y_s[i] * np.sin(theta)
R_p = R0 + x_rot
range_diff = range_bins - x_rot
pc_resp = amp_s[i] * np.sinc(range_diff / delta_R)
phase = -4 * np.pi * R_p / lam
sig[m, :] += pc_resp * np.exp(1j * phase)
np.random.seed(0)
sig += 0.02 * (np.random.randn(N_pulses, N_range) +
1j * np.random.randn(N_pulses, N_range))
# 位相補償
rp_mean = np.mean(np.abs(sig), axis=0)
dom_bin = np.argmax(rp_mean)
ph = np.angle(sig[:, dom_bin])
for m in range(N_pulses):
sig[m, :] *= np.exp(-1j * ph[m])
# 窓関数の比較
windows = {
'Rectangular': np.ones(N_pulses),
'Hamming': np.hamming(N_pulses),
'Hanning': np.hanning(N_pulses),
'Blackman': np.blackman(N_pulses),
}
N_cross = 1024
doppler = np.fft.fftshift(np.fft.fftfreq(N_cross, d=1/PRF))
cr_axis = doppler * lam / (2 * omega_val)
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# クロスレンジプロファイル(レンジビン = 支配散乱点)
for name, win in windows.items():
sig_w = sig * win[:, np.newaxis]
isar = np.fft.fftshift(np.fft.fft(sig_w, n=N_cross, axis=0), axes=0)
cross_profile = np.abs(isar[:, dom_bin])
cross_dB = 20 * np.log10(cross_profile / cross_profile.max() + 1e-12)
axes[0].plot(cr_axis, cross_dB, linewidth=1.5, label=name)
axes[0].set_xlabel('Cross-Range [m]', fontsize=13)
axes[0].set_ylabel('Power [dB]', fontsize=13)
axes[0].set_title('Cross-Range Profile at Dominant Range Bin', fontsize=13)
axes[0].set_xlim([-10, 10])
axes[0].set_ylim([-60, 5])
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)
# 真の散乱点位置をマーク
axes[0].axvline(x=0, color='gray', linestyle=':', alpha=0.5)
axes[0].axvline(x=2, color='gray', linestyle=':', alpha=0.5)
# Hamming窓でのISAR画像
sig_h = sig * np.hamming(N_pulses)[:, np.newaxis]
isar_h = np.fft.fftshift(np.fft.fft(sig_h, n=N_cross, axis=0), axes=0)
isar_dB = 20 * np.log10(np.abs(isar_h) + 1e-12)
isar_dB -= isar_dB.max()
extent = [range_bins[0], range_bins[-1], cr_axis[0], cr_axis[-1]]
im = axes[1].imshow(isar_dB, aspect='auto', extent=extent,
origin='lower', cmap='jet', vmin=-30, vmax=0)
axes[1].set_xlabel('Range [m]', fontsize=12)
axes[1].set_ylabel('Cross-Range [m]', fontsize=12)
axes[1].set_title('ISAR Image (Hamming Window)', fontsize=13)
axes[1].set_xlim([-10, 10])
axes[1].set_ylim([-10, 10])
plt.colorbar(im, ax=axes[1], label='Power [dB]')
plt.tight_layout()
plt.show()
矩形窓ではサイドローブが高く、弱い散乱点($y = 2\,\text{m}$、振幅0.3)がサイドローブに埋もれる可能性がありますが、Hamming窓やBlackman窓ではサイドローブが大幅に抑制され、弱い散乱点の検出が容易になります。
まとめ
本記事では、ISAR(逆合成開口レーダー)の原理と実装について解説しました。
- ISARは目標の回転運動を利用して合成開口を実現し、2次元画像を生成する技術である
- 各散乱点のドップラー周波数はクロスレンジ位置に比例する($f_D = 2\omega y / \lambda$)
- クロスレンジ分解能は $\Delta y = \lambda / (2\Delta\theta)$ で、回転角度 $\Delta\theta$ が大きいほど向上する
- 運動補償(包絡線アライメント+位相補償)は、並進運動を除去してISAR画像の品質を確保するための必須処理である
- レンジ-ドップラーアルゴリズムは、パルス圧縮+運動補償+クロスレンジFFTによる基本的なISARイメージング手法である
- ISARは船舶識別、航空機分類、宇宙デブリ観測など、非協力目標のイメージングに幅広く応用される
次のステップとして、以下の記事も参考にしてください。