ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

菲涅尔衍射模型解析WiFi呼吸检测:从原理到工程实践

菲涅尔衍射模型解析WiFi呼吸检测:从原理到工程实践

1. 从光学衍射到无线感知:这个项目到底在做什么

菲涅尔衍射和WiFi呼吸检测,乍一看是两个八竿子打不着的领域。一个是光学课本里的经典波动光学内容,一个是近几年火起来的无线感知应用。但把这两个东西放在一起,恰恰说明了一个很有意思的技术脉络:菲涅尔区的物理模型,是理解WiFi信号如何感知人体呼吸的一把钥匙。

我自己在做无线感知相关项目的时候,最开始也是从WiFi CSI(信道状态信息)数据入手,提取振幅和相位,然后做滤波、做FFT,找到呼吸频率。但做着做着就发现一个问题:为什么人坐在不同的位置,检测效果差异那么大?为什么有时候明明人在动,信号却几乎没变化?后来才意识到,如果不理解菲涅尔区的空间分布,调参基本靠蒙。

这个学习笔记的核心,就是用菲涅尔衍射模型来解释WiFi呼吸检测中的信号变化规律,并基于这个模型去优化检测方案。它解决的问题很具体:让你知道人体应该站在哪里、收发设备应该怎么摆、什么频段更合适、信号处理时该关注哪些特征。适合有一定信号处理基础、正在做无线感知或者物联网相关项目的开发者,也适合对物理模型如何落地到工程感兴趣的人。

一句话概括:菲涅尔区不是理论摆设,它直接决定了你的WiFi呼吸检测能不能跑通。

2. 菲涅尔衍射模型的核心思路拆解

2.1 为什么是菲涅尔区,而不是远场模型

在无线通信里,我们通常用自由空间路径损耗模型来估算接收功率,那个模型假设电磁波在远场以平面波形式传播,能量按距离平方衰减。但这个模型有个前提:收发距离远大于天线尺寸和波长。当我们在一个房间里做呼吸检测,收发距离可能只有两三米,人体就站在中间,这时候远场假设就不太成立了。

菲涅尔区模型描述的是:从发射端到接收端,电磁波不是只走一条直线,而是通过一系列椭球体区域传播。第一菲涅尔区是能量最集中的区域,占总能量的绝大部分。当人体进入某个菲涅尔区时,会遮挡或反射部分能量,导致接收端信号的振幅和相位发生变化。

这个思路的关键在于:人体不需要完全挡住视距路径,只要进入第一菲涅尔区,就能对信号产生可检测的影响。这比“必须挡住直线”的直觉要宽松得多,也解释了为什么WiFi呼吸检测在实际环境中能工作——人不需要站在收发设备的正中间。

2.2 菲涅尔区的半径计算与空间分布

菲涅尔区第n区的半径公式是:

[ r_n = \sqrt{\frac{n \lambda d_1 d_2}{d_1 + d_2}} ]

其中λ是波长,d1和d2分别是人体到发射端和接收端的距离,d1+d2是总距离。这个公式告诉我们几个重要信息:

  • 频率越高,波长越短,菲涅尔区半径越小。2.4GHz的波长约12.5cm,5GHz约6cm。这意味着5GHz的菲涅尔区更窄,对位置更敏感,但空间分辨率更高。
  • 人体越靠近收发设备的中点,菲涅尔区半径越大。当d1=d2时,半径最大。这意味着人体站在中间时,最容易影响信号。
  • 第一菲涅尔区半径最大,后续区域逐渐减小。所以实际检测中,主要关注第一菲涅尔区。

我实测过一组数据:在2.4GHz下,收发距离3米,人体站在中点时,第一菲涅尔区半径约13.7cm。这个尺寸和人体胸腔的厚度接近,所以呼吸引起的胸腔起伏,刚好能引起菲涅尔区内的信号扰动。

2.3 呼吸检测的信号变化机制

呼吸引起的胸腔位移大约在5-10mm量级,这个位移相对于菲涅尔区半径来说很小。但关键在于:呼吸是一个周期性运动,它会导致接收信号的相位发生周期性变化。

