用 Python 实现一个简单的“人呼吸检测”算法

——从毫米波雷达数据到呼吸曲线
如果你第一次接触毫米波雷达的生命体征检测,很可能会觉得这件事有点神奇。
一个人坐在椅子上几乎没有明显动作,
雷达却能够检测到他的呼吸。
很多人会下意识地认为,这背后一定依赖某种复杂的 AI 算法。但真实情况恰恰相反。
在绝大多数雷达系统中,呼吸检测算法其实非常“朴素”。
核心思想可以概括为一句话:
观察人体胸腔的微小周期运动。
只要雷达系统具有足够好的距离分辨率和相位稳定性,这种毫米级的微小位移就会反映在回波信号的相位变化中。
换句话说,雷达并不是直接“看到”胸腔在运动,而是通过信号中的相位变化推断出这种运动。
在这篇文章中,我们做一件非常具体的事情:
用 Python 写一个简单的呼吸检测算法,从原始雷达数据中恢复出一条呼吸曲线。
整套流程适用于 RS6013A 毫米波雷达实验套件 等能够输出原始 ADC 数据的系统。
一、呼吸检测的物理原理
在开始写代码之前,我们先从物理原理出发,理解雷达是如何感知呼吸的。
假设一个人坐在雷达前方 1 米左右 的位置,保持安静呼吸。
当人体呼吸时,胸腔会产生前后往复的运动,典型位移范围大约是:
几毫米到一厘米。
对于人来说,这样的位移几乎感觉不到,但对于毫米波雷达来说,这已经是一个相当明显的变化。
原因在于毫米波的波长非常短。
以常见的 77 GHz 毫米波雷达 为例,其波长大约为:
λ ≈ 3.9 mm
当胸腔移动 1 mm 时,回波信号的相位就会产生明显变化。
换句话说:
雷达并不是“看见胸腔”,
而是“看见相位在周期变化”。
只要把这种周期变化提取出来,就能得到呼吸信号。


图中示意了毫米波雷达检测呼吸的基本原理:
人体胸腔在呼吸过程中产生毫米级位移,这种微小运动会引起回波信号相位的周期变化。
二、相位变化与位移的数学关系
从数学角度来看,雷达检测呼吸的原理其实非常清晰。
当雷达接收到人体反射的电磁波时,回波信号可以表示为:
s(t)=Aejϕ
s(t) = A e^{j\phi}
s(t)=Aejϕ
其中:
- (AAA) 表示信号幅度
- (ϕ)(\phi)(ϕ) 表示信号相位
对于距离为 ® 的目标,其相位为:
ϕ=4πRλ
\phi = \frac{4\pi R}{\lambda}
ϕ=λ4πR
这里:
- (RRR) 为目标距离
- (λ\lambdaλ) 为雷达波长
当人体呼吸时,胸腔会产生微小位移 (\Delta R)。
此时相位变化为:
Δϕ=4πΔRλ
\Delta \phi = \frac{4\pi \Delta R}{\lambda}
Δϕ=λ4πΔR
这条公式非常关键,它说明:
毫米级位移会转化为明显的相位变化。
举一个简单例子。
对于 77 GHz 雷达:
λ≈3.9mm
\lambda \approx 3.9 mm
λ≈3.9mm
如果胸腔移动 1 mm:
Δϕ≈3.2rad
\Delta\phi \approx 3.2 rad
Δϕ≈3.2rad
这已经是一个非常明显的相位变化。
因此,通过连续观测相位随时间变化,就可以恢复出人体的呼吸节律。
三、呼吸检测的基本流程
从雷达数据到呼吸曲线,大致需要经过几个步骤。
首先,雷达持续发射 FMCW chirp 并采集 ADC 数据。
通过 Range FFT,可以得到每一帧的距离谱。
接下来,在距离谱中找到人体所在的 range bin。
人体所在的距离单元会随着呼吸产生微小相位变化。
然后提取这个 range bin 在连续帧中的相位变化。
最后,通过带通滤波去除噪声,就可以得到稳定的呼吸曲线。
整体流程如下:
ADC数据
↓
Range FFT
↓
定位人体所在距离bin
↓
提取相位变化
↓
带通滤波
↓
呼吸信号

