🐳

レーダーで始めるバイタルサイン検出 - IQデータの基本理解

に公開

はじめに

ミリ波センサーを使った非接触バイタルサイン検出が注目されています。本記事では、ミリ波センサーから得られるIQデータの構造と、それをどのように解析してバイタルサイン(呼吸・心拍)を検出するかを段階的に解説します。

この記事で説明していること

・IQデータの基礎から、データ構造を見ていきます。そして、バイタルサイン検出の基本的な手法について説明します。言語はpythonです。

IQデータとは何か

IQ(In-phase/Quadrature)データは、レーダーが受信した信号を複素数で表現したものです。

IQ = I + jQ
  • I成分(In-phase): 同相成分(実数部)
  • Q成分(Quadrature): 直交成分(虚数部)

振幅と位相の計算

IQデータから振幅と位相を取得する基本的なコード:

import numpy as np

# 振幅計算(物体の反射強度)
amplitude = np.abs(iq_data)
amplitude_db = 20 * np.log10(amplitude + 1e-12)  # dB変換

# 位相計算(物体の微小移動)
phase = np.angle(iq_data)
unwrapped_phase = np.unwrap(phase)  # 位相連続化

このデータから以下の情報を取得できます:

取得情報 計算方法 用途
振幅 np.abs(iq_data) 物体の反射強度
位相 np.angle(iq_data) 物体の微小移動
速度 位相変化率 ドップラー効果による速度検出

ミリ波センサーの一般的なデータ構造

Frame/Sweep/Subsweepの詳細階層構造

よくあるミリ波センサーは3層の階層構造で組織化されており、各層がデータ転送とセンサー性能に直接影響します。以下の図は、データフローの一例を表しています:

Frame(測定サイクル単位、frame_rate=20Hzの場合50ms間隔)
│
├── Sweep 1(時系列サンプル1、sweeps_per_frame=4の場合)
│   ├── Subsweep 1:距離80-159点(160個のIQ値)
│   │   └── I+jQ[0], I+jQ[1], ..., I+jQ[159] ← 各点に複素数データ
│   ├── Subsweep 2:距離160-239点(80個のIQ値)
│   │   └── I+jQ[0], I+jQ[1], ..., I+jQ[79]
│   └── Subsweep 3:距離240-319点(80個のIQ値)
│       └── I+jQ[0], I+jQ[1], ..., I+jQ[79]
│
├── Sweep 2(時系列サンプル2)
│   ├── Subsweep 1:同じ距離範囲を再測定
│   ├── Subsweep 2:異なるタイミングでの測定値
│   └── Subsweep 3:時系列での変化を捉える
│
├── Sweep 3, Sweep 4...

データ転送速度への影響

SweepやSubsweepの数値変更は、センサーからホストPCへのデータ転送量に直接影響します。データ転送量は以下の計算式で決まります:

1秒間のデータ転送量 = frame_rate × sweeps_per_frame × (各Subsweepの距離点数の合計) × 8バイト

例えば、frame_rate=20Hz、sweeps_per_frame=4、各Subsweepで160点の場合、1秒間に約41kBのデータが転送されます。Sweep数を倍の8に増やすと転送量も倍の82kBとなり、USBやUART通信の帯域幅制限に達する可能性があります。また、Subsweepの距離点数を増やすことで測定範囲を拡大できますが、同様にデータ転送量が増加するため、リアルタイム処理においては適切なバランスが重要になります。

フレームレートとスイープレート

ミリ波センサーにおいて、フレームレート(Frame Rate)とスイープレート(Sweep Rate)は測定性能を決定する重要なパラメータです。フレームレートは1秒間に取得するフレーム数を表し、主に検出したい現象の周波数特性に基づいて決定されます。バイタルサイン検出では、呼吸周波数(0.1-1Hz)の10-20倍である10-20Hzのフレームレートが一般的に使用されます。

一方、スイープレートは個々のSweep間の時間間隔を制御し、フレームレートよりもはるかに高い周波数で動作します。典型的には数kHzから数十kHzの範囲で設定され、この高速なサンプリングにより微小な位相変化の検出精度が大幅に向上します。特に速度算出においては、短い時間間隔での連続する位相測定が可能になるため、ドップラー効果による微小な周波数シフトをより正確に捉えることができます。

速度計算の精度は、スイープレートの設定に直接依存します。高いスイープレートを使用することで、連続するSweep間の時間差(Δt)が小さくなり、位相差(Δφ)の時間変化率(Δφ/Δt)がより正確に算出できます。これにより、従来では検出困難だった数mm/秒レベルの微小な速度変化も測定可能になります。ただし、スイープレートの向上はデータ処理負荷とデータ転送量の増加を伴うため、アプリケーションの要求に応じた適切な設定が重要です。

