RuView:用WiFi信号"看穿墙壁"------基于CSI的空间感知技术深度解析
你有没有想过,你家里那个落满灰尘的WiFi路由器,其实一直在用无线电波"描绘"整个房间的三维结构?
不是科幻。2023年卡内基梅隆大学(CMU)的DensePose From WiFi研究已经证明:WiFi信号可以像摄像头一样识别人体姿态。而现在,一个叫RuView的开源项目把这项技术从论文搬到了现实------用9美元一块的ESP32芯片,配合Rust写的信号处理管线,实现了穿墙生命体征监测、人体姿态估计和空间占用检测。
没有摄像头,没有可穿戴设备,没有云端。纯粹靠物理。
1. WiFi信号里藏着什么信息?
1.1 CSI:信道状态信息
WiFi路由器持续向外发射射频信号。当这些信号遇到人体、家具、墙壁时,会发生反射、衍射和散射。接收端可以通过一种叫做**信道状态信息(Channel State Information, CSI)**的数据来量化这些变化。
CSI本质上是一个复数矩阵,包含每个OFDM子载波的振幅 和相位:
ini
CSI = [H(f₁, t₁), H(f₂, t₁), ..., H(fₙ, t₁)]
[H(f₁, t₂), H(f₂, t₂), ..., H(fₙ, t₂)]
...
其中 H(f, t) 是频率为 f 的子载波在时刻 t 的频率响应。一个典型的WiFi信道有52-56个子载波,每个子载波携带独立的多径信息。
为什么OFDM子载波这么重要?因为不同频率的信号对空间扰动的敏感度不同。低频子载波穿透力强但空间分辨率低,高频子载波容易被遮挡但分辨率高。把所有子载波的信息综合起来,就能得到一个高维度的空间感知向量。
RuView使用ESP32-S3芯片作为CSI采集节点。ESP32的WiFi驱动被修改后可以输出每个接收到的帧的CSI数据,采样率可达100Hz以上。每个CSI帧包含52个子载波的复数值,单帧数据量约416字节。
1.2 为什么人体运动能被检测到?
人体主要由水组成,水的介电常数远高于空气(约80 vs 1)。当一个人在WiFi信号传播路径上移动时,他会:
- 改变多径传播路径:人体反射和吸收射频能量
- 引入相位偏移:人体的存在改变了信号的传播距离
- 造成振幅衰减:人体吸收部分射频能量
即使是静止不动的人,胸腔随呼吸的起伏(约5mm幅度)也会造成可检测的相位变化。心跳引起的体表微振动(<1mm)同样能被高灵敏度CSI系统捕捉到。
这里有一个直觉上的类比:想象你在一个黑暗的房间里,有人在房间里走动。你看不到他,但你能感觉到空气的流动变化------WiFi感知就像用无线电波"感觉"空气流动。
1.3 多径效应与空间指纹
在室内环境中,WiFi信号不会只走直线。它会从墙壁、地板、天花板、家具上反射,形成多条到达路径。这就是多径效应(Multipath Effect)。
每条路径有不同的长度,因此有不同的相位延迟。接收端收到的信号是所有路径的叠加:
ini
H = Σᵢ aᵢ · e^(-j2πfτᵢ)
其中 aᵢ 是第i条路径的衰减系数,τᵢ 是第i条路径的传播延迟。当人体移动时,会新增或消除某些传播路径,导致叠加结果发生显著变化。
RuView利用这个特性来建立射频指纹(RF Fingerprint)。通过长时间采集某个空间的CSI数据,可以建立该空间的"射频地图"------家具的位置、房间的形状都会编码在CSI的统计特征中。当环境发生变化(如家具被移动),射频指纹也会随之改变,系统就能检测到这种变化。
2. 信号处理管线
2.1 带通滤波提取生命体征
RuView的核心信号处理思路非常直接:不同频段的CSI变化对应不同的物理现象。
呼吸检测:呼吸频率在0.1-0.5 Hz(6-30 BPM)。对CSI振幅信号施加0.1-0.5 Hz带通滤波器,然后用过零检测计算频率:
rust
// RuView中的呼吸检测核心逻辑(简化版)
fn detect_breathing(csi_amplitude: &[f64], sample_rate: f64) -> f64 {
// 1. 带通滤波:0.1-0.5 Hz
let filtered = bandpass_filter(csi_amplitude, 0.1, 0.5, sample_rate);
// 2. 过零检测
let zero_crossings = count_zero_crossings(&filtered);
// 3. 计算BPM
let duration = csi_amplitude.len() as f64 / sample_rate;
let frequency = zero_crossings as f64 / (2.0 * duration);
frequency * 60.0 // 转换为BPM
}
fn bandpass_filter(signal: &[f64], low_hz: f64, high_hz: f64, sample_rate: f64) -> Vec<f64> {
// 二阶Butterworth带通滤波器
let nyquist = sample_rate / 2.0;
let low_normalized = low_hz / nyquist;
let high_normalized = high_hz / nyquist;
// 计算滤波器系数
let (b, a) = butterworth_coefficients(2, low_normalized, high_normalized);
// 应用IIR滤波
apply_iir_filter(signal, &b, &a)
}
心率检测:心率在0.8-2.0 Hz(40-120 BPM),使用同样的过零检测方法,只是滤波器通带不同。
这种基于频域分离的方法简单但有效。呼吸和心跳在频域上天然分离,不需要复杂的盲源分离算法。
实际测试中发现一个问题:当人坐着不动时,呼吸信号很稳定,但心率信号容易被身体的微小移动干扰。解决方法是在心率检测前加一个**自适应噪声消除(ANC)**步骤------用另一个子载波的信号作为参考噪声,通过LMS算法消除体动干扰。
2.2 Fresnel区模型与穿墙检测
WiFi信号的空间传播可以用Fresnel区来建模。第一Fresnel区是一个以发射端和接收端为焦点的椭球体。当人体进入某个Fresnel区时,会对该区域内的信号产生最大影响。
python
# Fresnel区边界计算
import numpy as np
def fresnel_zone_radius(d1, d2, wavelength, n=1):
"""
计算第n Fresnel区在指定点的半径
Args:
d1: 到发射端的距离
d2: 到接收端的距离
wavelength: 信号波长
n: Fresnel区序号
"""
total_distance = d1 + d2
return np.sqrt(n * wavelength * d1 * d2 / total_distance)
# 2.4 GHz WiFi的波长约为0.125m
wavelength = 0.125 # meters
# 计算在房间中心处的第一Fresnel区半径
d1, d2 = 3.0, 3.0 # 发射端和接收端各距中心3米
radius = fresnel_zone_radius(d1, d2, wavelength, n=1)
print(f"第一Fresnel区半径: {radius:.2f}m") # ≈ 0.43m
RuView利用这个模型实现了穿墙检测。当多个CSI节点部署在不同位置时,每个节点的Fresnel区交叉覆盖,人体在任何位置都会扰动至少一个节点的信号。
穿墙检测的物理原理是:WiFi信号(尤其是2.4GHz)可以穿透大多数非金属墙壁。信号穿过墙壁时会有衰减(通常5-15dB),但仍有足够的能量在墙的另一侧形成Fresnel区。当有人在墙的另一侧走动时,信号的多径结构会发生变化,这种变化可以被检测到。
2.3 去噪与异常值处理
原始CSI数据噪声很大------环境中的其他WiFi网络、蓝牙设备、微波炉都会造成干扰。RuView采用了多级去噪策略:
rust
// CSI数据预处理管线
fn preprocess_csi(raw_csi: Vec<CsiFrame>) -> Vec<CsiFrame> {
// 1. 相位展开(Phase Unwrapping)
let unwrapped = unwrap_phase(&raw_csi);
// 2. 移除线性相位偏移(去除频率偏移引起的相位漂移)
let calibrated = remove_linear_phase(&unwrapped);
// 3. Hampel滤波器去除异常值
let filtered = hampel_filter(&calibrated, window_size=5, threshold=3.0);
// 4. 移动平均平滑
let smoothed = moving_average(&filtered, window_size=3);
smoothed
}
fn hampel_filter(signal: &[f64], window_size: usize, threshold: f64) -> Vec<f64> {
let mut result = signal.to_vec();
for i in window_size..signal.len() - window_size {
let window = &signal[i - window_size..=i + window_size];
let median = median(window);
let mad = 1.4826 * median_absolute_deviation(window, median);
if (signal[i] - median).abs() > threshold * mad {
result[i] = median; // 用中位数替换异常值
}
}
result
}
Hampel滤波器比简单的均值滤波更鲁棒------它用中位数和MAD(中位绝对偏差)来检测异常值,对脉冲干扰有很好的抵抗力。
相位展开是另一个关键步骤。WiFi CSI的相位值被限制在[-π, π]范围内,当实际相位变化超过这个范围时会出现跳变。相位展开算法通过检测并补偿这些跳变,恢复出连续的相位曲线。这对后续的频率分析至关重要。
3. 姿态估计:从WiFi到人体关键点
3.1 WiFlow架构
RuView的姿态估计模块基于CMU的DensePose From WiFi研究,使用了名为WiFlow的架构。核心思路是:
- 输入:10个CSI传感器的信号(振幅+相位)
- 特征提取:1D卷积网络提取时频特征
- 关键点回归:输出17个COCO标准人体关键点的坐标
python
import torch
import torch.nn as nn
class WiFlowEncoder(nn.Module):
"""CSI特征提取网络"""
def __init__(self, num_sensors=10, num_subcarriers=52):
super().__init__()
# 每个传感器输入: [振幅, 相位] × 子载波数
self.input_dim = num_sensors * 2 * num_subcarriers
self.conv_blocks = nn.Sequential(
nn.Conv1d(1, 64, kernel_size=7, padding=3),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(64, 128, kernel_size=5, padding=2),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm1d(256),
nn.ReLU(),
nn.AdaptiveAvgPool1d(16),
)
self.fc = nn.Linear(256 * 16, 17 * 2) # 17个关键点, 每个(x, y)
def forward(self, csi_input):
# csi_input: [batch, num_sensors * 2 * num_subcarriers, time_steps]
x = self.conv_blocks(csi_input)
x = x.flatten(1)
keypoints = self.fc(x)
return keypoints.view(-1, 17, 2) # [batch, 17, 2]
这个网络结构并不复杂------3层1D卷积加一个全连接层。关键在于输入数据的构造:把10个传感器的振幅和相位数据拼接成一个高维向量,让网络自己学习如何融合多传感器信息。
3.2 无摄像头训练:代理标签方法
一个关键问题是:没有摄像头怎么训练?
RuView采用了一种**代理标签(Proxy Labels)**方法:
- 部署阶段:在目标空间同时放置CSI传感器和摄像头
- 数据采集:摄像头提取DensePose标注作为ground truth
- 模型训练:用CSI数据训练模型预测摄像头的结果
- 实际使用:移除摄像头,模型仅依赖CSI工作
这意味着训练需要一次性的摄像头辅助,但部署后完全不需要摄像头。目前PCK@20(20%阈值内的正确关键点比例)约为2.5%,看起来很低,但这是一个无摄像头系统------在完全黑暗、有遮挡的环境下也能工作。
项目README中提到,正在推进ADR-079------使用摄像头ground truth进行训练,目标是将PCK@20提升到35%以上。这个管线已经实现,但数据采集和评估阶段还在进行中。
3.3 脉冲神经网络:更省电的推理方案
RuView的边缘AI模块使用**脉冲神经网络(Spiking Neural Networks, SNN)**进行推理。与传统的连续激活函数不同,SNN使用离散的脉冲信号,更接近生物神经系统的工作方式。
SNN的优势在于:
- 能耗极低:只有在脉冲发放时才消耗能量,大部分时间是静默的
- 时序信息天然编码:脉冲的时间本身就携带信息,非常适合处理时序CSI数据
- 硬件友好:可以在神经形态芯片上高效运行
RuView使用8维特征向量作为SNN的输入,这些特征从原始CSI数据中提取------包括振幅均值、相位方差、频谱能量等统计量。SNN在Cognitum Seed上运行,推理延迟在毫秒级。
4. 边缘计算架构
4.1 ESP32网状网络
RuView的硬件基础是一个ESP32网状网络。每个节点成本约9-15美元,运行Rust编写的固件:
toml
# Cargo.toml (ESP32固件)
[package]
name = "ruview-node"
version = "0.1.0"
edition = "2021"
[dependencies]
esp-idf-sys = { version = "0.36", features = ["native"] }
esp-idf-hal = "0.43"
esp-wifi = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[features]
default = ["esp32s3"]
esp32s3 = []
多个ESP32节点组成mesh网络,每个节点负责采集其覆盖区域的CSI数据。节点之间通过ESP-NOW协议低延迟通信(延迟<1ms)。
ESP-NOW是乐鑫开发的轻量级通信协议,工作在WiFi物理层上但不需要建立WiFi连接。它的优势是延迟极低(通常<1ms)、功耗低、支持一对多通信。在RuView中,所有传感器节点将CSI数据通过ESP-NOW发送给主节点,主节点汇总后进行高级处理。
4.2 Cognitum Seed:边缘AI推理
CSI数据的高级处理(姿态估计、活动识别)在Cognitum Seed上运行------这是一个边缘AI计算模块,BOM成本约140美元。它提供:
- 脉冲神经网络(SNN)推理:比传统DNN更省电
- 加密见证链:每次测量都用Ed25519签名,防篡改
- 本地持久化记忆:环境模型存储在本地,不依赖云端
rust
// 加密见证链实现
use ed25519_dalek::{Keypair, Signature, Signer, Verifier};
struct WitnessChain {
keypair: Keypair,
chain: Vec<WitnessEntry>,
}
struct WitnessEntry {
timestamp: u64,
measurement_hash: [u8; 32],
previous_signature: Signature,
signature: Signature,
}
impl WitnessChain {
fn attest(&mut self, measurement: &CsiMeasurement) -> WitnessEntry {
let hash = sha256(measurement.to_bytes());
let previous_sig = self.chain.last()
.map(|e| e.signature)
.unwrap_or(Signature::new([0; 64]));
let message = [hash.as_slice(), previous_sig.to_bytes().as_slice()].concat();
let signature = self.keypair.sign(&message);
let entry = WitnessEntry {
timestamp: current_timestamp(),
measurement_hash: hash.into(),
previous_signature: previous_sig,
signature,
};
self.chain.push(entry.clone());
entry
}
}
这种设计使得每个测量值都有密码学证明。在医疗监测场景中,如果需要向医生证明某次心率数据确实是在某个时间点采集的,见证链可以提供不可伪造的证据。
4.3 系统总架构
把所有组件组合起来,RuView的完整数据流是这样的:
markdown
ESP32节点×N → ESP-NOW → 主节点 → CSI原始数据 → 去噪管线
→ 特征提取 → SNN推理 → 活动识别/姿态估计
→ 见证链签名 → 本地存储/WebSocket推送
硬件成本分解:
- ESP32-S3 × 3节点: $27
- Cognitum Seed: $140
- 电源/外壳/线材: ~$20
- 总计: ~$187(不到一台入门级摄像头的价格)
5. 实际部署与踩坑
5.1 传感器布局
传感器的放置对检测效果影响巨大。以下是我实际测试中的经验:
css
推荐布局(2节点,覆盖20㎡房间):
[Node A] [Node B]
\ /
\ Fresnel Zone 1 /
\ ___________ /
\ / \ /
\/ 人体 \/
/\ /\
/ \___________/ \
/ Fresnel Zone 2 \
/ \
[墙壁] [墙壁]
- 两个节点间距 3-6 米
- 高度 1.2-1.5 米(与人体躯干同高)
- 避免金属物体遮挡直射路径
5.2 常见问题与解决
问题1:CSI数据缺失帧
ESP32的WiFi驱动在高负载时会丢帧。解决方法是在采集端做插值:
rust
fn interpolate_missing_frames(frames: Vec<Option<CsiFrame>>) -> Vec<CsiFrame> {
let mut result = Vec::with_capacity(frames.len());
for i in 0..frames.len() {
if let Some(frame) = &frames[i] {
result.push(frame.clone());
} else {
// 线性插值:找前后最近的有效帧
let prev = (0..i).rev().find_map(|j| frames[j].as_ref());
let next = (i+1..frames.len()).find_map(|j| frames[j].as_ref());
match (prev, next) {
(Some(p), Some(n)) => result.push(interpolate(p, n, 0.5)),
(Some(p), None) => result.push(p.clone()),
(None, Some(n)) => result.push(n.clone()),
_ => {} // 两端都无数据,跳过
}
}
}
result
}
问题2:环境变化导致误报
家具移动、温度变化都会影响CSI基线。RuView用自适应基线校准解决这个问题------每30秒更新一次环境基线,使用指数移动平均:
rust
fn update_baseline(current: &CsiFrame, baseline: &mut CsiFrame, alpha: f64) {
for i in 0..baseline.subcarriers.len() {
baseline.subcarriers[i] = alpha * current.subcarriers[i]
+ (1.0 - alpha) * baseline.subcarriers[i];
}
}
// alpha = 0.05 时,约10分钟完成一次完整的基线更新
问题3:多WiFi网络干扰
2.4GHz频段拥挤时CSI数据噪声增大。解决方案:
- 切换到5GHz频段(可用子载波更多,带宽更大)
- 使用信道跳频(Channel Hopping)避开拥挤信道
- 增加节点数量提高冗余度
问题4:ESP32发热导致CSI漂移
长时间运行后ESP32芯片温度升高,会导致WiFi射频参数变化,引起CSI数据的系统性偏移。解决方法是在固件中加入温度补偿:
rust
fn temperature_compensate(csi: &mut CsiFrame, chip_temp: f32) {
// 温度偏移系数(经验值,需要针对具体硬件校准)
let temp_coeff = 0.002; // 每度偏移0.2%
let ref_temp = 25.0; // 参考温度25°C
let correction = 1.0 + temp_coeff * (chip_temp - ref_temp);
for amp in &mut csi.amplitudes {
*amp *= correction;
}
}
6. 性能数据
| 指标 | 数值 | 备注 |
|---|---|---|
| 姿态估计速度 | 171K embeddings/s | M4 Pro处理器 |
| 呼吸检测范围 | 6-30 BPM | 过零检测 |
| 心率检测范围 | 40-120 BPM | 带通滤波 |
| 存在检测延迟 | 0.012 ms | 模型+PIR融合 |
| 穿墙深度 | 最远5米 | Fresnel区+多径建模 |
| 单节点成本 | ~$9 | ESP32-S3 |
| 完整系统成本 | ~$140 | ESP32 mesh + Cognitum Seed |
| 姿态估计精度(PCK@20) | ~2.5% | 无摄像头代理标签 |
| SNN特征维度 | 8维 | 脉冲神经网络输入 |
| 多频扫描 | 6个WiFi信道 | 利用邻居路由器 |
7. 隐私与伦理
WiFi感知最大的优势也是最敏感的点------它不需要摄像头。这意味着:
- 隐私友好:没有图像数据,不会拍到人脸
- 穿墙能力:可以检测到隔壁房间的人
- 无法"关闭":只要WiFi开着就在被感知
RuView通过Ed25519见证链确保数据不被滥用,但穿墙检测能力带来的隐私问题仍然值得讨论。在一些国家,使用这类技术可能需要获得被监测者的同意。
项目本身是MIT开源协议,代码完全透明。用户可以审计数据流向,也可以断开网络让系统完全离线运行。这种设计从架构层面保障了用户对数据的控制权。
8. 论文引用
RuView的技术基础来自以下学术研究:
-
DensePose From WiFi - Carnegie Mellon University
- 论文:DensePose From WiFi
- 核心贡献:证明WiFi CSI可以用于人体姿态估计,提出WiFlow架构
-
Wi-Fi Sensing: A Survey - IEEE Communications Surveys & Tutorials
- 综述WiFi感知技术的发展历程和应用场景
-
PhaseFi: Phase-based Fingerprinting for Indoor Localization - IEEE
- 基于CSI相位信息的室内定位技术
-
Ed25519: high-speed high-security signatures - Daniel J. Bernstein
- RuView见证链使用的签名算法
9. 我的使用感受
说实话,第一次看到这个项目的时候我是怀疑的。WiFi信号真的能检测心率?但实际跑起来之后,确实能检测到------虽然精度比不上专业的胸带式心率计,但作为一个完全无接触的方案,已经很惊人了。
优点:
- 硬件成本极低,ESP32到处都能买到
- Rust写的信号处理管线性能很好,内存占用低
- 文档和架构设计很完善(有ADR、PRD等工程文档)
- 加密见证链的设计很有远见,为数据可信度提供了保障
- 项目活跃度很高,Issues和PRs都很活跃
不足:
- 姿态估计精度目前还比较低(PCK@20 ≈ 2.5%)
- ESP32的CSI驱动不稳定,偶尔丢帧
- 需要至少2个节点才能获得可用的空间分辨率
- 训练阶段仍需摄像头辅助
- 对于完全不懂信号处理的开发者,入门门槛偏高
适合人群:
- 对IoT和嵌入式开发感兴趣的工程师
- 想做智能家居感知系统的开发者
- 研究无线感知的学术人员
- 对隐私友好型传感器有需求的产品经理
推荐指数:⭐⭐⭐⭐☆(4/5)
这个项目的技术方向很有价值------用廉价硬件+开源软件实现原本需要昂贵设备才能做到的感知能力。虽然目前还在beta阶段,但已经能看出它在智能家居、养老看护、安防监控等场景的巨大潜力。想象一下:老人在家摔倒了,不需要佩戴任何设备,WiFi系统就能自动检测并报警。这种能力在老龄化社会中价值巨大。
期待后续在姿态估计精度上的提升,以及更多社区贡献者的加入。