四、Python 实现呼吸检测
假设我们已经从雷达系统获取一段数据:
radar_data.shape = [num_frames , num_samples]
代码如下:
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, filtfilt
# 加载雷达数据
data = np.load("radar_adc.npy")
num_frames, num_samples = data.shape
# Range FFT
range_fft = np.fft.fft(data, axis=1)
# 找到能量最大的距离bin(假设为人体)
range_bin = np.argmax(np.mean(np.abs(range_fft), axis=0))
# 提取该距离bin信号
signal = range_fft[:, range_bin]
# 相位
phase = np.angle(signal)
# 相位展开
phase = np.unwrap(phase)
# 呼吸频率范围
fs = 20
low = 0.1
high = 0.5
b, a = butter(2, [low/fs*2, high/fs*2], btype='bandpass')
breath = filtfilt(b, a, phase)
plt.figure(figsize=(8,4))
plt.plot(breath)
plt.title("Breathing Signal")
plt.xlabel("Frame")
plt.ylabel("Amplitude")
plt.show()
运行之后,你会得到一条比较平滑的曲线。
这条曲线的周期,就是人的呼吸周期。
五、MATLAB 版本实现
如果你使用 MATLAB,代码实现也非常直观:
load radar_adc.mat
[numFrames, numSamples] = size(data);
range_fft = fft(data, [], 2);
power = mean(abs(range_fft),1);
[~,range_bin] = max(power);
signal = range_fft(:,range_bin);
phase = angle(signal);
phase = unwrap(phase);
fs = 20;
[b,a] = butter(2,[0.1 0.5]/(fs/2),'bandpass');
breath = filtfilt(b,a,phase);
plot(breath)
title('Breathing Signal')
xlabel('Frame')
ylabel('Amplitude')
两种实现思路完全一致,只是语法不同。
六、运行结果示意
当算法正常工作时,你会看到类似下面这样的呼吸波形。


曲线大约每 3~5 秒 出现一个周期。
换算为呼吸频率,大概是:
12 ~ 20 次 / 分钟
这正是人体正常呼吸范围。
七、为什么毫米波雷达适合做呼吸检测
相比传统传感器,毫米波雷达有几个天然优势。
首先,它是 非接触式检测技术。
人不需要佩戴设备,只需坐在雷达前方即可检测。
其次,它 不受光照影响。
在完全黑暗环境下也可以工作。
更重要的是,毫米波雷达对 毫米级位移极其敏感。
因此,近年来很多研究和产品开始使用毫米波雷达进行:
- 呼吸检测
- 心跳检测
- 睡眠监测
- 老人跌倒检测
- 智能家居感知
八、从“能检测”到“好用”,还有很多工程细节
上面这套算法虽然简单,但真实系统中还会遇到很多问题,例如:
人体不止一个人时怎么办?
人轻微移动怎么办?
呼吸和心跳如何分离?
工程上通常会加入:
- 背景静态去除
- Range bin 跟踪
- 自适应滤波
- 多目标分离
很多时候,这些工程细节反而比基础算法更复杂。
九、写在最后
很多人第一次看到毫米波雷达呼吸检测时都会觉得:
这是不是某种“黑科技”。
但当你把整个链路拆开,会发现它其实非常朴素:
雷达只是非常擅长观察 微小位移。
当这种位移是周期性的,就变成了呼吸信号。
从 ADC 数据 到 Range FFT,再到 相位变化,这条信号处理链路其实非常优雅。
如果你手里有 RS6013A 毫米波雷达实验套件,完全可以自己试一遍。
第一次看到真实呼吸曲线的时候,你会有一种很特别的感觉:
雷达不仅在“看世界”,
它甚至在“听见生命”。
更多推荐



所有评论(0)