本篇主要讲做完实验后怎么处理收集到的数据。处理数据的第一步是正确的读取和存储这些数据。本教程参考了发表在Ebaina上的
基于Wi-Fi CSI的摔倒检测(一):CSI数据的导入_专栏_易百纳技术社区 (ebaina.com)
在此基础上做出了一些修改。
目录
1 实验数据基本介绍和解包
关于实验的硬件和软件操作见
2022/07 CSI TOOL 安装及使用_Silver_777的博客-CSDN博客
在实验中,CSI TOOL接收端采集到的信号数据会自动存在linux-80211n-csitool-supplementary/injection/ 中,以dat文件形式存在。这种文件格式是不能被直接查看的,CSI TOOL的发布者同步写了一个Matlab函数“read_bf_file”帮助读取这些数据。相关的函数都在linux-80211n-csitool-supplementary\matlab中。在运行后续程序的时候,要把这个路径添加到matlab的运行路径中,不然会报错。
以一个公开数据集的数据为例。我会一行一行代码的解释。(此公开数据集饰演的采样频率约为30Hz,属于较粗糙的水平)
close all
clear
clc
%% 读取原始数据
str = 'csi_a1_1.dat';% 输入文件名字
csi_trace = read_bf_file(str);
row = size(csi_trace,1);
result_matrix = zeros(row,181); % 第1~90列存储振幅,第91~180列存储相位,第181列存储时间戳
如上述代码所示,read_bf_file(文件名称)就可以调用函数读取,读取出的数据全部被存放在csi_trace这个单元胞数组中。每一个时间戳的数据包的所有相关信息都被封装在一个struct结构中。如下图所示,timestamp_low是时间戳,相邻数据包的时间戳间隔是10^6/采样频率,例如你的采样频率是100Hz,那么相邻数据包的时间戳基本相差10000(为什么说基本呢,因为我做实验的时候发现这个数值并不是严格一致,而是在 10^6/采样频率 上下微微浮动),Nrx是接受天线数量,Ntx是发射天线数量,我们所要使用的信号状态信息就是csi。
这个1×3×30的复数矩阵中。“1”表示发射天线数量,“3”表示接收天线数量,“30”表示每一路天线对包含的子载波数量(这个csi tool使用的intel 5300网卡就决定了每条天线对子载波数量是30)。因此可以看到,3条天线链路形成了90个子载波,也就是每一个时间戳的数据,都是由90个子载波构成的。这个矩阵全由复数构成,这与无线信号的特性相关。在无线通信中,信号一般被表示为复数,其中实部表示幅度(或强度),虚部表示相位(或角度)。在这种表示方式下,复数的模代表信号的幅度,复数的辐角代表信号的相位。
然后声明了一个result_matrix数组,将把这个每个csi的振幅和相位数据都存放进去。result_matrix的维度是(X,181),其中X表示时间戳个数;第二维度有181列,是由于每个数据包都包含90个子载波,第1~90列分别表示1~90号子载波的振幅数据,第91~180分别表示1~90号子载的相位数据,第181是时间戳。这样存储便于后面读取,三个关键的信息,振幅、相位、时间戳都有了,能够随时调用。(read_bf_file函数读取需要时间,如果你的采样频率又大采样时间又长,整体数据文件很大的话,会花费较长的时间)。
2 数据读取
在上一部分中,完成了dat原始数据文件的初步解包,并声明了数组result_matrix。接下来需要对csi中的复数矩阵进行计算,这里用Matlab自带的abs和angle函数就可以把振幅和相位信息提取出来。这一步也很简单,就是遍历一遍信号段,挨个算,代码如下。
%% 计算,将该dat原始文件的振幅和相位都存储在result_matrix矩阵中
for i = 1:row
%读取第i条数据
csi_entry = csi_trace{i};%结构体形式
%读取并存储第i条数据的时间戳
result_matrix(i,181) = csi_trace{i}.timestamp_low;
%读取第i条数据的CSI值
csi = get_scaled_csi(csi_entry);%复数矩阵,1*3*30
csi1 = squeeze(csi);% 去掉全为1的数组以降维,复数矩阵,3*30
% 提取当前时刻的信号数据包
current_packet = csi1;%复数矩阵,3*30
% 计算振幅信息
amplitude = abs(current_packet);
% 计算相位信息
phase = angle(current_packet);
% 将振幅和相位信息存储到result_matrix中
result_matrix(i, 1:90) = reshape(amplitude, 1, []);
result_matrix(i, 91:180) = reshape(phase, 1, []);
end
3 数据可视化
跑完数据了总要看看数据正不正常,所以有时候需要可视化看下信号图像。这里我plot了所有子载波共90根,如果想看单根天线的图像,修改画图代码即可。再result_matrix的排列是一根一根天线来的,例如在前90列的振幅数据中,1~30是第一个天线对的子载波,31~60是第二个天线对...在91~180的相位数据中,91~120是第一个...
% 可视化
% 振幅图
figure(1);
plot(result_matrix(:, 1:90));
xlabel('时间');
ylabel('振幅');
title('振幅随时间的变化');
grid on;
% 相位图
figure(2);
plot(result_matrix(:, 91:180));
xlabel('时间');
ylabel('相位 (弧度)');
title('相位随时间的变化');
grid on;
振幅图比较直观,可以看到振幅随着时间的变化,在信号不受干扰的时候,振幅是平稳的,当活动发生时,信号开始波动。
相位图由于相位跳变而导致图像显得很混乱,具体相位跳变的概念可以搜寻有关解释。总之相位的原始图是很混乱的,需要进行相位解卷绕处理。
用matlab自带的unwrap函数做一个简单的接卷绕,代码如下
% 相位接卷绕后的图
phase_wrapped = result_matrix(:, 91:180); % 原始相位数据
phase_unwrapped = unwrap(phase_wrapped);
figure;
subplot(2, 1, 1);
plot(phase_wrapped);
title('Wrapped Phase');
subplot(2, 1, 2);
plot(phase_unwrapped);
title('Unwrapped Phase');
解卷绕前后图像如下
4 完整代码及公开数据集地址
close all
clear
clc
%% 读取原始数据
str = 'csi_a1_1.dat';% 输入文件名字
csi_trace = read_bf_file(str);
csi_trace(all(cellfun(@isempty,csi_trace),2),:) = [];%去掉空部分,以防设备传输出现丢数据问题
row = size(csi_trace,1);
result_matrix = zeros(row,181); % 第1~90列存储振幅,第91~180列存储相位,第181列存储时间戳
Fs = 1000; % 采样频率
%% 计算,将该dat原始文件的振幅和相位都存储在result_matrix矩阵中
% 这一节得到是result_matrix,维度为result_matrix
for i = 1:row
%读取第i条数据
csi_entry = csi_trace{i};%结构体形式
%读取并存储第i条数据的时间戳
result_matrix(i,181) = csi_trace{i}.timestamp_low;
%读取第i条数据的CSI值
csi = get_scaled_csi(csi_entry);%复数矩阵,1*3*30
csi1 = squeeze(csi);% 去掉全为1的数组,复数矩阵,3*30
% 提取当前时刻的信号数据包
current_packet = csi1;
% 计算振幅信息
amplitude = abs(current_packet);
% 计算相位信息
phase = angle(current_packet);
% 将振幅和相位信息存储到result_matrix中
result_matrix(i, 1:90) = reshape(amplitude, 1, []);
result_matrix(i, 91:180) = reshape(phase, 1, []);
end
% 可视化
% 振幅图
figure(1);
plot(result_matrix(:, 1:90));
xlabel('时间');
ylabel('振幅');
title('振幅随时间的变化');
grid on;
% 相位图
figure(2);
plot(result_matrix(:, 91:180));
xlabel('时间');
ylabel('相位 (弧度)');
title('相位随时间的变化');
grid on;
% 相位接卷绕后的图
phase_wrapped = result_matrix(:, 91:180); % 原始相位数据
phase_unwrapped = unwrap(phase_wrapped);
figure(3);
subplot(2, 1, 1);
plot(phase_wrapped);
title('Wrapped Phase');
subplot(2, 1, 2);
plot(phase_unwrapped);
title('Unwrapped Phase');
总结,这一部分的内容很简单,主要理解一下数据流,从dat文件→cell单元胞→struct结构体→csi复数矩阵和时间戳→计算振幅和相位→存储数据。
本博客使用的样例dat文件来自公开数据集wiar:
GitHub - linteresa/WiAR: WiFi-based activity recognition dataset
论文:
Wiar: A Public Dataset for Wifi-Based Activity Recognition
Wiar: A Public Dataset for Wifi-Based Activity Recognition | IEEE Journals & Magazine | IEEE Xplore