レーダー信号処理におけるCFAR検出アルゴリズムの実装
はじめに
レーダーやセンサーを用いた距離測定において、「そこに対象物が存在するか」を判断することは基本的かつ重要な課題です。しかし、実際の測定環境ではノイズが常に存在するため、単純に振幅の大きさだけで判断すると誤検出(False Alarm)が発生してしまいます。
CFAR(Constant False Alarm Rate) は、このような課題を解決するための適応的な閾値設定手法です。各測定点(ビン)に対して、周囲のノイズレベルを統計的に推定し、それに基づいて動的に検出閾値を決定します。これにより、ノイズレベルが変動する環境でも誤警報率を一定に保ちながらターゲットを検出できます。
CFARは以下のような特徴を持ちます:
- 適応的な閾値設定:局所的なノイズレベルに応じて閾値を自動調整
- 複数対象検出:閾値を超える全てのターゲットを同時に検出可能
- ロバスト性:ノイズ環境の変化に強い
今回は、呼吸モニタリングシステムにおいてCA-CFAR(Cell Averaging CFAR)アルゴリズムを実装し、ターゲットの自動検出を実装しました。
この記事で説明すること
- CFAR検出アルゴリズムの基本概念と種類
- CA-CFAR(Cell Averaging CFAR)の動作原理
- Pythonによる実装方法とコード解説
- 距離スイープデータへの適用例
- 実装における設定パラメータの選び方
CFARの説明
CA-CFAR(Cell Averaging CFAR)の構造
CA-CFARは、検出対象セル(CUT: Cell Under Test)の周囲にガードセルと参照セルを配置し、参照セルの平均パワーからノイズレベルを推定する手法です。
基本構造:

処理フロー:


