
1. 从标题说起不用摄像头和传感器WiFi 凭什么能“看见”你第一次看到“通过 WiFi 实现监控你在家的一举一动不用摄像头、不用传感器、更不需要什么特殊硬件”这个说法我的第一反应是这标题党也太敢写了。但仔细琢磨之后我发现它背后其实站着一个非常扎实的技术方向——WiFi 感知WiFi Sensing也有人叫它无线感知或信道状态信息感知。它的核心逻辑并不玄乎WiFi 信号在室内传播时会因为你走动、坐下、挥手甚至呼吸产生反射、折射、散射的变化。这些变化被网卡记录下来经过算法处理就能反推出你在干什么。换句话说WiFi 信号本身就是一种“看不见的雷达”。它不需要额外的摄像头不需要穿戴设备也不需要红外传感器。你家里那台路由器、你手机里的 WiFi 芯片、你笔记本的无线网卡就已经是现成的“感知硬件”了。这件事之所以最近又被翻出来讨论是因为**信道状态信息CSI, Channel State Information**的获取门槛在降低一些开源工具和消费级网卡已经能比较方便地拿到细粒度的物理层数据让个人开发者在家里做原型验证成为可能。这篇文章适合谁看如果你是做物联网、智能家居、边缘计算方向的开发者或者你对“用无线信号做人体感知”这件事有好奇想自己动手跑一个最小可用原型那这篇内容就是写给你的。我会从整体设计思路讲起拆到 CSI 数据怎么拿、怎么清洗、怎么提取特征、怎么做一个能区分“有人走动”和“没人”的分类器最后再聊几个我实际踩过的坑。全程不涉及任何特殊硬件用的都是市面上能买到的普通网卡和一台跑 Linux 的机器。注意本文讨论的是基于无线信号物理层信息的感知技术所有实验均在合法合规的本地环境中进行不涉及任何网络入侵或隐私侵犯行为。实际部署时请务必遵守当地法律法规尊重他人隐私。2. 整体设计思路为什么选 CSI 而不是 RSSI2.1 核心需求拆解从“有没有人”到“人在做什么”这个项目的目标可以拆成三个层次。第一层是存在检测房间里有没有人。第二层是动作识别人在走动、坐下、挥手还是摔倒。第三层是身份与状态推断是谁、呼吸频率多少、睡姿如何。标题里说的“一举一动”听起来很夸张但落到工程上绝大多数可复现的方案都集中在第一层和第二层。第三层不是不能做而是对数据采集的稳定性和标注量要求高得多个人开发者很难在短时间内做出可靠结果。所以我的整体设计思路是先做存在检测再做动作分类最后尝试呼吸监测。这三个任务共用同一套 CSI 采集链路区别只在于特征提取和模型选择。这样做的好处是你不需要一开始就追求“全知全能”而是可以逐层验证每一步都有可观测的输出。2.2 为什么不用 RSSI粒度太粗信息量不够很多人第一次想到的是用 RSSIReceived Signal Strength Indicator接收信号强度来做感知。RSSI 确实容易拿几乎所有网卡都支持但它的问题在于粒度太粗。RSSI 是整条链路上所有子载波信号强度的聚合值一个人走动带来的 RSSI 波动可能和空调风吹动窗帘带来的波动差不多。你很难从一维的强度序列里区分出“人”和“环境噪声”。CSI 则完全不同。在 OFDM正交频分复用系统里WiFi 信号被分成几十甚至上百个子载波。CSI 记录的是每个子载波上的幅度和相位。当人体在空间中移动时不同子载波受到的多径影响不同有的增强、有的减弱形成一组高维的、随时间和频率变化的矩阵。这就像从“一个温度计”升级到“一张热力图”信息量差了几个数量级。对比维度RSSICSI数据维度单值每包一个强度复数矩阵子载波 × 天线对时间分辨率低通常秒级高可达毫秒级对微小动作敏感度差好能捕捉呼吸级起伏获取难度极低中等需要特定网卡和工具适用任务粗略存在检测动作识别、呼吸监测、定位2.3 方案选型消费级网卡 开源工具链硬件方面我选择的是支持 CSI 提取的消费级 WiFi 网卡。市面上有几款常见的芯片方案被开源社区广泛支持比如某些基于 Intel 5300、Atheros 9k 系列的网卡。这些网卡价格不贵二手市场很容易买到。一台普通的路由器作为信号发射端一台装有 Linux 的笔记本作为接收端就构成了最基本的采集环境。软件方面Linux 下有几个成熟的开源工具可以拿到 CSI 数据。它们的工作原理是修改网卡驱动在收到每个数据包时把物理层信息导出到用户空间。你不需要自己写驱动只需要编译安装对应的工具然后用脚本读取输出即可。采集到的数据通常是文本格式每行包含时间戳、子载波索引、幅度、相位等信息。提示不同网卡型号和驱动版本对 CSI 的支持程度差异很大。建议先确认你手头的网卡是否在开源工具的支持列表里再决定是否继续。如果网卡不支持换一块支持的网卡比折腾驱动要省事得多。3. 核心细节解析CSI 数据里到底藏着什么3.1 幅度与相位两个维度的信息各有用途CSI 数据最核心的两个量是幅度和相位。幅度反映的是信号经过多径传播后的衰减和增强情况。当你走动时身体会遮挡某些路径、反射某些路径导致不同子载波的幅度出现有规律的起伏。幅度信息比较稳定受硬件相位噪声影响小所以大多数入门方案都先从幅度入手。相位则更敏感但也更“脏”。相位里包含了载波频偏CFO、**采样频偏SFO**以及硬件本身的相位噪声。如果不做校准原始相位几乎没法直接用。但相位的好处是对微小位移极其敏感呼吸时胸腔的起伏在相位上会有清晰体现。所以做呼吸监测时相位是绕不开的。我的建议是存在检测和动作分类先用幅度呼吸监测再处理相位。这样你可以先把链路跑通再逐步增加难度。3.2 子载波选择不是越多越好一条 WiFi 链路通常有 30 个或 56 个子载波取决于带宽和协议。理论上子载波越多信息越丰富。但实际中边缘子载波的信噪比往往很差而且有些子载波是保护频带不携带有效信息。如果你把所有子载波都塞进模型不仅计算量大还会引入大量噪声。我通常的做法是先画出所有子载波的幅度随时间变化的曲线肉眼观察哪些子载波对动作响应明显、哪些基本是平的。然后选择响应明显且彼此相关性较低的一组子载波。一般选 10 到 20 个就足够了。这一步没有固定公式更多是靠观察和经验。3.3 时间窗口多长才合适CSI 采集是连续的但模型需要固定长度的输入。时间窗口的选择直接影响识别效果。窗口太短动作还没完成特征不完整窗口太长会混入无关动作而且延迟增加。对于存在检测窗口可以长一些比如 2 到 5 秒因为“有人”这个状态是持续的。对于动作识别窗口通常取 0.5 到 2 秒具体取决于动作的持续时间。比如“挥手”可能 0.5 秒就结束了“从走到坐”可能需要 2 秒。你可以先录一批数据统计每个动作的实际时长再取一个覆盖大多数动作的窗口长度。注意窗口之间可以有重叠这叫滑动窗口。重叠比例通常取 50%既能增加样本量又能保证不会漏掉窗口边界处的动作。4. 实操过程从零搭建一个 WiFi 感知原型4.1 环境准备与网卡配置先说一下我的实验环境。发射端是一台普通家用路由器工作在 5GHz 频段带宽 20MHz 或 40MHz。接收端是一台装了 Ubuntu 的笔记本网卡是某款支持 CSI 提取的型号。两台设备之间没有遮挡距离大约 3 到 5 米。第一步是确认网卡驱动和固件版本。不同版本的驱动对 CSI 输出的格式有影响建议按照开源工具的文档把驱动和固件固定在一个已知可用的版本上。然后关闭网卡的各种节能和自动调整功能因为这些功能会改变物理层参数导致 CSI 数据出现莫名其妙的跳变。# 关闭网卡的节能模式示例具体接口名需替换 sudo iw dev wlan0 set power_save off # 固定信道和带宽示例 sudo iw dev wlan0 set channel 36 HT20第二步是配置发射端。路由器不需要特殊设置但建议关闭波束成形和 MU-MIMO因为这些技术会动态调整天线权重让 CSI 变得不稳定。固定信道、固定带宽、固定发射功率能让采集到的数据更可复现。4.2 CSI 数据采集与格式解析工具跑起来之后你会看到类似这样的输出timestamp, subcarrier_index, amplitude, phase 0.001, 0, 12.3, -0.45 0.001, 1, 11.8, 0.32 ...每一行对应一个子载波在某个时刻的测量值。实际数据里还会有天线索引、RSSI 等字段。你需要写一个解析脚本把原始文本转换成三维数组时间 × 子载波 × 天线。然后按时间窗口切片得到一个个样本。采集时要注意标注。你需要在采集的同时记录“当前在做什么”。我的做法是一个人负责做动作另一个人在旁边用手机秒表记录每个动作的起止时间事后根据时间戳对齐。更省事的办法是写一个简单的标注脚本按快捷键打标签比如按 1 表示“走动”按 2 表示“坐下”按 3 表示“无人”。import numpy as np import pandas as pd # 读取原始 CSI 数据 raw pd.read_csv(csi_raw.csv) # 按时间戳和子载波索引重塑为矩阵 csi_matrix raw.pivot_table( indextimestamp, columnssubcarrier_index, valuesamplitude ).values # 滑动窗口切片 def sliding_window(data, window_size, step): samples [] for start in range(0, len(data) - window_size, step): samples.append(data[start:start window_size]) return np.array(samples) windows sliding_window(csi_matrix, window_size100, step50) print(windows.shape) # (样本数, 时间步, 子载波数)4.3 特征提取从原始波形到可分类的特征原始 CSI 窗口不能直接丢给分类器因为维度太高、噪声太大。你需要提取有物理意义的特征。我常用的特征分三类第一类是统计特征均值、方差、偏度、峰度、最大值、最小值。这些特征计算简单对动作引起的幅度变化很敏感。比如“无人”时方差很小“走动”时方差明显增大。第二类是频域特征对每个子载波的时间序列做 FFT取低频段的能量。人体动作的频率通常在 0.5 到 5 Hz 之间呼吸在 0.2 到 0.5 Hz 之间。通过带通滤波和频谱分析可以把动作和呼吸分开。第三类是相关性特征不同子载波之间的相关系数。当有人走动时多径结构改变子载波之间的相关性会发生变化。这个特征对区分“有人但静止”和“无人”特别有用。from scipy.stats import skew, kurtosis from scipy.fft import fft def extract_features(window): features [] # 统计特征 features.extend([ np.mean(window), np.std(window), skew(window.flatten()), kurtosis(window.flatten()) ]) # 频域特征 spectrum np.abs(fft(window, axis0)) low_freq_energy np.sum(spectrum[1:10], axis0) features.extend(low_freq_energy[:5]) # 相关性特征 corr np.corrcoef(window.T) features.append(np.mean(corr[np.triu_indices_from(corr, k1)])) return np.array(features)4.4 模型训练与评估从逻辑回归到轻量级神经网络特征提取完之后分类器的选择就比较自由了。我的建议是先从逻辑回归或随机森林开始因为这两个模型训练快、可解释性强能帮你快速判断特征是否有效。如果随机森林的准确率能到 80% 以上说明特征提取方向是对的再考虑上更复杂的模型。数据划分要特别注意不能随机划分因为相邻窗口之间有重叠随机划分会导致训练集和测试集信息泄漏。正确做法是按时间段划分前 70% 的时间段做训练后 30% 做测试。这样测试集里的动作和训练集里的动作在时间上完全不重叠评估结果更真实。模型训练速度可解释性适合阶段逻辑回归极快强特征验证随机森林快中等基线模型SVM中等弱小样本一维 CNN慢弱数据量大时如果要做实时推理模型不能太重。我试过在树莓派上跑随机森林单次推理在毫秒级完全跟得上 CSI 的包速率。一维 CNN 虽然准确率可能高几个点但在边缘设备上跑起来比较吃力需要做量化或剪枝。5. 常见问题与排查技巧实录5.1 数据波动大、无法复现怎么办这是最常见的问题。同样的动作今天采的数据和明天采的数据分布不一样。原因通常有三个环境变化家具挪动、门窗开关、设备状态变化网卡温度漂移、路由器自动调整、人员位置变化发射端和接收端的位置动了。解决办法是每次采集前重新校准基线。具体做法是先采集 30 秒“无人”状态的数据计算每个子载波的均值和方差作为归一化的参考。后续所有数据都减去这个均值、除以这个标准差。这样可以在一定程度上消除环境漂移的影响。5.2 相位数据完全不可用怎么办原始相位确实没法直接用但可以通过线性变换做校准。常用的方法是对每个数据包的所有子载波相位做线性拟合减去拟合出的斜率和截距。这个操作的物理意义是消除载波频偏和采样频偏带来的线性相位旋转。校准之后相位的变化就主要来自人体运动了。def calibrate_phase(phase): # phase: 形状为 (子载波数,) 的原始相位 indices np.arange(len(phase)) slope, intercept np.polyfit(indices, phase, 1) calibrated phase - (slope * indices intercept) return calibrated注意相位校准只能在单个数据包内做。跨数据包的相位关系需要更复杂的同步机制入门阶段可以先不处理。5.3 如何区分“人静止”和“无人”这是存在检测里最难的场景。人坐在那里不动CSI 幅度可能和无人时差不多。这时候相位信息和呼吸引起的微动就派上用场了。即使人静止呼吸也会导致胸腔起伏在相位上产生 0.2 到 0.5 Hz 的周期性变化。你可以对相位序列做带通滤波如果在这个频段有明显的能量峰值就说明有人。另一个技巧是利用多天线。如果接收端有多根天线不同天线接收到的信号经历了不同的多径。人静止时不同天线之间的相位差仍然会因为呼吸而微小变化。这个差分信号比单天线信号更干净。5.4 常见问题速查表现象可能原因排查方向数据全是零或缺失网卡不支持 CSI / 驱动未加载检查工具输出和驱动日志幅度跳变剧烈网卡节能模式未关 / 信道切换关闭节能固定信道动作识别准确率低窗口长度不合适 / 特征不足调整窗口增加频域特征相位杂乱无章未做相位校准应用线性校准实时推理延迟高模型太重 / 采集频率过高换轻量模型降采样不同天数据不可比环境漂移每次采集前重新校准基线5.5 几个我踩过的坑第一个坑是网卡过热。连续采集半小时后网卡温度升高CSI 的幅度基线会缓慢漂移。解决办法是加散热片或者每采集 10 分钟休息 2 分钟。第二个坑是路由器自动信道选择。有些路由器会在检测到干扰时自动换信道换信道瞬间 CSI 会完全断掉。一定要在路由器设置里锁定信道。第三个坑是标注不同步。手动按秒表标注的误差可能达到几百毫秒对于短动作来说这是致命的。后来我改用键盘快捷键标注采集脚本同时记录按键时间戳误差降到了几十毫秒以内。第四个坑是过度依赖幅度。一开始我只用幅度做呼吸监测效果很差。后来加入校准后的相位呼吸检测的准确率明显提升。所以不要怕麻烦相位该处理还是要处理。6. 进阶方向从动作识别到呼吸与身份推断6.1 呼吸监测把相位用起来呼吸监测的核心是从相位序列中提取 0.2 到 0.5 Hz 的周期性成分。具体步骤是先对相位做校准然后带通滤波再用峰值检测或 FFT 找主频。主频乘以 60 就是每分钟呼吸次数。我实测下来在安静环境下呼吸频率的误差可以控制在每分钟 1 到 2 次。难点在于身体大动作的干扰。如果人在呼吸的同时还在看手机、翻身相位里会混入低频运动成分。这时候需要先做动作检测只在“静止”片段上做呼吸分析。6.2 身份识别步态作为生物特征每个人走路的姿态、步幅、频率都有细微差异这些差异会在 CSI 中留下痕迹。理论上可以用步态做身份识别。但实际做起来数据量和标注成本是最大的瓶颈。你需要同一个人在不同时间、不同状态下走很多次还要采集多个人做对比。个人开发者很难凑齐足够的数据。我的建议是把这个方向当作长期目标先把手势识别和呼吸监测做扎实。6.3 多房间与穿墙场景WiFi 信号可以穿墙这意味着理论上一个发射端可以覆盖多个房间。但穿墙之后信号衰减严重CSI 的信噪比会大幅下降。而且多房间场景下不同房间的动作会混在一起很难分离。目前比较可行的方案是每个房间放一个接收端做空间分集而不是指望一个接收端搞定全屋。7. 隐私与合规技术之外的必修课这个项目最敏感的地方在于“监控”两个字。技术本身是中性的但用在什么地方、怎么用决定了它是便利还是侵犯。我在做这个原型的时候给自己定了三条规矩只在自己家里做、不采集任何可识别个人身份的信息、数据本地处理不上传。如果你要把这个技术用到实际产品里比如独居老人跌倒检测那必须在知情同意的前提下进行并且要提供明确的关闭方式。WiFi 感知的一个“优势”是隐蔽性强但隐蔽性用在监控上就是灾难。技术开发者应该主动把透明度做进设计里而不是等出了问题再补救。提示任何涉及人体感知的项目在部署前都应该做隐私影响评估。即使是在自己家里如果有多人居住也应该告知并征得同意。8. 写在最后一些个人体会这个项目我从头到尾跑了一遍最大的感受是WiFi 感知的门槛在“数据”上不在“算法”上。网上有很多论文讲各种复杂的神经网络但你真正动手之后会发现最难的是采到干净、可复现、标注准确的数据。算法反而不是瓶颈随机森林加好的特征效果往往不比深度学习差多少。另一个体会是不要追求一步到位。我一开始就想做“全屋动作识别”结果数据采了一堆标注乱七八糟模型怎么调都不对。后来退回到“有人/无人”二分类把链路跑通再逐步增加动作类别反而顺利得多。做原型就是这样先让系统跑起来再让它跑得准。最后分享一个小技巧采集数据时放一个手机在旁邊录像。事后如果发现某段数据有问题可以回看录像确认当时到底发生了什么。这个习惯帮我省了很多排查时间。视频不用保留太久确认完数据就可以删掉既方便又不涉及长期存储问题。这个方向后续还可以往多模态融合走比如把 WiFi CSI 和麦克风阵列的声学信号结合做更鲁棒的动作识别。也可以往低功耗边缘部署走把模型量化后跑在嵌入式设备上做成一个不用联网的本地感知模块。这些我都还在摸索有新的进展再聊。