用Wi-Fi CSI进行行为识别:从源码解析到BVP特征与EMD分类 简介Widar3.0Release-Matlab_csi_wifi_行为识别_是一套面向无线感知与行为识别研究的Matlab工具包基于Widar3.0项目实现Wi-Fi CSI信号处理与人体动作识别适合物联网、无线感知及机器学习方向的研究者与工程师使用。压缩包共638个文件以Matlab源码m文件、技术文档tex和实验数据dat/mat为主同时提供C/Python辅助脚本、EPS图像及PDF说明等总体积约19.07MB结构完整便于按模块调用。目前已有2320人学习下载。资源覆盖从CSI数据采集、去噪预处理、特征提取到模型训练与行为分类的完整流程附带的tutorial文档和样例数据可帮助读者快速复现行走、跑步等动作的识别实验也可作为进一步开发无线感知算法的参考基线。1. 用Wi-Fi CSI识别人体行为先解决数据从哪来Wi-Fi信号在室内不是直线传播而是经过墙壁、家具、人体多次反射。当人抬手、转身、走动时这些反射路径的微小变化会叠加在信道状态信息CSI上。Widar3.0正是利用这一点把普通Wi-Fi通信的CSI变成行为识别的输入而Matlab这套开源代码把整个链路摆在桌面上。很多人下载后卡在第一步read_bfee.c编译不过或者不知道emd.c在流程里做什么。这里从CSI数值格式讲起逐步推导到BVP特征和EMD距离分类让每个文件都能对应到可执行的Matlab代码。2. CSI数据格式与Widar3.0源码结构2.1 为什么CSI不是普通信号强度一个常见误区是把Wi-Fi感知等同于RSSI检测。RSSI是把所有路径的能量叠加成一个标量位于接收端的AGC增益后面人体动作稍微远一点就无法分辨。CSI则不同它在OFDM每个子载波上输出一个复数包含了该子载波的幅度和相位等价于把多径信道投影到几十个频率分辨率单元上。即使20MHz带宽的802.11n也有30个左右CSI数据点。人体移动会导致某些子载波的幅度下落几个dB同时相位发生旋转这些变化构成了行为识别的原始信号。在Widar3.0源码包里log.all_csi.6.7.6就是一组这样的原始CSI日志按照Intel 5300或类似网卡的驱动格式存储。每个数据包里面包含发送天线数、接收天线数、子载波数和复数CSI值。处理它不能直接用Matlab的load必须先通过read_bfee.c解析。这套流程和Widar3.0选择Matlab作为主环境也有关系Matlab的矩阵索引和复数运算非常直接CSI在语法层面就是多维复数数组调试时还能直接imagesc看BVP热图比C快速得多。2.2 源码包文件分工与编译read_bfee.c是CSI Tool的经典读取器逐包读取日志把二进制字节转换成结构体。mex.bat是Windows环境下的批量编译脚本内容基本是把read_bfee.c和emd.c编译成mex文件。emd.c、c_emd.c、pyemd.c三份代码围绕EMD距离展开emd.c是封装c_emd.c是核心C实现pyemd.c暴露给Python调用。test.c用最短的例子验证EMD计算与预期是否一致编译和调试时很有用。tutorial.bib引用的是Widar3.0配套论文实施时最好先读一遍里面定义了物理模型。编译方法其实很直接先选定C编译器再执行下面的mex命令mex -setup mex read_bfee.c mex c_emd.cmex -setup用于选择C编译器Matlab在Windows下通常需要安装MinGW-w64或Visual Studio。Linux下如果系统gcc版本过高mex会拒绝识别可以先安装gcc-7再用mex CCgcc-7指定编译器。编译成功后当前目录会生成.mexa64或.mexw64文件Matlab会在当前路径自动加载。2.3 读取第一个CSI包写一段可运行的Matlab脚本把日志读进来并检查维度csi_trace read_bfee(log.all_csi.6.7.6); fprintf(总共收到 %d 个数据包\n, length(csi_trace)); pkt csi_trace{1}; csi pkt.csi; % 复数矩阵维度为 Ntx x Nrx x Nsub rssi pkt.rssi; % 从同一个包取出的RSSI便于和CSI对比 timestamp pkt.timestamp; % 包到达时间 fprintf(CSI维度: %dx%dx%d, RSSI: %d, 时间戳: %d\n, ... size(csi,1), size(csi,2), size(csi,3), rssi);这段代码先调用read_bfee把整个日志解析成cell数组然后取第一个包。pkt.csi矩阵大小由发射天线数、接收天线数和子载波数决定。一般笔记本内置网卡是1发2收或3发3收子载波数常见30个。如果读取时返回空值先检查日志文件路径是否有中文或空格某些系统下会影响底层C代码打开文件。下表总结了read_bfee返回结构体中的常见字段实际使用时按字段名取数即可字段含义典型值csi复数CSI矩阵Ntx x Nrx x Nsubrssi接收信号强度单位dBmtimestamp接收时间戳微秒级整数rate物理层速率6.5Mbps等len负载长度字节2.4 解析时的常见坑日志文件有几个GB时read_bfee会把所有包读入内存很容易触发Out of Memory。常见做法是分批读取read_bfee支持传入起始包号和读取数量例如read_bfee(filename, 1000, 200)表示从第1000个包开始读200个包。分批读取后可以边读边提取幅度不必保留原始复数占用全部内存。另外log.all_csi.6.7.6这类文件名里的数字和采集参数有关不要擅自修改否则脚本里的路径和元数据可能对不上。如果mex编译报错缺少头文件说明Matlab没有找到C标准库重新执行mex -setup就能解决。3. 从CSI到BVP把信号变成物理可解释的特征3.1 先去掉静态背景CSI混叠了静态路径和动态路径。静态路径来自墙面、桌椅这些固定物体它们在一个场景里基本不变而人体运动的信息只存在于动态路径中。Widar3.0处理时会先估计静态CSI分量再在原始CSI上减去保留动态分量。最简单的静态参考是取一段没有目标动作的时间窗均值比如采集刚开始房间无人或人静止时的数据。window_size 100; % 用前100个包估计静态分量 static_ref mean(csi_all(1:window_size, :, :, :), 1); csi_dyn csi_all - static_ref;这段代码在第一个维度上求均值得到1 x Ntx x Nrx x Nsub的静态参考。Matlab R2016b之后会自动做广播所以csi_dyn的形状和csi_all保持一致。window_size不宜过大否则目标动作早已移动静态估计被污染也不宜过小否则噪声导致参考不稳。一般取0.5秒内的包数目例如包速率1000时取500静止场景下取100也能用。3.2 多普勒频谱动作速度的直接体现人体各部位相对Wi-Fi收发机的运动速度不同反射信号会产生不同频率的多普勒频移。频率偏移fd等于速度沿反射路径分量除以波长速度越快多普勒频率越高。对动态CSI沿时间轴做短时傅里叶变换STFT就得到“频率-时间”的多普勒谱。频率分辨率由窗长决定时间分辨率由滑动的重叠程度决定。Widar3.0的BVP正是从多个子载波的多普勒谱中估计出的速度分布所以STFT参数直接决定后续识别精度。Nfft 256; window hamming(Nfft); overlap_rate 0.9; % 对第1根发射天线、第1根接收天线、第1个子载波做STFT [s, f, t] spectrogram(squeeze(csi_dyn(:,1,1,1)), ... window, round(overlap_rate*Nfft), Nfft, fs);squeeze去掉长度为1的维度转成一维时间序列fs是CSI包速率Widar3.0采集时通常用Linux的CSI Tool打时间戳包速率在500到1000Hz之间具体可以按时间戳差值的倒数计算。使用spectrogram时Nfft是FFT点数overlap_rate0.9表示相邻窗口重叠90%时间分辨率较高但计算量也大。如果fs为1000HzNfft256对应的多普勒频率分辨率约为3.9Hz能区分每秒移动不到1厘米的缓慢动作足够识别大部分人体手势。3.3 拼接BVP矩阵单个子载波的多普勒谱噪声很大Widar3.0把所有可用子载波的多普勒谱做非相干累加得到BVP。BVP的横轴是时间纵轴是速度颜色深浅代表当时人体在该速度上的能量。bvp zeros(length(f), length(t)); for tx 1:ntx for rx 1:nrx for sc 1:nsub s spectrogram(squeeze(csi_dyn(:, tx, rx, sc)), ... window, round(overlap_rate*Nfft), Nfft, fs); bvp bvp abs(s); end end end bvp bvp / (ntx * nrx * nsub);这里没有对相位做相干叠加因为不同子载波的初始相位不一致直接相加可能互相抵消。实际使用中可以对每一组收发天线的BVP先做归一化再加总避免某根天线信号弱导致整体特征被压制。叠加后的bvp大小是(Nfft/21) x length(t)在送入分类器之前通常裁剪为只保留速度范围[-Vmax, Vmax]的部分。Vmax由Wi-Fi中心频率和可观察速度决定比如5GHz频段下2kHz最大多普勒频率对应约0.13m/s实际人体躯干速度会超过这个值所以要结合场景调整。3.4 参数选择与计算开销参数建议范围影响STFT窗长128~512点窗长越大频谱越平滑但动作时间上会被拉长重叠率0.75~0.95提高时间分辨率但增加计算量BVP速度裁剪±2m/s保留主要肢体速度抑制高频噪声子载波使用数全部信息最多但运算量最大这组参数可以先用默认值跑通再观察测试集准确率。需要强调的是BVP相比原始CSI的优势在于具备一定的跨设备迁移能力。原始CSI的幅度会受到发射功率、衰减器和网卡型号影响而BVP是一种速度分布和目标的物理运动更相关因此换环境时不容易完全失效这也是Widar3.0选择BVP而不是直接用CSI做分类的原因。4. 用EMD距离度量BVP差异完成行为识别4.1 为什么EMD适合匹配BVPBVP矩阵如果要送入分类器最直接的方式是拉平成向量算欧氏距离。但同样动作在不同位置做BVP的速度峰值位置会发生偏移直方图在某个bin上错位。欧氏距离对错位极其敏感即便两个分布形状相同只是偏移一个bin距离也会变得很大。EMDEarth Movers Distance把两个分布看成两堆土计算把一个分布搬运成另一个分布的最小工作量。它能容忍峰值在相邻bin之间移动因此在Widar3.0的最近邻分类中表现得比欧氏距离稳定。4.2 编译并验证EMD代码源码包里的emd.c是EMD算法的C实现test.c给出了最小示例。先编译mex c_emd.c如果导出的函数名是emdMatlab里直接传两个列向量即可dist emd(hist1, hist2);如果编译后提示找不到emd需要查看test.c中实际调用的函数名可能导出的是emd_hat或emd_c。用test.c提供的思想做一个已知结果的验证x [1 0 0]; y [0 1 0]; d emd(x, y); % 两个完全错开的分布距离期望为1这个验证的意义在于确认mex接口和Matlab的数据类型对齐。EMD通常要求输入为double类型的向量如果传入单精度或cell数组会报错在调用前用double()强制转换。4.3 基于BVP模板的最近邻分类Widar3.0的行为识别不依赖复杂分类器而是对每种行为建立一个或多个模板然后用EMD找最近的模板。% 假设 train_bvp{class} 是 N x M 矩阵每行是一个BVP向量 num_classes length(train_bvp); templates cell(num_classes, 1); for c 1:num_classes templates{c} mean(train_bvp{c}, 1); % 类别平均BVP向量 end % 测试阶段 pred zeros(length(test_bvp), 1); for i 1:length(test_bvp) dmin inf; for c 1:num_classes d emd(double(test_bvp{i}(:)), double(templates{c}(:))); if d dmin dmin d; pred(i) c; end end endtemplates{c}是该类训练样本的逐点平均。emd的输入必须长度相等所以在提取BVP时就要固定窗口和裁剪范围否则两个向量维数不同EMD会直接报错。平均模板能平滑单次动作的随机抖动但也会抹掉一些个体差异如果每个类别内动作模式差异大可以在每个类里多保留几个聚类中心作为模板再取最小的EMD。4.4 参数调优和分类器替换参数作用常见值模板数每类保留的BVP中心1~5分类器最近邻、KNN、SVM最近邻最简单EMD代价函数定义移动成本欧氏距离或曼哈顿距离从经验看先跑一个最近邻基线记录混淆矩阵如果某个动作经常混淆把该动作的多个模板分开不要再平均。如果计算量允许可以用fitcknn封装KNN把EMD矩阵作为距离项。这种做法的好处是分类器变成一个开放接口后续换深度网络时不需要重新调整特征提取部分。需要注意EMD计算复杂度高于欧氏距离当测试样本多时会成为瓶颈。常见做法是先对BVP做降维比如只保留速度轴的投影或者对时间维降采样使向量长度从几千降到几百EMD计算速度显著提升识别准确率通常不会明显下降。5. 最后一章验证方法与实战技巧5.1 行为识别实验怎么评价Widar3.0这类项目最怕把同一段数据既当训练又当测试准确率虚高。正确做法是留一法每次留出一个动作样本其余全部作为模板。这样得到的准确率接近真实场景。具体流程是先把连续日志切成分段样本保证每个人的同一动作只有一段进入训练集再用其余段做模板。否则相邻包高度相关模型会记住这种相关性导致评分虚高。5.2 避免环境因素带来虚假高精度CSI对室内家具摆放非常敏感。同一个动作在房间左边做和右边做BVP的形状可能差异明显。如果训练集只在左边采集、测试集也在左边准确率会很高换到右边立刻下降。可以做数据增强把原BVP沿速度轴随机平移模拟不同位置的变化。平移几个bin后EMD本身有容忍能力训练出的模板会更鲁棒。5.3 从日志回放验证全流程拿到代码包后先不要急着用自己的采集数据。用log.all_csi.6.7.6回放整个流程确定mex编译、BVP提取和EMD分类三部分都没有bug。回放时打印每个步骤的变量尺寸形成检查清单读取后csi大小是Ntx x Nrx x Nsub动态CSI去掉静态分量后能量不是零BVP矩阵行数是Nfft/21。任何一步尺寸异常都能快速定位到对应的源码文件。确认无误后再以自己的数据替换日志文件按同样的代码跑一遍。5.4 用混淆矩阵定位失败样本常见做法是输出混淆矩阵观察哪些动作互相混。如果“走路”和“跑步”混淆考虑把BVP速度轴范围调大或者增加STFT频率分辨率。如果“坐下”和“站立”混淆因为两者速度都很低需要观察BVP中的低频能量变化而不是只看峰值。这种调试方法比单纯追求平均准确率更有指导意义。本文还有配套的精品资源点击获取