データ構造に対応するコード部分

# メインループでのデータ取得部分
results = client.get_next()  # 1つのFrame取得

# 複数スイープの平均化処理(Sweep処理)
if iq_data.ndim == 2 and iq_data.shape[0] > 1:
    averaged_sweep = np.mean(iq_data, axis=0)  # Sweep軸で平均化
else:
    averaged_sweep = iq_data

# Subsweepデータから特定距離の値を抽出
target_iq = averaged_sweep[self.target_position]  # 特定距離ポイント

Frame/Sweep/Subsweepの設定方法

センサー設定コード


# センサー設定
sensor_config = SensorConfig(
    sweeps_per_frame=1,        # 1フレームあたりのSweep数
    frame_rate=20,             # フレームレート(Hz)
    subsweeps=[
        SubsweepConfig(
            start_point=80,        # 開始距離ポイント
            num_points=160         # 距離ポイント数
        ),
    ],
)

① Sweepの意味と速度計算での活用

Sweepは時系列での連続測定サンプルを表し、レーダーによるドップラー効果の検出において中核的な役割を果たします。連続するSweep間の位相変化を解析することで、微小な物体の移動速度を高精度で算出できます。複数のSweepを取得することで、単発測定では困難なランダムノイズの抑制も可能になります。特にバイタルサイン検出では、呼吸による胸部の周期的な移動をSweep間の位相差として捉えることができ、時間分解能を向上させるためにSweep数を調整することで、より細かな生体信号の変化を検出できるようになります。

# 速度計算の例(複数Sweepを利用)
phase_diff = phase[sweep_n+1] - phase[sweep_n]  # Sweep間位相差
velocity = (phase_diff * wavelength) / (4 * π * dt)  # 速度算出

② Subsweepの意味と測定モード

Subsweepは、単一のSweep内で異なる測定条件を並行して適用できる機能で、レーダーセンサーの柔軟性を大幅に向上させます。例えば、近距離と遠距離で異なるプロファイル設定を使い分けたり、対象物に応じてゲイン調整を行うことができます。

特に重要なのが測定モードの選択です。高精度モードでは、長い積分時間と高い送信電力を使用することで、微小な信号変化も捉えることができますが、測定に時間がかかりデータ転送量も増加します。一方、高速モードでは、短い積分時間と最適化されたアルゴリズムにより高速な測定を実現しますが、感度と精度はやや低下します。バイタルサイン検出では、呼吸による微小な位相変化を検出する必要があるため、通常は高精度モードが推奨されますが、リアルタイム処理が重要な場合は高速モードとの適切なバランスを見つけることが重要になります。

バイタルサイン検出での活用

バイタルサイン検出においては、各階層が特定の役割を持ちます。Frameは20Hz程度での連続測定により、呼吸の基本周波数範囲である0.1-1Hzの周期的な変化を確実に捕捉できます。これは、ナイキスト定理に基づき、呼吸信号を正確にサンプリングするために必要な最小フレームレートを大幅に上回る設定です。

Sweepについては、バイタルサインが比較的低周波数の現象であるため、通常は1フレーム当たり1Sweepで十分な精度が得られます。ただし、より高精度な測定や複雑な生体信号の解析が必要な場合は、複数Sweepによる平均化処理を適用することで、測定ノイズを効果的に抑制できます。

Subsweepは、単一の距離範囲での胸部の微小移動を検出するために最適化されます。呼吸による胸部の前後移動は数ミリメートルの範囲で発生するため、高精度モードを使用して位相変化を敏感に検出する必要があります。この微小な移動が複素IQ信号の位相変化として現れ、後段の信号処理により呼吸パターンとして抽出されます。

低周波バイタルサインでの最適化設定

呼吸や心拍などの低周波生体信号(0〜3Hz程度)では、前述の高速モードも実際には必要ありません。これらの信号は非常にゆっくりとした変化であるため、センサーの応答速度よりも測定精度と安定性が重要になります。実用的なアプローチとして、Subsweepを1つに設定し、複数のSweepを平均化して代表値を取得する方法が推奨されます。

# 低周波バイタルサイン用の設定例
sensor_config = SensorConfig(
    sweeps_per_frame=4,        # 4つのSweepで平均化
    frame_rate=20,             # 20Hz(呼吸周波数の20倍)
    subsweeps=[
        SubsweepConfig(
            start_point=80,    # 単一の距離範囲
            num_points=160,    # 適度な距離分解能
            profile=a121.Profile.PROFILE_1  # 高精度モード
        ),
    ],
)

