1. 从标题拆解这个项目的真实意图RuView8 美元的 WiFi看穿墙壁——这个标题我第一次看到的时候第一反应是又是一个标题党。但仔细琢磨了一下它其实精准地描述了一个非常硬核的技术方向利用廉价商用WiFi设备的信道状态信息CSIChannel State Information来实现穿墙人体姿态估计与生命体征监测。先把这个标题拆开来看。RuView是项目代号从命名逻辑推测大概率是RF射频加View视图/看见的组合词暗示用射频信号去看世界。8美元的WiFi指的是硬件成本——一块ESP32系列模组或者类似的低成本WiFi开发板批量采购价确实能压到个位数美元。看穿墙壁则是核心卖点也是整个项目最有冲击力的部分WiFi信号能穿透非金属墙体通过分析信号在传播路径上因人体运动产生的微小扰动反推出墙后的人体姿态。这个项目解决的核心问题是在不需要摄像头、不需要穿戴设备的前提下实现对人的非接触式感知。传统方案要么依赖视觉摄像头有隐私问题、有遮挡问题要么依赖可穿戴传感器需要佩戴、体验差。而WiFi感知恰好卡在一个很微妙的位置——它既有穿透能力又不像雷达那样需要昂贵的专用硬件还能复用已经无处不在的WiFi基础设施。适合谁来参考这个项目我认为有三类人值得深入看一是做智能家居和养老监护的开发者这个技术可以直接用来做跌倒检测、呼吸监测二是做人机交互的研究人员WiFi手势识别是一个很有潜力的方向三是对无线感知感兴趣的嵌入式工程师这个项目涉及信号处理、机器学习部署、嵌入式开发等多个层面的知识。下面我会从整体设计思路、核心技术细节、实操落地过程、常见问题排查四个维度把这个项目彻底拆开讲清楚。2. 整体设计思路与方案选型2.1 为什么选WiFi而不是毫米波雷达或UWB这是我在动手之前反复问自己的第一个问题。市面上做穿墙感知的方案不止WiFi一种毫米波雷达60GHz/77GHz和UWB超宽带都能做而且精度往往更高。但为什么这个项目偏偏选了WiFi核心原因有三个。第一是成本。一块毫米波雷达模组动辄几十到上百美元UWB模组也要十几美元起步而ESP32系列WiFi模组批量价确实能压到8美元以内这个成本差距是数量级的。第二是普适性。WiFi已经无处不在你家里、办公室里到处都是WiFi信号这意味着这个技术可以寄生在现有基础设施上不需要额外部署专用设备。第三是穿透性。2.4GHz WiFi信号对石膏板墙、木门、砖墙的穿透能力相当不错而毫米波在穿透性上反而吃亏——频率越高穿透损耗越大。当然WiFi感知也有明显的短板。它的空间分辨率远不如毫米波雷达信号带宽只有20/40/80MHz而毫米波雷达可以做到GHz级别的带宽。这就导致WiFi感知在精细姿态估计上天然吃亏只能做到粗粒度的骨架估计比如判断人是站着、坐着、躺着、在走动或者识别挥手、跌倒这类大动作。想用它做手指级别的精细识别目前还不太现实。提示如果你的应用场景对精度要求极高比如手语识别WiFi方案可能不是最优解。但如果你的需求是有没有人在不在动有没有摔倒呼吸频率多少WiFi方案的性价比是无敌的。2.2 CSI为什么比RSSI更适合做感知很多人做WiFi感知的第一反应是用RSSI接收信号强度指示因为几乎所有WiFi芯片都能直接读到这个值。但RSSI的问题在于它太粗了——它只是把所有子载波的信号强度做了一个聚合平均丢失了绝大部分信息。CSI则完全不同。在OFDM正交频分复用系统中WiFi信号被分配到多个子载波上传输CSI记录的是每个子载波上的幅度和相位信息。以20MHz带宽为例通常有52到56个数据子载波每个子载波都能提供一组复数幅度相位。这意味着CSI提供的是一个高维的细粒度信道响应矩阵而不是一个标量。打个比方RSSI就像是你站在房间外面只能听到里面有声音还是没声音而CSI相当于你有一组麦克风阵列能听到声音的频率分布、相位差、回声模式从而推断出房间里的布局和人的位置。这个信息量的差距是本质性的。RuView项目的核心就是围绕CSI数据的采集、清洗、特征提取和姿态推断来展开的。整个系统的技术栈大致是这样的层级技术选型作用硬件层ESP32-S3 / Intel 5300采集CSI原始数据信号处理层相位解缠、滤波、去噪把原始CSI变成可用信号特征层多普勒频移、时延剖面提取与人体运动相关的特征模型层CNN / LSTM / Transformer从特征推断姿态应用层跌倒检测、呼吸监测落地到具体场景2.3 为什么用ESP32而不是Intel 5300早期WiFi感知研究几乎都用Intel 5300网卡因为它能通过修改驱动导出CSI数据而且有成熟的CSI Tool工具链。但Intel 5300的问题是它是PCIe接口的网卡需要一台PC来承载部署灵活性很差而且这块卡早就停产了二手市场价格被炒得很高。ESP32系列则完全不同。它是一颗SoC自带WiFi射频支持CSI数据导出乐鑫官方在ESP-IDF中提供了CSI API体积小、功耗低、价格便宜可以直接部署在任意位置。RuView选择ESP32作为硬件平台本质上是在可部署性和数据质量之间做了一个权衡——ESP32的CSI数据质量确实不如Intel 5300子载波数量少、采样率受限但它让这个技术真正具备了大规模部署的可能性。注意ESP32的CSI数据需要通过串口或WiFi回传到上位机处理ESP32本身算力有限跑不动复杂的神经网络。所以RuView的架构一定是ESP32采集 上位机推理的分体式设计这一点在动手之前就要想清楚。3. 核心技术细节与实操要点3.1 CSI数据到底长什么样先给你一个直观的认识。ESP32通过esp_wifi_set_csi_rx_cb注册回调函数后每次收到WiFi包都会触发回调回调里能拿到一个wifi_csi_info_t结构体里面包含buf指向CSI数据的缓冲区每个子载波用两个字节表示高字节虚部低字节实部具体格式取决于芯片型号len数据长度rx_ctrl接收控制信息包括RSSI、信道、时间戳等以ESP32-S3为例在20MHz带宽、HT20模式下通常能拿到64个子载波的CSI数据实际有效子载波约52个。每个子载波是一个复数所以一次CSI采样大约是64×2128字节的原始数据。这里有个坑我必须提前说ESP32的CSI数据格式在不同芯片型号之间是不一样的。ESP32、ESP32-S3、ESP32-C3的CSI数据排列方式、子载波顺序、有无导频子载波都有差异。你在网上抄的代码换一块芯片可能就跑不通了。我建议第一步先写一个最简单的采集程序把原始数据打印出来对照乐鑫官方文档确认数据格式再往下做。3.2 相位处理是整个流程中最难的部分CSI数据里幅度信息相对好处理但相位信息才是真正包含人体运动信息的关键。问题在于原始相位存在两个致命问题第一是相位解缠Phase Unwrapping。CSI相位是通过atan2(虚部, 实部)算出来的结果被限制在[-π, π]之间。当真实相位超过这个范围时会发生跳变。你需要把这些跳变接回去恢复出连续的相位曲线。常用的算法是Itai-Itai相位解缠核心思路是检测相邻采样点之间的相位差如果差值超过π就加减2π进行修正。第二是载波频偏CFO和采样频偏SFO。发射端和接收端的晶振不可能完全一致这会导致相位随时间线性漂移。如果不消除这个漂移你看到的相位变化可能全是频偏造成的跟人体运动毫无关系。常用的消除方法是线性拟合去漂移对一段时间窗口内的相位做线性回归然后减去拟合出的线性分量。import numpy as np def unwrap_phase(phase): Itai-Itai相位解缠 unwrapped np.zeros_like(phase) unwrapped[0] phase[0] for i in range(1, len(phase)): diff phase[i] - phase[i-1] if diff np.pi: diff - 2 * np.pi elif diff -np.pi: diff 2 * np.pi unwrapped[i] unwrapped[i-1] diff return unwrapped def remove_linear_trend(phase): 线性拟合去漂移 n len(phase) x np.arange(n) coeffs np.polyfit(x, phase, 1) trend np.polyval(coeffs, x) return phase - trend这两步做完之后相位数据才真正可用。我实测下来如果跳过相位解缠直接做后续处理模型基本学不到任何有用的东西准确率跟随机猜差不多。3.3 子载波选择与降维64个子载波不是每一个都有用。有些子载波信噪比很低有些受多径干扰严重还有些是导频子载波不携带数据。直接拿全部子载波喂给模型不仅计算量大还会引入噪声。RuView这类项目通常的做法是先计算每个子载波在一段时间窗口内的方差选方差最大的前N个子载波。为什么选方差大的因为方差大意味着这个子载波对信道变化敏感而人体运动正是引起信道变化的主要原因。方差小的子载波要么是静态的要么是被噪声淹没的对姿态估计没有贡献。一般来说选前20到30个子载波就能覆盖大部分有效信息。这个降维过程能把数据量减少一半以上对后续模型推理速度提升很明显。3.4 从CSI到姿态的模型设计这是整个项目最核心也最有挑战的部分。CSI数据本质上是时序信号而姿态估计是一个回归/分类问题。RuView的模型设计大概率采用了CNNLSTM的混合架构CNN部分把CSI数据看成一张图像横轴是时间纵轴是子载波每个像素是幅度或相位值。用卷积核在时间和子载波两个维度上提取局部特征。LSTM部分CNN提取的特征序列送入LSTM捕捉时间上的长距离依赖关系比如人从站立到跌倒这个动作的时序模式。输出层根据任务不同输出可以是分类站立/坐着/躺着/跌倒或回归关节点坐标。训练数据的采集是个大工程。你需要在不同位置、不同朝向、不同人数的情况下同步采集CSI数据和对应的真实姿态用摄像头或动捕设备标注。这个数据集的规模和多样性直接决定了模型的泛化能力。我见过很多demo在实验室里跑得很好一换环境就崩根本原因就是训练数据太单一。实操心得如果你刚开始做不要一上来就追求精细姿态估计。先从有人/没人的二分类做起再做到站立/坐着/躺着的三分类最后再挑战跌倒检测和骨架估计。每一步都验证通过再往下走否则你会被中间某个环节的bug卡死。4. 完整实操流程与关键环节实现4.1 硬件准备与接线RuView的硬件清单其实很简洁组件型号建议数量备注主控模组ESP32-S3-DevKitC2块一块发一块收USB数据线Type-C2根供电串口通信上位机任意Linux/Windows PC1台跑数据处理和模型路由器普通2.4GHz路由器1台提供WiFi信号源接线方面没什么复杂的ESP32通过USB直接连电脑就行。但有一个关键点收发两端要配置成相同的信道和带宽。如果发送端在信道6接收端在信道1那收到的CSI数据基本是噪声。我建议固定使用信道62.437GHz因为大多数家用路由器默认就在这个信道干扰相对可控。4.2 ESP32端CSI采集程序ESP32端的核心任务是连接WiFi、注册CSI回调、把CSI数据通过串口或UDP发出去。下面是关键代码框架#include esp_wifi.h #include esp_log.h static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info) { if (!info || !info-buf) return; // 打印CSI数据长度和RSSI printf(CSI_LEN:%d,RSSI:%d,, info-len, info-rx_ctrl.rssi); // 逐字节输出CSI原始数据 for (int i 0; i info-len; i) { printf(%02x, info-buf[i]); } printf(\n); } void app_main(void) { // WiFi初始化略 esp_wifi_set_csi_rx_cb(wifi_csi_rx_cb, NULL); esp_wifi_set_csi_config((wifi_csi_config_t){ .lltf_en true, .htltf_en true, .stbc_htltf2_en true, .ltf_merge_en true, .channel_filter_en false, .manu_scale false, }); esp_wifi_set_csi(true); }这段代码里有个细节值得说channel_filter_en我建议设为false。开启信道滤波虽然能减少噪声但也会滤掉一些有用的多径信息。在初期调试阶段先把原始数据拿全后面再在上位机做滤波灵活性更高。4.3 上位机数据处理流水线上位机收到串口数据后需要经过一条完整的处理流水线才能变成模型可用的输入。我把它拆成六步第一步解析原始数据。把十六进制字符串还原成复数数组。注意ESP32的CSI数据格式是每两个字节一组具体哪个是实部哪个是虚部要对照芯片手册确认。第二步相位解缠与去漂移。就是前面3.2节讲的那两步这是整个流水线里最关键的预处理。第三步异常值剔除。CSI数据里经常会出现突发的异常值比如WiFi重传、信道切换导致的跳变。我通常用中值滤波或者3σ原则把这些点剔掉。第四步滑动窗口切分。姿态估计需要一段时间的信号不能只看单次采样。通常窗口长度取0.5到1秒步长取窗口的一半。以100Hz采样率为例窗口就是50到100个采样点。第五步归一化。把幅度和相位分别归一化到均值为0、方差为1消除不同设备、不同距离带来的尺度差异。第六步特征提取或直接送入模型。如果模型是端到端的这一步可以省略如果用的是传统机器学习方法还需要手动提取多普勒频移、时延剖面等特征。def process_csi_pipeline(raw_csi, window_size100, step50): 完整的CSI处理流水线 # 1. 解析为复数 csi_complex parse_csi(raw_csi) # 2. 提取幅度和相位 amplitude np.abs(csi_complex) phase np.angle(csi_complex) # 3. 相位处理 phase np.array([unwrap_phase(p) for p in phase.T]).T phase np.array([remove_linear_trend(p) for p in phase.T]).T # 4. 异常值剔除 amplitude median_filter(amplitude, size3) phase median_filter(phase, size3) # 5. 滑动窗口切分 windows [] for start in range(0, len(amplitude) - window_size, step): amp_win amplitude[start:startwindow_size] phase_win phase[start:startwindow_size] windows.append(np.stack([amp_win, phase_win], axis-1)) # 6. 归一化 windows [(w - w.mean()) / (w.std() 1e-8) for w in windows] return np.array(windows)4.4 模型训练与部署模型训练这块我建议先用公开数据集验证流程。WiFi感知领域有几个公开数据集可以用比如用Intel 5300采集的那些。虽然硬件平台不同但数据格式和处理思路是相通的可以帮你快速验证预处理流水线是否正确。训练的时候有几个参数需要特别注意学习率建议从1e-3开始用余弦退火调度。WiFi CSI数据噪声大学习率太高容易震荡。Batch Size32到64比较合适。太小了梯度不稳太大了显存吃不消。数据增强可以对CSI数据做时间轴上的随机裁剪、幅度上的随机缩放模拟不同距离和不同动作速度的情况。正则化Dropout和权重衰减都要加WiFi感知模型很容易过拟合到特定环境。部署的时候如果上位机是PC直接用PyTorch或TensorFlow推理就行。如果想部署到边缘设备比如树莓派需要把模型转成ONNX或TFLite格式做量化压缩。我实测下来一个轻量级的CNNLSTM模型量化后大概2到3MB在树莓派4上能做到实时推理。注意模型在不同房间之间的泛化是个大问题。在客厅训练的模型搬到卧室可能准确率直接掉一半。解决办法要么是在多个环境采集数据训练要么是用域适应Domain Adaptation技术。这个问题目前学术界也没有完美解法实际部署时要有心理预期。5. 常见问题与排查技巧实录5.1 CSI数据全是噪声怎么办这是新手最常遇到的问题。串口打印出来的CSI数据看起来毫无规律幅度和相位都在乱跳。排查思路按以下顺序来先确认收发两端在同一信道。这是最容易被忽略的问题。用esp_wifi_set_channel显式设置信道两端必须一致。再确认CSI回调真的被触发了。在回调函数里加一个计数器看每秒能触发多少次。如果次数是0说明CSI功能没开启成功检查esp_wifi_set_csi(true)是否被调用。然后检查数据解析格式。不同芯片的CSI数据字节序可能不同实部虚部的位置也可能相反。试着把实部虚部对调一下看看相位是否变得连续。最后检查环境干扰。2.4GHz频段非常拥挤微波炉、蓝牙设备、邻居的WiFi都会造成干扰。尽量在相对干净的信道做实验或者用5GHz频段但5GHz穿透性差穿墙场景还是得用2.4GHz。5.2 模型准确率上不去怎么调准确率卡在某个值上不去通常不是模型结构的问题而是数据或预处理的问题。我整理了一个排查表现象可能原因排查方法训练准确率高测试低过拟合增加数据增强加Dropout训练测试都低预处理有问题可视化CSI数据检查相位是否连续某些类别准确率特别低类别不平衡重采样或调整损失函数权重换环境后准确率暴跌泛化能力差多环境数据训练域适应准确率随机波动数据对齐有问题检查CSI和标签的时间同步5.3 穿墙后信号衰减太严重这是物理规律决定的2.4GHz WiFi穿过一堵砖墙大概衰减10到15dB穿过混凝土墙衰减更多。如果穿墙后信号太弱可以考虑这几个办法增加发射功率ESP32的发射功率可以调到20dBm但要注意法规限制。使用定向天线把全向天线换成定向天线增益能提升6到8dBi。缩短距离在墙的两侧各放一个接收端而不是隔很远。降低带宽20MHz带宽比40MHz的穿透性更好因为功率密度更集中。5.4 多个人同时在场怎么处理单人场景下CSI变化主要来自一个人的运动相对好处理。多个人同时在场时多个人的运动信号会叠加在一起分离难度大幅上升。目前的解决方案主要有两个方向一是用多天线MIMO做空间分离不同天线接收到的信号来自不同路径可以通过波束成形区分不同位置的人二是用盲源分离算法如ICA把混合信号拆开。但说实话多人场景目前还是学术界的研究热点工程上能做到3人以内已经不错了。实操心得我踩过最大的坑是忽略了CSI数据的时间同步问题。ESP32的CSI回调时间戳精度有限如果上位机同时处理多路CSI数据时间对齐没做好模型学到的全是错位的模式。后来我在每个CSI包前面加了一个高精度的时间戳用esp_timer_get_time上位机按时间戳对齐问题才解决。6. 这个项目还能怎么扩展RuView这个框架搭起来之后能做的事情远不止姿态估计。我自己尝试过几个扩展方向效果都还不错。呼吸和心率监测。人在静止状态下胸腔的起伏会引起CSI相位的周期性变化。这个变化的频率就是呼吸频率通常在0.2到0.5Hz之间。心率稍微难一点因为心跳引起的胸腔位移更小但用带通滤波加FFT还是能提取出来。这个功能在养老监护场景下非常实用。手势识别。挥手、握拳、手指滑动这些动作会在CSI数据上留下不同的模式。相比姿态估计手势识别的动作幅度小但模式更固定反而更容易做到高准确率。我试过用10个手势做分类在单人场景下能做到90%以上的准确率。入侵检测。这个是最简单的应用——只要检测到CSI方差突然增大就说明有人进入了监测区域。不需要复杂的模型一个阈值判断就能搞定。适合做低功耗的长期监测。睡眠分期。通过分析夜间CSI信号的周期性变化可以粗略判断人处于深睡、浅睡还是清醒状态。这个方向目前还比较前沿但已经有论文在做了。最后分享一个我在实际部署中的小技巧ESP32的CSI采样率不要设太高。很多人一上来就想跑100Hz甚至更高但实际上人体运动的主要频率成分在10Hz以下50Hz的采样率完全够用。采样率越高数据量越大串口传输越容易丢包反而得不偿失。我一般用50Hz采样配合1秒的滑动窗口效果很稳。