-
ノイズレベル推定:左右の参照セルの平均パワーを計算
noise_level = mean(左参照セル ∪ 右参照セル) -
閾値計算:ノイズレベルにスケールファクタを乗算
threshold = noise_level × scale_factor -
検出判定:CUTの振幅が閾値を超えた場合にターゲット検出
if amplitude[CUT] > threshold → ターゲット検出
なぜガードセルが必要か
今回の距離スイープデータでは信号強度が低く、ピークの半値幅が広いという特徴があります。ガードセルがないと、ターゲット信号の裾野が参照セルに含まれてしまい、ノイズレベルが過大評価されます。その結果、閾値が高くなりすぎて検出できなくなります。
ガードセルを設置することで、ターゲット信号の影響を避けつつ、その外側の参照セルから純粋なノイズレベルを推定できます。ターゲット信号の半値幅が狭ければガードセルを少なくし、半値幅が広ければその分、複数のbinにピークが存在するのでガードセルを増やします。ガードセルとはノイズを計算しない領域(bin)のことです。
実装コード
以下は、CA-CFARアルゴリズムの実装例です:
class CFAR_Processor:
"""CA-CFAR (Cell Averaging CFAR) プロセッサ"""
def __init__(self, num_reference_cells=8, num_guard_cells=3, scale_factor=2.0):
"""
Parameters:
num_reference_cells: 各側の参照セル数
num_guard_cells: 各側のガードセル数
scale_factor: 閾値スケールファクタ (α)
"""
self.num_ref = num_reference_cells
self.num_guard = num_guard_cells
self.scale_factor = scale_factor
# ウィンドウサイズ計算
self.window_size = 2 * (num_reference_cells + num_guard_cells) + 1
def compute_threshold(self, amplitude_data):
"""CFAR閾値を計算"""
n_bins = len(amplitude_data)
threshold_array = np.zeros(n_bins)
# dBを線形スケールに変換
amplitude_linear = 10 ** (amplitude_data / 20.0)
for cut_idx in range(n_bins):
# 左側参照セルの範囲
left_ref_start = max(0, cut_idx - self.num_ref - self.num_guard)
left_ref_end = max(0, cut_idx - self.num_guard)
# 右側参照セルの範囲
right_ref_start = min(n_bins, cut_idx + self.num_guard + 1)
right_ref_end = min(n_bins, cut_idx + self.num_guard + self.num_ref + 1)
# 参照セルのデータを取得
left_cells = amplitude_linear[left_ref_start:left_ref_end]
right_cells = amplitude_linear[right_ref_start:right_ref_end]
reference_cells = np.concatenate([left_cells, right_cells])
if len(reference_cells) > 0:
# ノイズレベル推定(線形スケールで平均)
noise_level_linear = np.mean(reference_cells)
# 閾値計算
threshold_linear = noise_level_linear * self.scale_factor
# dBスケールに戻す
threshold_array[cut_idx] = 20 * np.log10(threshold_linear + 1e-12)
else:
threshold_array[cut_idx] = np.min(amplitude_data)
return threshold_array
def detect_targets(self, amplitude_data):
"""
CFARでターゲット検出
Returns:
threshold_array: 各ビンの閾値
detection_mask: 検出されたビンのマスク (True/False)
detected_indices: 検出されたビンのインデックス
"""
threshold_array = self.compute_threshold(amplitude_data)
# 閾値を超えたビンを検出
detection_mask = amplitude_data > threshold_array
detected_indices = np.where(detection_mask)[0]
return threshold_array, detection_mask, detected_indices
パラメータの選び方
参照セル数(num_reference_cells):
・少ない(8~15セル):局所的なノイズ変動に敏感、ピーク周辺の外側に設定
・多い(25~40セル):広域のノイズレベルを平均化、安定した推定
ガードセル数(num_guard_cells):
・ピーク幅に応じて設定(今回は広いピークのため25~30セル程度)
・狭いピーク:少ないガードセルで対応可能
スケールファクタ(scale_factor):
・1.5~2.0:高感度(誤検出リスク増)
・2.0~3.0:中感度
・3.0~5.0:低感度(見逃しリスク増)
距離スイープへの適用
実際の測定データでは、特定の探索範囲内でCFAR検出を行い、最も振幅の大きいターゲットを選択します:
def detect_peak_position_with_cfar(self, amplitude_data):
"""CFAR処理を用いたピーク位置検出"""
try:
# 探索範囲の設定
search_start = max(0, self.peak_search_start)
search_end = min(len(amplitude_data), self.peak_search_end)
search_region = amplitude_data[search_start:search_end]
if self.enable_cfar:
# CFAR検出を実行
threshold_array, detection_mask, detected_indices = \
self.cfar_processor.detect_targets(search_region)
if len(detected_indices) > 0:
# 検出されたビンの中で最大振幅を持つビンを選択
detected_amplitudes = search_region[detected_indices]
max_detected_idx = detected_indices[np.argmax(detected_amplitudes)]
absolute_peak_position = search_start + max_detected_idx
cfar_info = {
'cfar_enabled': True,
'threshold_array': threshold_array,
'num_detected': len(detected_indices),
'detected_indices': detected_indices + search_start
}
else:
# 検出されない場合はフォールバック
relative_peak_idx = np.argmax(search_region)
absolute_peak_position = search_start + relative_peak_idx
cfar_info = {'cfar_enabled': True, 'fallback_used': True}
return absolute_peak_position, cfar_info
except Exception as e:
return self.target_position, None
結果
実装したCFARアルゴリズムを呼吸モニタリングシステムに統合し、以下の結果を得ました:
検出性能
・自動ターゲット検出:100フレームごとにCFAR処理を実行し、ターゲット位置を自動更新
・検出精度:ノイズフロアから約10~15dB以上のピークを安定して検出
・誤検出の抑制:適切なガードセル設定により、ノイズによる誤検出を大幅に削減
実装パラメータ(最終設定)
self.analyzer = RealTimeVitalAnalyzer(
cfar_num_ref=30, # 参照セル数(各側)
cfar_num_guard=10, # ガードセル数(各側)
cfar_scale=2.0 # スケールファクタ
)
この設定により、広いピーク幅を持つ信号に対しても安定した検出が可能になりました。
視覚化
距離スペクトラムのプロット上に、赤い点線でCFAR閾値を表示し、検出されたターゲット位置を白い縦線で示すことで、アルゴリズムの動作を直感的に確認できます。

CFAR閾値ライン(赤い点線)
self.curve_cfar_threshold = self.plot_range.plot(
pen=pg.mkPen('r', width=2, style=QtCore.Qt.DashLine),
name='CFAR Threshold'
)
ターゲット位置(白い縦線)
self.target_line = pg.InfiniteLine(
pos=display_peak_distance_m,
angle=90,
pen=pg.mkPen('white', width=1, style=QtCore.Qt.DashLine)
)
まとめ
本記事では、レーダー信号処理におけるCFAR検出アルゴリズムについて解説し、Pythonによる実装例を示しました。
CFARのメリット:
・適応的な閾値設定により、ノイズ環境の変動に対してロバスト
・複数ターゲットの同時検出が可能
・誤警報率を一定に保ちながら検出を実現
実装のポイント:
・信号特性に応じたガードセル・参照セル数の調整が重要
・dB⇔線形スケールの変換を正しく行うこと
・スケールファクタで感度を調整可能
CFARは、レーダーだけでなく、超音波センサーやLiDARなど、様々なセンサーシステムに応用できる汎用的な手法です。今回の実装をベースに、用途に応じたカスタマイズを行うことで、より高精度な検出システムを構築できると思います。。
Discussion