WiFi就是传感器:开源项目RuView如何用普通路由器实现无摄像头人体感知
你家的路由器每时每刻都在向房间里喷射无线电波。当有人走动、呼吸甚至静坐时,这些电磁波会被身体扰动,并在接收端留下可被测量的指纹。本文从程序员视角拆解:如何用一块九美元的 ESP32,把「WiFi 信号」变成一台不需要摄像头、不需要可穿戴设备的人体感知雷达。
一、背景:当 WiFi 变成「透视眼」
2013 年,MIT CSAIL 的 Dina Katabi 团队在《Through-Wall Human Pose Estimation Using Radio Signals》中证明了一件反直觉的事:无线电信号可以穿墙感知人体。五年后,同一团队在 CVPR 2018 上发布了 RF-Pose,用 WiFi 频段的射频信号重建出人体的骨架关键点。这项研究引爆了一个新方向——WiFi 感知(WiFi Sensing)。
传统的人体感知方案各有硬伤:
- 摄像头:隐私代价极高,且黑暗、遮挡、雾气环境失效;
- 可穿戴设备:需要人主动佩戴,老人、婴儿、失能人群往往无法配合;
- 红外 / 毫米波雷达:成本高,部署复杂。
这条技术之所以在 2020 年代后才真正「能落地」,关键在于三件事同时成熟:一是 商用 WiFi 芯片开始开放 CSI 接口(Intel 5300 网卡、Atheros,以及如今的 ESP32-S3),让普通人买得起采集端;二是 边缘算力(树莓派、NPU、甚至 ESP32 自身)足够跑轻量模型;三是 自监督与对比学习 让模型不再依赖昂贵的人工标注。RuView 正是站在这三股浪潮交汇点上的集大成者。
而 WiFi 感知的思路是——不增加任何新硬件,复用你已经拥有的路由器与网卡。你房间的每一个角落都已经被 2.4GHz/5GHz 的无线电波填满,人体作为导体和介电质,会反射、绕射、吸收这些波。只要能读到信号在「经过人体前后」的变化,就能反推人的位置、动作甚至呼吸心跳。
开源项目 RuView(WiFi DensePose)把这套学术方案工程化、产品化了。它的官方定义是:「Turn ordinary WiFi into a spatial intelligence / sensing system.」 一块 ESP32-S3(约 9 美元)读取房间里的人体无线电反射,一个 4-bit 量化、仅 8KB 的预训练模型就能告诉你:谁在房间、呼吸如何、心率趋势怎样、是否跌倒——全程无摄像头、无穿戴、无云端、无订阅。
二、核心概念:CSI、相位与菲涅尔区
在动手之前,先把「信号到底携带了什么信息」这件事在直觉上理顺,能让你少走很多弯路。WiFi 感知之所以可行,本质上是因为电磁波对空间里的导体极其敏感:人体含有大量水分,是良导体,一旦出现,原本直达的波就会多出反射与散射路径,接收端看到的复数信道响应随之改变。我们要做的,就是把这个「改变」从噪声里捞出来,并映射到人的动作与生理状态。
2.1 RSSI 太粗糙,我们需要 CSI
普通 WiFi 网卡只对外暴露 RSSI(接收信号强度),它是一个被严重平滑过的标量,只能粗略判断「近了 / 远了」。要做人级别感知,必须拿到 CSI(Channel State Information,信道状态信息)。
CSI 描述信号在每条「子载波(subcarrier)」上的传播特性。802.11n/ac 把 20MHz 信道划分成数十个子载波,对每个子载波,CSI 给出了一个复数响应:
H(k) = |H(k)| · e^{j·φ(k)}
|H(k)|是幅度(子载波 k 的衰减);φ(k)是相位(信号经历的相移)。
以 ESP32-S3 为例,开启 CSI 后每帧可以拿到约 52~64 个子载波的复数样本。这么多维度的细粒度信息,正是深度学习模型能够「看见」人体的原材料。
2.2 为什么相位比幅度更关键
幅度对硬件噪声、天线增益、家具摆放非常敏感,鲁棒性差。而相位承载了「信号走过的几何路径长度」信息:
φ(k) = -2π · (d / λ) (d 为收发路径总长度,λ 为波长)
人移动 1 毫米,都会引起相位可观测的微小变化。这也是 RuView 做生命体征监测的物理学基础——呼吸引起的胸腔起伏(毫米级)会改变反射路径长度,进而调制相位。
2.3 菲涅尔区(Fresnel Zone):穿墙的物理原理
RuView 的穿墙能力来自「菲涅尔区 + 多径建模」。当发射机与接收机之间存在直射路径和绕射路径时,人体进入不同菲涅尔区会改变收发两端的相位叠加结果。通过建模多径与菲涅尔几何,系统能在隔着一堵内墙(约 5 米内,视信号而定)的情况下仍检测到人体。这也是为什么 README 中把穿墙能力标注为「signal-dependent」——混凝土承重墙和石膏板隔墙的效果天差地别。
2.4 相位净化的必要性
但原始相位不能直接喂给模型,它有两大「脏」:
- 卷绕(wrapping):相位被收敛到
[-π, π],真实相移超过 2π 就会跳变; - 线性倾斜(SFO / 包检测偏移):发射端与接收端的采样时钟频率存在微小偏差(Sampling Frequency Offset),加上 ESP32 在检测到一个 WiFi 包时存在 timing 抖动,导致相位沿子载波序号呈线性倾斜。这条倾斜和人体无关,必须剔除。
RuView 的 PhaseSanitizer 正是解决这个问题的核心组件——「净化」后的相位,才是一台干净的人体传感器信号。
2.5 CSI 张量的真实形状与调试技巧
第一次拿到 CSI 数据时,程序员最该做的是「先肉眼看一眼」。下面这段小代码把一帧 CSI 打印成幅度-相位,帮助你确认采集是否正常:
import numpy as np
csi = load_csi_from_esp32() # 假设返回 [frames, subcarriers] 复数
frame0 = csi[0]
print('shape :', csi.shape)
print('amplitude :', np.abs(frame0)[:8].round(3))
print('phase raw :', np.angle(frame0)[:8].round(3))
# 正常情况:幅度在不同子载波有起伏;相位应呈近似线性(待净化)
调试时常见的「坑」有两个:子载波 0 附近因直流泄漏幅度异常大;以及个别子载波被信道屏蔽、值恒为 0。这些都应在净化前先做掩码剔除,否则会污染后面的线性拟合与带通滤波。
三、架构分析:从电波到骨架的六层流水线
RuView 的整体数据流可以概括为一句话:ESP32 抓 CSI → 网桥转发 → 相位净化 → 特征提取 → 深度学习模型 → 姿态 / 语义输出。
理解这条流水线时,记住一个原则:越靠近硬件的信号越脏,越靠近应用的信号越语义化。CSI 是脏的复数矩阵,经过净化变成干净的相位,再经编码器变成 128 维嵌入,最后由姿态头解码成 17 个关键点。每一层都在做「降维 + 去噪 + 升语义」,这也是为什么轻量模型能在边缘跑得动——脏活累活在前处理阶段就被滤掉了。
┌─────────┐ CSI(UDP/WS) ┌──────────┐ 净化+特征 ┌──────────────┐
│ ESP32-S3 │ ───────────────▶ │ Bridge │ ───────────▶ │ DL Model │
│ (Mesh) │ │(Node/Py) │ │ (Encoder+Pose)│
└─────────┘ └──────────┘ └──────┬───────┘
│ 邻居路由器当免费雷达 │ 17 关键点 + 语义
│ (6 信道多频扫描) ┌───────▼────────┐
└────────────────────────────────────────────────▶ │ Edge Modules │
│ (105 个 Cogs) │
└───────┬────────┘
│ MQTT/Matter
┌───────▼────────┐
│ HA / Apple / │
│ Google / Alexa │
└────────────────┘
3.1 边缘采集层:ESP32 上的 CSI 抓取
ESP32-S3 通过 ESP-IDF 的 esp_wifi_set_csi() 进入「CSI 监听模式」,把每个收到的 WiFi 包的子载波复数样本通过回调推出来,再经 UDP / WebSocket 流送给网桥。RuView 还会做多频 Mesh 扫描:在 6 个 WiFi 信道之间跳频(TDM 时隙调度),用邻居家的路由器当「免费雷达照射源」,把可感知带宽提升到约 3 倍。
3.2 网桥层(Bridge)
网桥(Node.js 或 Python)负责接收 ESP32 的 CSI 流、做缓冲与对齐,然后调用相位净化与特征提取。它既可以用 Docker 跑(内置仿真数据,无需硬件即可体验),也可以直连真实 ESP32。
3.3 净化与特征层:PhaseSanitizer
这一步就是第二节提到的相位净化:解卷绕 + 去除子载波方向的线性倾斜 + 生命体征带通滤波。净化后的相位张量(形状约为 [帧数, 子载波数])是后续所有模型的输入。
3.4 模型层:对比编码器 + 姿态头
RuView 的模型设计很有层次:
- 对比编码器(Contrastive Encoder):128 维自监督对比学习得到的 CSI 嵌入,在 M4 Pro 上可达 164,183 emb/s;4-bit 量化版仅 8KB,小到能塞进树莓派微秒级推理,甚至直接跑在 ESP32 上;
- 姿态头(Pose Head):基于 Graph Transformer + Cross-Attention 的架构,加载
pose_v1.safetensors(Candle 推理),输出 17 个人体关键点(DensePose 风格)。在 MM-Fi 数据集random_split协议上,RuView 的wifi-densepose-mmfi-pose达到 82.69% 的 torso-PCK@20(集成版 83.59%),超过了 MultiFormer(72.25%)与 CSI2Pose(68.41%); - v2 编码器:诚实重测后的留一时序三元组(temporal-triplet)准确率为 82.3%(早先「100% 存在检测」的单类录制数字已被官方撤回)。
3.5 边缘模块层(Edge Modules / Cogs)
模型之上是一套 105 个 Cog 模块目录,覆盖健康、安防、楼宇、零售、工业、科研等场景。它们体积小、可在 ESP32 上本地运行,例如:跌倒检测(相位加速度阈值 + 三帧去抖 + 5 秒冷却,延迟 < 200ms)、多人计数、呼吸 / 心率提取、房间活动识别。
3.6 集成层:智能家居与 Matter
RuView 通过 HA-DISCO MQTT 发布器一键接入 Home Assistant(--mqtt 一个参数即可),每个节点暴露 21 个实体(11 路原始信号 + 10 路语义状态,如「有人睡眠 / 可能遇险 / 房间活跃 / 老人久未活动异常 / 跌倒风险升高」等),并附带 3 个 HA Blueprint。它还能作为 Matter Bridge 配对进 Apple Home / Google Home / Alexa / SmartThings,让 Siri、Google Assistant、Alexa 直接用语音播报「某房间是否有人、生命体征如何」。
3.7 部署拓扑:从单节点到 Mesh
最小可用系统是「一块 ESP32-S3 + 一台网桥(树莓派)」。但 RuView 的真实威力在多节点 Mesh:3–6 块 ESP32 组成的网格能以不同视角照射同一空间,用三角化提升定位精度、用多径多样性抑制遮挡。完整方案再加入 Cognitum Seed(约 140 美元 BOM 总和)——它提供持久化向量库、kNN 检索与 Ed25519 证人链,让系统「记住」环境并支持防篡改审计。对预算敏感的场景,纯 ESP32 Mesh(约 54 美元)已能覆盖存在、运动、呼吸、心率、跌倒与多人计数等核心能力。
四、代码示例:亲手把 WiFi 变成传感器
下面三段代码覆盖了「净化 → 采集 → 接入智能家居」三个关键环节。
4.1 PhaseSanitizer:相位净化核心实现
下面这段 PhaseSanitizer 类提炼自 RuView 的 phase_sanitizer.py 思路,依赖 numpy 与 scipy,可直接用于真实 CSI 数据:
import numpy as np
from scipy.signal import butter, filtfilt
class PhaseSanitizer:
"""RuView 相位净化器:把原始 CSI 相位还原为可被深度学习模型使用的干净信号。
核心两步:
1. 跨子载波 + 跨时间联合解卷绕(unwrap);
2. 拟合并减去「相位沿子载波序号的线性倾斜」,该倾斜来自采样频率偏移(SFO)
与包检测(Packet Detection)的 timing 抖动,与人体无关,必须剔除。
"""
def __init__(self, n_subcarriers: int = 52, fs: float = 1000.0):
self.n_subcarriers = n_subcarriers
self.fs = float(fs) # CSI 帧率(Hz),ESP32 常设 1000 可调
# ------------------------------------------------------------------
def raw_phase(self, csi_complex: np.ndarray) -> np.ndarray:
"""从复数 CSI 提取原始相位角。
csi_complex: shape [n_frames, n_subcarriers],每元素为 H(k)=a+bj
返回: 形状相同的相位角,值域 [-π, π]
"""
return np.angle(csi_complex)
# ------------------------------------------------------------------
def unwrap_phase(self, phase: np.ndarray) -> np.ndarray:
"""跨子载波 + 跨时间联合解卷绕,消除 ±π 跳变。"""
phase = np.unwrap(phase, axis=-1) # 先沿子载波方向
phase = np.unwrap(phase, axis=0) # 再沿时间方向
return phase
# ------------------------------------------------------------------
def sanitize_packet_detection(self, phase_unwrapped: np.ndarray) -> np.ndarray:
"""核心净化:消除 SFO / 包检测引入的线性相位倾斜。
对每一帧 k 拟合 φ = a·i + b(i 为子载波序号),减去线性项。
phase_unwrapped: shape [n_frames, n_subcarriers]
返回: 净化后相位,同形状
"""
i = np.arange(self.n_subcarriers)
A = np.vstack([i, np.ones_like(i)]).T # 设计矩阵 [i, 1]
# lstsq 一次解出所有帧的 [a, b],coeffs shape = [2, n_frames]
coeffs, *_ = np.linalg.lstsq(A, phase_unwrapped.T, rcond=None)
a, b = coeffs[0], coeffs[1] # 各 shape [n_frames]
linear = a[:, None] * i[None, :] + b[:, None] # 重建线性倾斜
return phase_unwrapped - linear
# ------------------------------------------------------------------
def bandpass(self, sig_1d: np.ndarray, low: float, high: float) -> np.ndarray:
"""4 阶巴特沃斯带通滤波。"""
nyq = 0.5 * self.fs
b, a = butter(4, [low / nyq, high / nyq], btype="band")
return filtfilt(b, a, sig_1d)
# ------------------------------------------------------------------
def breathing_rate(self, sanitized: np.ndarray, fs: float = 20.0) -> float:
"""呼吸率:取中频子载波时间序列,0.1–0.5 Hz 带通 + 过零检测,返回 BPM。"""
sig = sanitized[:, self.n_subcarriers // 2]
sig = self.bandpass(sig, 0.1, 0.5)
zero_cross = int(np.sum(np.diff(np.sign(sig)) != 0))
duration_s = len(sig) / fs
# 一次完整呼吸 = 2 次过零 → BPM = zero_cross / duration * 30
return zero_cross / duration_s * 30 if duration_s > 0 else 0.0
# ------------------------------------------------------------------
def heart_rate(self, sanitized: np.ndarray, fs: float = 20.0) -> float:
"""心率:0.8–2.0 Hz 带通 + 过零检测,返回 BPM。"""
sig = sanitized[:, self.n_subcarriers // 2]
sig = self.bandpass(sig, 0.8, 2.0)
zero_cross = int(np.sum(np.diff(np.sign(sig)) != 0))
duration_s = len(sig) / fs
return zero_cross / duration_s * 30 if duration_s > 0 else 0.0
# 典型调用流程
# csi = load_csi_from_esp32() # [frames, subcarriers] 复数
# ps = PhaseSanitizer(n_subcarriers=csi.shape[1])
# phi = ps.raw_phase(csi)
# phi = ps.unwrap_phase(phi)
# phi_clean = ps.sanitize_packet_detection(phi)
# bpm = ps.breathing_rate(phi_clean)
README 给出的实测区间与此一致:呼吸 6–30 BPM、心率 40–120 BPM,均为实时;存在检测延迟 < 1ms,环境校准约 30 秒。
4.2 ESP32 端:开启 CSI 监听(ESP-IDF C)
下面是 RuView esp32-csi-node 固件思路的最小可运行骨架。关键点在于 esp_wifi_set_csi(true) 与回调注册:
#include "esp_wifi.h"
#include "esp_event.h"
// CSI 接收回调:每收到一个包就被调用一次
static void csi_rx_cb(void *ctx, wifi_csi_info_t *info) {
if (!info || !info->buf) return;
const wifi_csi_item_t *items = (const wifi_csi_item_t *)info->buf;
uint16_t len = info->len; // 子载波个数
// 这里把 items(每个含 int8 实部/虚部的定点数)通过 UDP / WebSocket 推给网桥
send_csi_over_udp(items, len * sizeof(wifi_csi_item_t));
}
void app_main(void) {
esp_netif_init();
esp_event_loop_create_default();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
// 开启 CSI 并注册回调
esp_wifi_set_csi(true);
esp_wifi_set_csi_rx_cb(csi_rx_cb, NULL);
// CSI 配置:启用 L-LTF / HT-LTF,开启子载波合并与移位
wifi_csi_config_t csi_conf = {
.lltf_en = true,
.htltf_en = true,
.stbc_htltf2_en = true,
.ltf_merge_en = true,
.channel_filter_en = true,
.manu_scale = true,
.shift = true,
};
esp_wifi_set_csi_config(&csi_conf);
esp_wifi_start();
}
烧录命令(来自官方文档)大致是:
python -m esptool --chip esp32s3 --port COM9 --baud 460800 write_flash 0x0 bootloader.bin 0x8000 partition-table.bin 0xf000 ota_data_initial.bin 0x20000 esp32-csi-node.bin
python firmware/esp32-csi-node/provision.py --port COM9 --ssid "YourWiFi" --password "secret" --target-ip 192.168.1.20
ESP32-C6 款(约 6–10 美元,支持 WiFi 6)复用同一套固件,编译目标切到 esp32c6 即可,还能拿到 HE-LTF 子载波标记与 802.15.4 mesh 时间同步等研究特性。
4.3 智能家居接入:Home Assistant MQTT 发布器
RuView 用 paho-mqtt 把推断结果以 Home Assistant MQTT Discovery 格式发布。下面是一段精简的 RuViewMqttClient:
import json
import paho.mqtt.client as mqtt
class RuViewMqttClient:
"""把 RuView 推断结果以 HA MQTT Discovery 格式发布,零配置接入 Home Assistant。"""
def __init__(self, broker: str = "192.168.1.10", port: int = 1883,
node: str = "living_room"):
self.node = node
self.client = mqtt.Client()
self.client.connect(broker, port, keepalive=60)
self.client.loop_start()
self.discovery_base = f"homeassistant/sensor/ruview_{node}"
def publish_presence(self, present: bool, confidence: float) -> None:
# 1) 先发一次 retain 的「自动发现」配置,HA 会自动出现该实体
cfg = {
"name": f"RuView {self.node} 存在",
"state_topic": f"ruview/{self.node}/presence",
"device_class": "presence",
"unique_id": f"ruview_{self.node}_presence",
"value_template": "{{ value_json.present }}",
"json_attributes_topic": f"ruview/{self.node}/presence",
}
self.client.publish(f"{self.discovery_base}_presence/config",
json.dumps(cfg), retain=True)
# 2) 再发实时状态
self.client.publish(f"ruview/{self.node}/presence",
json.dumps({"present": present,
"confidence": round(confidence, 3)}))
def publish_vitals(self, breathing_bpm: float, heart_bpm: float) -> None:
self.client.publish(f"ruview/{self.node}/vitals",
json.dumps({"breathing": round(breathing_bpm, 1),
"heart": round(heart_bpm, 1)}))
# 用法:与 RuView 网桥的推断循环拼接
# mq = RuViewMqttClient(node="bedroom")
# mq.publish_presence(present=True, confidence=0.91)
# mq.publish_vitals(bpm_breath, bpm_heart)
有了这层,你就可以在 Home Assistant 里写自动化:「卧室检测到有人且呼吸异常 → 推送通知给子女」;或在 Apple Home 里问 Siri:「卧室有人吗?」
五、性能优化:把模型塞进 8KB
RuView 的一大工程亮点是「性能与成本的极致平衡」,对程序员很有启发:
| 指标 | 数值 | 说明 |
|---|---|---|
| 存在检测延迟 | < 1 ms | 相位方差回退方案甚至无需模型 |
| 环境校准 | ~30 s | 脉冲神经网络(SNN)本地自适应 |
| 17 关键点姿态冷启动 | 8.4 ms(Pi 5) | Candle 加载 pose_v1.safetensors |
| CSI 嵌入吞吐 | 164,183 emb/s(M4 Pro) | 128 维对比编码器 |
| 跌倒检测 | < 200 ms | 相位加速度阈值 + 去抖 + 冷却 |
| 穿墙距离 | ≤ 5 m(视信号) | 菲涅尔区 + 多径建模 |
| 单节点硬件成本 | $9(ESP32-S3) | 完整系统含 Seed 约 $140 |
| 编码器体积 | 8 KB(4-bit 量化) | 可在 ESP32 本地跑 |
优化手段主要有四招:
- 量化到 4-bit:对比编码器量化后仅 8KB,推理从「秒级」降到「微秒级」,直接下沉到边缘;
- 相位方差回退(phase-variance fallback):简单存在检测不依赖任何神经网络,纯相位方差统计即可,延迟 < 1ms,适合电池节点的极速唤醒;
- 多频 Mesh + 邻居照射:6 信道跳频 + TDM 调度把感知带宽拉到约 3 倍,相当于「白嫖」邻居路由器的信号当雷达源;
- 脉冲神经网络(SNN)本地自适应:环境变化(挪了家具)时,SNN 能在 30 秒内本地重学,无需重新训练大模型。
模型本身也已上架 Hugging Face(ruvnet/wifi-densepose-pretrained、ruvnet/wifi-densepose-mmfi-pose),pip install ruview(或 wifi-densepose)即可拿到编译好的 PyO3 轮子(约 250KB,支持 Linux/macOS/Windows)。
六、隐私与合规:无摄像头不等于无责任
这一部分可能是全文最重要、却最容易被工程师忽略的一节。技术越「无感」,越容易在当事人不知情的情况下持续采集——正因如此,做感知系统的人必须主动把隐私当成「功能」而不是「合规负担」来设计,而不是等项目上线被投诉了才补。
「不装摄像头」常被当作隐私卖点,但 WiFi 感知仍然是个人信息处理——它能推断谁在房间、是否独处、睡眠与作息规律,这些同样敏感,受 GDPR / 国内个人信息保护法约束。RuView 在架构层做了几件对的事:
- 全本地、无云端:信号不出家门,从源头降低泄露面;
- 加密证人链(Ed25519 Witness Chain):每次测量都做 Ed25519 签名,形成防篡改的证据链,便于审计「数据没被偷偷改过 / 没被外传」;
- 输出是骨架而非像素:模型产出的是 17 个关键点与语义标签,而非可辨识的人脸或身形照片,信息粒度本身更低。
但作为开发者仍需注意:
- 默认不录制:不要把 CSI 原始流默认落盘,遵循数据最小化;
- 显式同意:在家庭 / 办公场景部署前取得被感知者同意,尤其是涉及老人、儿童、雇工时;
- 可关闭、可审计:提供物理开关与查账接口(证人链正是为此);
- 区分「存在」与「身份」:尽量只输出「有人 / 无人」语义,避免长期留存可关联到具体个人的射频指纹。
换句话说,WiFi 感知的隐私优势是「架构红利」,不是「免死金牌」——把粗粒度、本地化、可审计三件事坐实,才算真正合规。
七、应用场景与未来方向
WiFi 感知不是要取代摄像头,而是去填摄像头「做不好或不该做」的空白:它不拍脸、不依赖光照、不要求人配合,因此特别适合长期、常态化、对隐私敏感的空间智能。下面分场景看它到底解决了什么真实痛点。
典型场景
- 老人照护:跌倒检测(< 200ms)、久未活动异常、夜间离床告警,免去摄像头对尊严的侵犯;
- 婴儿监护:非接触式呼吸 / 心率监测,不必在婴儿床上架摄像头;
- 智能家居:存在即开灯、无人即休眠,比被动红外更准,且能区分「人走但宠物在」;
- 安防:穿墙入侵检测,作为摄像头的互补传感器。
未来方向
RuView 路线图里最值得关注的两块:一是 Matter Bridge,让 WiFi 感知直接成为主流智能家居生态的一等公民;二是 World Model(OccWorld / TransVQVAE),用 15 帧 CSI 预测未来 occupancy(约 209ms 推理、RTX 5080 上 3.4GB 显存),把「感知现在」推向「预测未来」。此外,Camera depth(MiDaS)+ WiFi CSI + mmWave 的 3D 点云融合(约 22ms 流水线、每帧 19K+ 点)也在推进,目标是统一空间模型。这条路一旦走通,WiFi 感知将从「二维平面上的存在与姿态」升级为「三维空间里的连续理解」,有望与机器人导航、AR 空间锚定等场景打通,让「空间智能」从概念走向日常。
八、局限、挑战与那些「坑」
作为工程师,必须清醒看待 WiFi 感知的边界:
- 多径歧义:房间里金属、镜子、玻璃会造成强反射,姿态估计可能把「镜中虚影」当成人。RuView 用多频扫描与多节点融合缓解,但无法根除;
- 需要一定的信号覆盖:完全被金属屏蔽的卫生间、承重墙后的房间效果会骤降。穿墙约 5 米是「视信号」上限;
- 环境漂移:家具挪动、季节湿度变化都会改变射频指纹,依赖 SNN 的 30 秒本地自适应来跟进;
- 多人重叠:两个人贴得很近时信号会混叠,多人计数需要自适应 P95 归一化与可调去重因子(
/api/v1/config/dedup-factor); - 监管差异:在部分国家与地区,主动发射并分析无线电做人员监测可能涉及频谱与隐私法规,部署前需确认合规。
九、动手路线图:从仿真到真机
如果你被勾起了兴趣,建议按这个顺序上手,避免一上来就被硬件劝退:
- 跑通仿真:
docker pull ruvnet/wifi-densepose && docker run -p 3000:3000 ruvnet/wifi-densepose,打开localhost:3000看整套流水线输出; - 装 Python 包:
pip install "ruview[client]",用SensingClient读仿真流,RuViewMqttClient推到本地 Home Assistant; - 点亮硬件:买一块 ESP32-S3(约 9 美元),按官方
provision.py烧录 CSI 固件,把--target-ip指向你的网桥; - 写第一个净化脚本:把本文的
PhaseSanitizer接到真实 CSI 流,先验证呼吸率数字是否合理; - 接智能家居:加一个
--mqtt,在 HA 里做「卧室有人且久未活动 → 通知」的自动化。
整套链路开源、可复现,是理解「信号即传感器」这件事最便宜的入口。
十、总结
RuView 给我们的最大启示是:传感器不一定需要被「造」出来,它可以一直都在。你家的路由器就是一台被低估的雷达。通过 CSI 抓取、相位净化(PhaseSanitizer)、轻量深度学习(8KB 量化编码器 + Graph Transformer 姿态头)与边缘智能(105 个 Cog 模块),九美元的 ESP32 就能实现无摄像头的人体感知。
对程序员而言,这条技术栈的可玩性极高:从 ESP-IDF 的 CSI 回调,到 numpy 里几行相位净化,再到 paho-mqtt 把结果喂进 Home Assistant,整条链路开源、可复现、可本地部署。它既是对「隐私优先感知」的一次工程示范,也是把学术 WiFi 感知真正落到寻常百姓家的务实尝试。
如果你也想试试,最省事的方式是 docker pull ruvnet/wifi-densepose 用仿真数据跑通整条流水线,再决定要不要花九美元买块 ESP32-S3 真刀真枪地「看穿墙壁」。
最后要强调一点:RuView 的价值不只是技术酷,更在于它把「信号即传感器」这件事开源化、平民化、可审计化。你可以在 GitHub 上读到每一个 Cog 的源码、在 Hugging Face 上核验每一个模型的权重、用证人链追溯每一次测量——这种透明度,恰恰是闭源摄像头方案给不了的。对一个关心隐私时代的程序员来说,这或许比「穿墙」本身更值得玩味。
参考资料:RuView 官方仓库(github.com/ruvnet/RuView)、README 与 ADR 文档、MM-Fi 数据集评测、MIT RF-Pose / WiFi 感知相关论文。文中性能数字均引自 RuView 官方文档与 Hugging Face 模型卡,部分早期「100% 存在检测」表述已被官方撤回,本文采用其诚实重测后的数值。