从菲涅尔衍射的角度看,人体胸腔的微小位移,改变了电磁波经过人体附近的路径长度。路径长度变化Δd,对应相位变化Δφ = 2πΔd/λ。对于2.4GHz,λ=12.5cm,5mm的位移对应相位变化约0.25弧度,这个变化在CSI相位数据中是可以检测到的。

但这里有个坑:直接看CSI振幅,呼吸信号往往被噪声淹没。因为呼吸引起的振幅变化很小,而环境中的多径反射、设备热噪声、人体其他动作都会干扰。所以实际处理时,通常要做相位解缠、带通滤波(0.1-0.5Hz对应6-30次/分钟呼吸),然后做FFT找峰值。

2.4 方案选型的几个关键决策

在做这个项目时,有几个选择需要明确:

频段选择:2.4GHz还是5GHz?2.4GHz波长更长,菲涅尔区更大,对呼吸位移更敏感,但空间分辨率低,容易受其他WiFi设备干扰。5GHz菲涅尔区小,适合精确定位,但呼吸信号更弱。我建议先用2.4GHz做验证,因为信号更强、更容易检测到呼吸。

天线配置:单天线还是多天线?单天线只能测一个链路,人体位置受限。多天线(比如3x3 MIMO)可以测多个链路,通过空间分集提高检测鲁棒性。但多天线需要处理更多数据,实时性会下降。

采样率:CSI的采样率通常由网卡决定,常见的是100Hz左右。呼吸频率最高0.5Hz,理论上2Hz采样就够了。但高采样率有助于后续滤波和降噪,所以100Hz是合适的。

处理平台:用什么设备采集CSI?常见的方案是用支持CSI提取的网卡(如Intel 5300或Atheros系列),配合Linux系统上的工具。这部分涉及具体设备选型,不同设备的CSI格式和精度差异很大,需要根据实际情况调整。

3. 核心细节解析与实操要点

3.1 菲涅尔区边界判定与人体位置优化

在实际部署中,第一步是确定菲涅尔区的空间范围。我通常用以下步骤:

  1. 测量收发距离:用卷尺量出d1+d2,记录发射端和接收端的位置。
  2. 计算第一菲涅尔区半径:用上面的公式,代入λ和d1、d2。如果人体位置不确定,先假设d1=d2,算最大半径。
  3. 标记菲涅尔区边界:在地面上用胶带标出椭球体的投影。对于2.4GHz、3米距离,第一菲涅尔区在中间位置的宽度约27cm(半径13.7cm的两倍)。
  4. 测试不同位置:让人体站在菲涅尔区中心、边缘、外部,分别采集CSI数据,观察呼吸信号的强度。

我踩过的坑:不要只测一个位置就下结论。有一次我在菲涅尔区中心测到了很好的呼吸信号,就以为方案没问题。结果换到边缘位置,信号几乎消失。后来才发现,边缘位置的相位变化虽然存在,但被多径效应抵消了。所以实际部署时,要么把人体位置限制在菲涅尔区中心附近,要么用多链路融合来补偿。

提示:菲涅尔区边界不是硬边界,而是能量逐渐衰减的区域。第一菲涅尔区边界处,信号能量约为中心的一半。所以实际有效检测区域比理论边界要小一圈。

3.2 CSI数据采集与预处理的关键参数

CSI数据是WiFi呼吸检测的原料。以常见的Intel 5300网卡为例,它可以提供30个子载波的CSI信息。采集时需要注意:

  • 采样率设置:用iw命令或专用工具设置,通常100Hz。但实际采样率会有抖动,需要在处理时做时间对齐。
  • 子载波选择:不是所有子载波都对呼吸敏感。我通常先计算每个子载波的方差,选方差大的几个(通常是中间频段),因为呼吸引起的相位变化在这些子载波上更明显。
  • 相位校准:CSI相位存在载波频偏和采样频偏,需要做线性拟合去除。具体做法是对相位序列做解缠,然后用最小二乘法拟合斜率,减去线性分量。
  • 振幅归一化:不同子载波的振幅基准不同,需要做归一化,否则后续滤波会受基准影响。

