STFT周波数推定における自己相関ベース信頼度スコアの実装
1. はじめに
短時間フーリエ変換(STFT)は時系列信号の周波数解析において広く用いられる手法ですが、実用上の課題があります。STFTは一定の窓長を定義して周波数推定を行うため、窓内で信号が安定している場合は精度の高い推定が可能です。しかし、窓内に信号が含まれている領域と含まれていない領域が混在する場合、その推定結果がどの程度信頼できるのかを判断する明確な基準がありません。
従来の対処法として、短い窓・中程度の窓・長い窓を組み合わせた適応ウィンドウ方式(複数の窓を使用して急峻な変化も安定した変化にも対応)や、SN比やピーク半値幅に基づく閾値判定(そもそも閾値を超えないものは出力しない)が考えられます。しかし、前者は不安定な信号に対してどっちつかずの精度になり、後者は不安定な状況下では閾値を超えない可能性があるため、いずれも実用的ではありません。
本記事では、STFT実行前の信号に対して自己相関を用いた信頼度スコアを算出し、周波数推定結果と同時に出力することで、推定値の信頼性を定量的に評価する手法を提案します。
この記事の基本構成はバイタルサイン可視化システムを構築した話の記事になりますので詳細は省きます。
2. この記事で説明すること
- STFT周波数推定における信頼度評価の必要性
- 自己相関を用いた周期性評価の原理
- 3種類の信号処理段階における信頼度スコアの比較
- 実測データによる信頼度スコアの有効性検証
3. 信頼度算出について
本実装では、信号処理の3つの段階で信頼度スコアを算出しています。
3種類の信頼度スコア
def _perform_phase_stft_analysis(self, bpf_signal, time_axis, unwrapped_phase=None):
"""
STFT解析 + 3種類の信頼度評価
Parameters:
bpf_signal: BPF後の位相信号
time_axis: 時間軸
unwrapped_phase: BPF前のアンラップ位相信号(オプション)
"""
# EMAスムージング適用
ema_smoothed_signal = self.apply_ema_smoothing_array(bpf_signal)
# Kalmanフィルタ適用
kalman_filtered_signal, kalman_frequencies, kalman_amplitudes = \
self.apply_kalman_smoothing_array(ema_smoothed_signal)
# 3種類の信頼度スコアを計算
# (1) BPF前の信号(アンラップ位相)
confidence_score_raw = 0.0
if unwrapped_phase is not None and len(unwrapped_phase) >= self.frame_freq * 2:
confidence_score_raw = self._evaluate_periodicity(unwrapped_phase)
# (2) BPF後/スムージング前
confidence_score_bpf = self._evaluate_periodicity(bpf_signal)
# (3) Kalmanフィルタ後
confidence_score_kalman = self._evaluate_periodicity(kalman_filtered_signal)
それぞれの信頼度スコアは以下の段階で算出されます:
- Raw: バンドパスフィルタ前のアンラップ位相信号
- BPF: バンドパスフィルタ後、スムージング前の信号
- Kalman: Kalmanフィルタ適用後の信号
4. 自己相関による周期性評価の原理
信頼度スコアの算出には自己相関を用いた周期性評価を採用しています。
def _evaluate_periodicity(self, signal):
"""
自己相関ベースの周期性評価
Parameters:
signal: 時系列信号(BPFまたはKalmanフィルタ後の位相データ)
Returns:
periodicity_score: 0.0-1.0の周期性スコア
"""
try:
if len(signal) < self.frame_freq * 2:
return 0.0
# 自己相関の計算
autocorr = np.correlate(signal, signal, mode='full')
mid = len(autocorr) // 2
autocorr_positive = autocorr[mid:]
# 正規化(τ=0で割る)
if autocorr_positive[0] == 0:
return 0.0
normalized_autocorr = autocorr_positive / autocorr_positive[0]
# 探索範囲の設定(呼吸周期: 1-10秒)
min_lag = int(self.frame_freq * 1) # 1秒
max_lag = min(int(self.frame_freq * 10), len(normalized_autocorr)) # 10秒
if max_lag <= min_lag:
return 0.0
# 探索範囲内でのピーク値を取得
search_region = normalized_autocorr[min_lag:max_lag]
peak_value = np.max(search_region)
# スコア化(0.0-1.0にクリップ)
periodicity_score = np.clip(peak_value, 0.0, 1.0)
return periodicity_score
except Exception as e:
return 0.0
自己相関の仕組み
自己相関関数は、信号を時間シフトさせた自分自身との相関を計算します。周期的な信号の場合、周期に対応する時間遅れ(ラグ)で高い相関値を示します。
本実装では:
-
自己相関の計算:
np.correlateで信号と自身の相関を全ラグで計算 - 正規化: ラグ0(自己相関のピーク)で正規化し、0-1の範囲に収める
- 探索範囲の限定: 呼吸の周期(1-10秒)に相当するラグ範囲を設定
- ピーク値の抽出: 探索範囲内での最大相関値を信頼度スコアとして採用
この手法により、信号が周期的であるほど高いスコアが得られ、不規則な信号では低いスコアとなります。
5. 信頼度の変化
添付されたグラフは、実測データにおける3種類の信頼度スコアの時間変化を示しています。

グラフの解釈
- 横軸: フレーム番号(時間経過)
- 縦軸: 信頼度スコア(0.0-1.0)
- 青色領域: 呼吸安定領域
主な観察結果
-
RAW(赤線): フィルタリング前の生信号は全体的に高い信頼度を示すが、これは必ずしも正確な周期性を反映していない可能性があります
-
スムージング前/BPF後(橙線): 不安定な領域(フレーム0-1500)では低い信頼度(0.2-0.4)を示し、呼吸安定領域(フレーム1500以降)では高い信頼度(0.6-)に上昇します。この明確な差別化が最も重要な特徴です
-
スムージング後/Kalman(紫線): 平滑化により変動は抑えられますが、不安定領域と安定領域の差が縮小し、判別性が低下しています
信頼度スコアの計算に使用する信号
グラフから明らかなように、BPF後/スムージング前の信号(橙線)が最も優れた判別性を示します:
- 不安定時: 0.2-0.4(低信頼度)
- 安定時: 0.6-0.9(高信頼度)
この明確な差により、STFT結果の信頼性を定量的に評価できます。

実装への反映
# 表示用は推奨のBPF後スコアを使用
confidence_score = confidence_score_bpf
# コンソール出力による確認
print(f"[Confidence] Raw:{confidence_score_raw:.3f} | "
f"BPF:{confidence_score_bpf:.3f} | "
f"Kalman:{confidence_score_kalman:.3f} | "
f"Freq:{peak_frequency:.3f}Hz | BPM:{bpm:.1f}")
6. まとめ
本記事では、STFT周波数推定における自己相関ベースの信頼度スコア算出手法を検討しました。
主要なポイント
- 従来の課題: STFTは窓内の信号状態によって推定精度が変動するが、その信頼性を評価する基準を検証した
- 提案手法: 自己相関を用いて信号の周期性を定量評価し、0-1の信頼度スコアとして出力
- 3段階評価: Raw/BPF/Kalmanの3段階で信頼度を算出し、BPF段階が最も優れた判別性を示すことを実証
- 実用的効果: 不安定時と安定時で信頼度スコアが明確に分離(0.2-0.4 vs 0.6-0.9)し、推定結果の採用判断が可能に
Discussion