# 複数Sweepの平均化処理
if iq_data.ndim == 2 and iq_data.shape[0] > 1:
    averaged_sweep = np.mean(iq_data, axis=0)  # Sweep軸で平均化
    noise_reduced_data = averaged_sweep  # ノイズが大幅に低減
else:
    averaged_sweep = iq_data

データ取得における必須項目

センサーからデータを取得する際は、後段の解析処理を考慮して以下の項目を記録することが重要です。timestamp(絶対時刻)は、特に複数センサーを使用するシステムにおいて同期を取るために不可欠な情報です。異なるセンサー間でのデータ対応付けや、システム全体での時系列解析を行う場合、各データポイントの正確な取得時刻が必要になります。

time_s(システム開始からの経過時間)は、連続的な時系列解析やフィルタリング処理において、均等な時間間隔でのデータ処理を保証するために使用されます。Frame_idxとsweep_idxは、データの階層構造を維持し、特定のフレームやスイープでの異常値検出や品質管理に活用されます。

i、q成分は複素IQ信号の基本要素として、後段でのあらゆる信号処理の基礎となります。Amplitude(振幅)は物体検出や距離スペクトラム作成に使用され、phase(位相)は微小移動の検出とバイタルサイン抽出の情報となります。

# データ記録の実装例
data_record = {
    'timestamp': time.time(),           # 絶対時刻(複数センサー同期用)
    'time_s': elapsed_time,            # 経過時間(連続解析用)
    'frame_idx': current_frame,        # フレーム番号(データ管理用)
    'sweep_idx': current_sweep,        # スイープ番号(品質管理用)
    'i': np.real(iq_data),            # I成分(実部)
    'q': np.imag(iq_data),            # Q成分(虚部)
    'amplitude': np.abs(iq_data),      # 振幅(物体検出用)
    'phase': np.angle(iq_data)         # 位相(移動検出用)
}

距離スイープによる対象物検出

横軸を距離ビン、縦軸をAmplitudeとしてプロットしたグラフは距離スイープと呼ばれ、センサー前方の物体分布を可視化できます。距離ビンは、センサー内部の離散的な測定点を表しており、実際の物理的距離への変換が可能です。

# 距離ビンから実距離への変換
def convert_range_bins_to_distance(range_bins, start_point=80, step_length=0.0025):
    """距離ビンを実際の距離(m)に変換"""
    distances_m = (start_point + range_bins) * step_length
    return distances_m

# 距離スイープの作成
range_bins = np.arange(len(amplitude_data))
distances_m = convert_range_bins_to_distance(range_bins)

# グラフ化(matplotlibを使用)
plt.figure(figsize=(10, 6))
plt.plot(distances_m, amplitude_data)
plt.xlabel('Distance (m)')
plt.ylabel('Amplitude (dB)')
plt.title('Range Sweep - Object Detection')
plt.grid(True)

距離スイープにおけるAmplitudeのピーク値は、そこに反射物体が存在することを示します。このピーク位置(ピークビン)を特定し、その距離での時系列位相データを取得することで、対象物の微小移動を継続的に監視できます。バイタルサイン検出では、人体(主に胸部)からの反射が最も強く現れる距離ビンを特定し、そこでの位相変化を呼吸信号として解析します。

# ピークビンの検出と位相データ抽出
def detect_peak_bin_and_extract_phase(amplitude_data, iq_data):
    """距離スイープからピークビンを検出し、位相データを抽出"""
    # ピーク位置の検出
    peak_bin = np.argmax(amplitude_data)
    peak_amplitude = amplitude_data[peak_bin]
    
    # そのビンでの位相データ抽出
    target_phase = np.angle(iq_data[peak_bin])
    
    return peak_bin, peak_amplitude, target_phase

# 時系列での位相追跡
phase_history = []
for frame_data in continuous_iq_data:
    peak_bin, _, phase_value = detect_peak_bin_and_extract_phase(
        np.abs(frame_data), frame_data
    )
    phase_history.append(phase_value)

# 位相の連続化処理
unwrapped_phase_history = np.unwrap(phase_history)

まとめ

センサーデータの構造

  • 構造: Frame > Sweep > Subsweepの階層構造
  • 成分: I(実部)とQ(虚部)の複素数データ
  • 情報: 振幅(反射強度)、位相(移動検出)、速度(ドップラー)
  • 設定: 用途に応じたSweep/Subsweep数の最適化

Discussion