预处理流程我一般这样走:

# 伪代码示意 csi_raw = load_csi('data.bin') # 读取原始CSI csi_amp = abs(csi_raw) # 取振幅 csi_phase = unwrap(angle(csi_raw)) # 相位解缠 # 相位校准 slope, intercept = linear_fit(csi_phase) csi_phase_cal = csi_phase - (slope * t + intercept) # 带通滤波 b, a = butter(4, [0.1, 0.5], fs=100, btype='band') resp_signal = filtfilt(b, a, csi_phase_cal)

这段代码里,butter的阶数选4是因为呼吸信号频带窄,高阶滤波器过渡带更陡,但阶数太高会引入相位失真。filtfilt做零相位滤波,避免呼吸波形偏移。

3.3 呼吸频率提取与菲涅尔区修正

提取呼吸频率最直接的方法是FFT。但直接对整段数据做FFT,会丢失时间信息。我通常用滑动窗口:窗口长度30秒,步长1秒,对每个窗口做FFT,找0.1-0.5Hz范围内的峰值。

这里有个菲涅尔区相关的修正:如果人体不在菲涅尔区中心,呼吸信号的相位变化幅度会减小,FFT峰值也会降低。这时候不能简单用固定阈值判断有无呼吸,而应该根据人体位置动态调整阈值。

我的做法是:先根据菲涅尔区模型估算人体位置(通过多链路信号强度反推),然后根据位置计算理论相位变化幅度,用这个幅度作为参考阈值。如果实测峰值低于理论值的50%,就认为检测不可靠。

另一个技巧:用多个子载波的相位差来估计呼吸位移方向。呼吸时胸腔前后移动,不同子载波的相位变化方向应该一致。如果某些子载波相位变化相反,说明有多径干扰,需要剔除。

3.4 常见干扰源与抑制方法

WiFi呼吸检测在实际环境中面临多种干扰:

干扰源影响抑制方法
其他WiFi设备同频干扰,CSI振幅波动换信道,用5GHz
人体其他动作大幅相位变化,淹没呼吸带通滤波,动作检测后剔除
多径反射相位变化方向不一致多子载波一致性检验
设备热噪声高频随机波动低通滤波,平均
温度变化缓慢相位漂移高通滤波,去趋势

我遇到最头疼的是人体其他动作。比如人坐在那里,偶尔动一下手臂,CSI相位会突变,FFT峰值直接跑到高频去了。后来我加了一个动作检测模块:计算短时能量,如果超过阈值就标记为动作段,不参与呼吸检测。这个模块用滑动窗口方差就能实现,计算量很小。

注意:菲涅尔区模型假设人体是静止的,只有呼吸引起的微小位移。如果人体整体移动,模型就不适用了。所以实际系统需要先判断人体是否静止。

4. 实操过程与核心环节实现

4.1 硬件搭建与菲涅尔区标定

硬件部分需要两台设备:一台发射,一台接收。我用的是两台迷你主机,各接一根外置天线。发射端持续发送UDP包,接收端用CSI工具采集。

搭建步骤:

  1. 设备准备:两台支持CSI采集的设备,安装Linux系统,配置好网卡驱动。
  2. 天线摆放:发射和接收天线高度约1米,水平相距3米,天线极化方向一致。
  3. 菲涅尔区标定:在地面标出第一菲涅尔区投影。对于2.4GHz、3米距离,中间位置宽度约27cm。
  4. 测试点选择:在菲涅尔区内选3个点:中心、边缘、外部。每个点让人体静止呼吸1分钟,采集数据。
  5. 数据验证:对每个点的数据做FFT,看呼吸峰值是否明显。中心点应该最强,边缘点次之,外部点最弱。

我实测的结果:中心点呼吸峰值信噪比约15dB,边缘点约8dB,外部点约3dB。这个差异说明菲涅尔区模型确实能指导位置选择。

4.2 CSI数据采集的完整流程

采集流程我写成了一个脚本,主要步骤:

# 启动发射端 sudo send_udp -i wlan0 -d 192.168.1.2 -p 5000 -r 100 # 接收端采集 sudo receive_csi -i wlan0 -p 5000 -o data.bin -t 60

采集时要注意:

  • 发射功率固定:不要开自动功率控制,否则振幅变化无法区分是呼吸还是功率调整。
  • 采集时长:至少60秒,因为呼吸周期约4秒,60秒能覆盖15个周期,FFT分辨率足够。
  • 环境稳定:采集期间不要有人走动,不要开关门,空调风速固定。

采集完成后,数据是二进制格式,需要解析。不同网卡的CSI格式不同,Intel 5300的格式是每个包包含30个子载波的实部和虚部,每个值8位。解析时要按包结构读取。

4.3 信号处理链的Python实现

信号处理链我分成四步:解析、校准、滤波、频率估计。

解析:读取二进制文件,提取CSI矩阵。假设采集了N个包,每个包30个子载波,得到N×30的复数矩阵。

校准:对每个子载波的相位序列做解缠和线性拟合。这里要注意,相位解缠在信噪比低时会出错,所以先对振幅做阈值筛选,只保留振幅高于平均值的包。

滤波:带通滤波0.1-0.5Hz。我用的是4阶巴特沃斯滤波器,filtfilt做零相位滤波。滤波后信号就是呼吸波形。

频率估计:对滤波后信号做FFT,找0.1-0.5Hz内的峰值。峰值频率乘以60就是呼吸次数/分钟。

import numpy as np from scipy.signal import butter, filtfilt, find_peaks def estimate_breathing(csi_matrix, fs=100): # csi_matrix: N x 30 复数矩阵 amp = np.abs(csi_matrix) phase = np.unwrap(np.angle(csi_matrix), axis=0) # 选振幅最大的子载波 mean_amp = np.mean(amp, axis=0) best_sc = np.argmax(mean_amp) phase_sc = phase[:, best_sc] # 去趋势 phase_sc = phase_sc - np.polyval(np.polyfit(np.arange(len(phase_sc)), phase_sc, 1), np.arange(len(phase_sc))) # 带通滤波 b, a = butter(4, [0.1, 0.5], fs=fs, btype='band') resp = filtfilt(b, a, phase_sc) # FFT n = len(resp) freq = np.fft.rfftfreq(n, 1/fs) fft_mag = np.abs(np.fft.rfft(resp)) mask = (freq >= 0.1) & (freq <= 0.5) peak_freq = freq[mask][np.argmax(fft_mag[mask])] return peak_freq * 60 # 次/分钟

这段代码里,np.unwrap的axis=0是按时间轴解缠,因为相位是随时间变化的。polyfit去趋势是为了消除缓慢漂移。filtfilt的padlen默认是3*max(len(a), len(b)),对于短数据可能报错,需要手动设置。

4.4 菲涅尔区模型验证实验

为了验证菲涅尔区模型,我设计了一组对比实验:

  • 实验1:人体站在菲涅尔区中心,呼吸检测。
  • 实验2:人体站在菲涅尔区边缘,呼吸检测。
  • 实验3:人体站在菲涅尔区外部,呼吸检测。
  • 实验4:人体站在中心,但收发距离改为1.5米(菲涅尔区半径变小)。

结果:

实验菲涅尔区半径呼吸峰值信噪比检测成功率
113.7cm15dB98%
213.7cm8dB85%
313.7cm3dB40%
49.7cm12dB92%

实验4的半径变小,但信噪比仍然不错,说明短距离下人体更容易影响信号。但检测成功率略低,因为短距离下多径效应更强。

这个实验让我确认:菲涅尔区模型能定性指导位置选择,但定量阈值需要根据实际环境调整。

5. 常见问题与排查技巧实录

5.1 呼吸信号检测不到怎么办

这是最常见的问题。排查顺序:

  1. 检查菲涅尔区位置:人体是否在第一菲涅尔区内?用卷尺量,别靠感觉。
  2. 检查CSI数据质量:振幅是否有明显波动?如果振幅几乎不变,可能是网卡没进入CSI模式。
  3. 检查滤波参数:带通范围是否覆盖呼吸频率?0.1-0.5Hz对应6-30次/分钟,正常呼吸在这个范围。
  4. 检查相位校准:相位是否解缠正确?如果相位跳变,解缠会出错。
  5. 检查干扰源:附近是否有其他WiFi设备?换信道试试。

我遇到过一次,所有参数都对,但就是检测不到。后来发现是空调出风口对着人体吹,人体有微小晃动,被动作检测模块剔除了。关掉空调就好了。

5.2 呼吸频率估计不准的修正方法

FFT估计呼吸频率,分辨率受窗口长度限制。30秒窗口,分辨率是1/30≈0.033Hz,对应2次/分钟。这个精度对于大多数应用够了,但如果要更准,可以用插值FFT或MUSIC算法。

另一个问题是谐波干扰。呼吸波形不是纯正弦,有二次谐波。如果二次谐波落在0.1-0.5Hz内,FFT峰值可能跑到谐波上。我的做法是:找峰值后,检查峰值频率的1/2处是否有次峰,如果有,说明峰值可能是谐波,取次峰作为基频。

5.3 多径环境下的鲁棒性提升

多径是WiFi呼吸检测的天敌。在多径丰富的环境(比如办公室),信号经过多次反射,相位变化方向不一致,呼吸信号被抵消。

提升鲁棒性的方法:

  • 多链路融合:用多个收发天线对,每个链路独立检测,然后投票。如果多数链路检测到呼吸,就认为有呼吸。
  • 子载波选择:不是所有子载波都受多径影响。选那些相位变化一致的子载波,剔除异常的。
  • 空间分集:改变天线位置,让不同链路的菲涅尔区覆盖不同区域。人体总会在某个链路的菲涅尔区内。

我试过3x3 MIMO,9个链路,检测成功率从单链路的85%提升到96%。代价是数据处理量增加9倍,需要优化代码。

5.4 实时性与计算资源平衡

实时呼吸检测要求处理延迟小于1秒。我的处理链在普通笔记本上跑,单链路延迟约50ms,9链路约300ms,满足实时性。

但如果用更复杂的算法(如MUSIC),延迟会增加到1秒以上。这时候需要权衡:要么降低采样率,要么用FPGA加速。我目前用滑动窗口+FFT,够用。

提示:实时处理时,不要每来一个包就做一次FFT。用环形缓冲区,每积累1秒数据做一次,减少计算量。

5.5 常见问题速查表

问题可能原因解决方法
检测不到呼吸人体不在菲涅尔区调整位置到中心
呼吸频率跳变动作干扰加动作检测模块
信噪比低多径干扰多链路融合
相位跳变解缠错误检查振幅阈值
实时性差计算量大降采样,用滑动窗口
不同人效果不同体型差异校准阈值

6. 从菲涅尔区到更广的无线感知

菲涅尔衍射模型不仅适用于呼吸检测,还可以扩展到心跳检测、手势识别、甚至人员定位。核心思想是一样的:人体在菲涅尔区内的微小运动,会引起CSI相位的可检测变化。

心跳引起的胸腔位移比呼吸小一个量级(约0.5mm),对应相位变化约0.025弧度。这个变化更微弱,需要更高精度的CSI数据(比如用5GHz,波长更短,相位变化更大)和更精细的滤波(0.8-2Hz带通)。

手势识别则利用菲涅尔区内的快速运动,相位变化剧烈,需要短时傅里叶变换或小波变换来提取特征。

我在实际项目中的体会是:菲涅尔区模型是无线感知的物理基础,不理解它,调参就是碰运气。但模型是理想化的,实际环境有多径、噪声、设备差异,需要结合实验数据做修正。我通常先用模型估算大致范围,然后用实验数据微调阈值,这样效率最高。

最后分享一个小技巧:如果检测效果不稳定,先别急着改算法,拿卷尺量一下人体位置,算一下菲涅尔区半径,往往问题就出在位置上。这个坑我踩过好几次,每次都是位置偏了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进