首页 / 资讯中心 / 文章详情

基于距离-时间像的雷达人体动作识别:从HRRP到深度学习分类

基于距离-时间像的雷达人体动作识别:从HRRP到深度学习分类 ★ FEATURED ARTICLE
简介一份发表于《桂林理工大学学报》的学术论文PDF主题是基于时间距离像与深度卷积神经网络DCNN的人体动作分类研究面向雷达目标识别、深度学习应用与信号处理等方向的研究人员、研究生。论文针对人体微多普勒特征易受姿态和躯干回波影响而难以稳定提取的难题采用超宽带雷达获取高分辨率距离像并构建覆盖整个动作的时间-距离像作为DCNN输入在挥手、打乒乓球、拍篮球等9种典型动作上平均分类精度达96.67%并采集了5000多组回波数据。资源包内仅含1个PDF文件大小3.49MB属于期刊原文内容完整适合深度学习研究者进行学术研读与方法借鉴。目前已有101人学习是一份具备真实实验验证的高质量专业文献对雷达监测、安全监控等领域具有参考价值。1. 时间距离像是什么一帧距离像和一整张“距离-时间图”不是一回事假设你在养老院墙角装了一台超宽带雷达不用摄像头老人不抵触不用手环没人忘充电。雷达把不同距离上的反射强度沿距离排开得到一帧高分辨距离像HRRP把连续几十帧堆起来就是一张“距离-时间图”也就是标题里的时间距离像。基于它做深度学习分类就是让模型从这张二维图里认出跌倒、走路、起坐等动作。适合三类人雷达信号处理想迁移深度学习的工程师、智慧养老或安防项目组、拿雷达加深度学习做毕设的学生。它的价值在于保留了动作的时序信息这是它区别于“单帧HRRP目标识别”的关键所在。2. 从一维距离像到距离-时间图数据从哪来预处理做到哪一步才算干净2.1 雷达回波怎么变成一帧距离像先讲清HRRP这一步先明确一个概念雷达发射宽带信号后接收机拿到的原始数据是一段混着直达波、静态杂波和人体回波的时域波形。要变成距离像需要对脉冲压缩后的回波做匹配滤波或逆傅里叶变换IFFT得到幅度沿距离维的分布。距离分辨率由带宽决定ΔR c / (2B)。B是发射信号带宽c是光速。举例带宽1 GHz时ΔR约0.15 m带宽4 GHz时约3.75 cm。人体动作识别一般希望距离分辨率在厘米级所以工程上多用超宽带雷达或毫米波雷达带宽至少1 GHz。在实际采集里一帧距离像通常记为长度为R的向量R等于雷达最大距离窗内的距离采样点数。比如距离窗0到6米、分辨率为0.04 m那么R大约是128到150。每发射一个脉冲得到一个这样的向量脉冲重复频率PRF决定帧率常见取20到50帧/秒。注意这里的“帧”不是视频帧而是一个脉冲周期内得到的距离向量。拿到原始回波后第一步是去掉直流分量和射频直耦干扰。直流分量会让距离零点的幅度持续偏高直接送入神经网络会让前几个距离单元占据主导。常见做法是时域上做去均值或对慢时间维做高通滤波。这一步看起来简单但漏掉它的人不少后面模型的表现会很奇怪。2.2 把多帧距离像拼成距离-时间图帧长、帧率、滑窗怎么设得到一帧一帧的距离像之后下一步是决定“一次分类”吃多少帧。这里有一个最开始就要定死的概念距离-时间图的横轴是距离单元纵轴是帧序号时间每个像素是那一帧在对应距离单元的幅度。我们要给模型输入的是这个二维矩阵而不是一维向量。滑窗参数直接影响分类效果。我一般按动作的自然周期来选窗长跌倒从开始失重到落地大概0.6到1.2秒走路一个完整步态周期约1秒站立或坐下这类动作可能持续2到3秒。所以动作类型建议窗长建议帧率建议滑窗步长跌倒1.0秒约30帧30fps30 FPS0.5秒走路/跑步1.5~2.0秒30 FPS0.5秒站立/坐下/蹲起2.0~3.0秒20 FPS1.0秒多动作连续识别1.0秒30 FPS0.25秒这里的原则是窗长至少要覆盖动作的关键变化段太长会把前后无关动作混进同一张图步长决定相邻样本的重叠程度步长越大样本越少但冗余度也低。把原始距离像序列切成N段后每段形成一个尺寸为“窗长×距离单元数”的矩阵。代码示意如下import numpy as np def sliding_window(rt_sequence, window_len32, step16): rt_sequence: 连续距离像形状 [total_frames, range_bins] 返回滑动窗切分后的距离-时间图列表每张形状 [window_len, range_bins] n_frames rt_sequence.shape[0] frames [] for start in range(0, n_frames - window_len 1, step): frames.append(rt_sequence[start:start window_len]) return np.stack(frames, axis0)这段代码的思路是用一个固定窗口沿慢时间滑动每次取window_len帧步进step帧。返回值是四维数组[样本数, 窗长, 距离单元数]。后续送入模型前还需要在前面加一个通道维度变成[样本数, 1, 窗长, 距离单元数]。window_len和step是这里最重要的两个参数它们决定了模型看到的“动作片段时间范围”和训练样本量。如果step等于window_len则相邻样本完全不重叠样本量最小适合动作边界清晰的离线数据如果step取window_len的一半甚至四分之一样本量翻倍但相邻样本高度相似划分数据集时要小心泄漏。2.3 距离对齐目标在距离窗里漂移不校正就完蛋这是整个预处理里最容易翻车的一步。雷达安装位置固定但人不会站在同一个距离上活动。同一个“走路”动作在距离窗左半边做和右半边做距离像在图中的位置完全不同。如果直接把原始距离像堆成图送给CNN模型很可能学到“动作发生在图像哪个区域”而不是“动作本身长什么样”跨位置一测就崩。对齐的常见做法是重心对齐。先计算一帧距离像的重心位置def align_centroid(rt_frame, target_center64): rt_frame: 一帧距离像形状 [range_bins] 返回平移到固定重心的距离像 amp rt_frame.astype(np.float64) energy np.sum(np.abs(amp)) if energy 1e-6: return rt_frame centroid np.sum(np.arange(len(amp)) * np.abs(amp)) / energy shift int(round(target_center - centroid)) return np.roll(rt_frame, shift)原理是把这一帧距离像看成沿距离轴的“质量分布”用幅度加权位置算出重心再把重心搬到固定位置target_center。np.roll是循环移位目标超出边界时会回到另一侧所以target_center要选在距离窗中部给两侧留出至少20个距离单元的余量。如果你发现对齐后图像左边或右边出现明显断层说明移位量过大要么目标太靠近距离窗边缘要么雷达探测范围留得不够。对齐必须在动目标显示之后做顺序错了会引入新的伪影。原因很简单动目标显示后目标幅度变小、杂波被削弱重心计算更能反映真实目标位置如果先对齐再滤杂波静态杂波会把重心拉向固定位置对齐结果等于没对齐。2.4 归一化与数据增强幅度抖动、时间伸缩、位置平移距离-时间图每个像素的物理量是回波幅度不同雷达、不同增益下绝对数值差别很大所以归一化是必须的。我建议按样本归一化对每张距离-时间图做min-max归一化把幅度范围压到0到1。不要拿所有样本的全局最大值归一化因为个别强反射会让大部分样本的幅度被压缩到很低模型很容易学“死”。数据增强方面三个操作最有效幅度抖动、时间伸缩、距离平移。幅度抖动是对整张图乘一个0.8到1.2之间的随机系数模拟不同雷达增益时间伸缩用线性插值把窗长拉长或缩短到原来的0.9到1.1倍模拟动作快慢变化距离平移是对整张图沿距离轴随机平移1到2个距离单元模拟对齐残留误差。需要注意不要做水平翻转因为“动作从左边走向右边”翻转成“从右边走向左边”物理上没有对称性而且跌倒方向翻转后语义可能改变。这三点做完之后样本量一般能扩到原来的4到8倍跨位置和跨人的泛化会明显好于不做增强。3. 动作分类模型怎么选CNN、LSTM还是Transformer先看你的数据量3.1 为什么二维CNN可以直接吃距离-时间图距离-时间图本质是二维矩阵可以直接看成灰度图因此二维CNN是第一个想到的方案。它的优势在局部特征提取卷积核对时间和距离两个方向做局部响应能捕捉类似“距离边缘出现周期性强弱变化”这样的模式——走路时双腿摆动会让距离像宽度周期性变化跌倒时目标距离像会出现快速下移和展宽。但这里必须说一个常见误区不要把二维CNN等价于图像分类CNN。距离-时间图不是自然图像它的每一列距离轴有严格物理意义卷积核在距离方向上的尺度应该对应真实距离范围。举例来说如果每个距离单元代表4厘米卷积核在距离方向取5像素相当于只感知20厘米的范围这对人体动作远远不够所以距离方向的卷积核应设得大一些比如5到7个像素。相应地时间方向卷积核可以小一些3像素就够因为帧率通常不低相邻帧变化不大。关于预训练权重别用ImageNet预训练。自然图像的三通道统计特性和距离-时间图完全不同预训练权重不但帮不上忙还会拖慢收敛。我见过有人把距离-时间图复制三通道塞进ResNet18效果反而不如一个从头训练的三层小网络。数据量不够就靠数据增强和正则化而不是靠迁移一个语义完全无关的模型。3.2 序列模型怎么选LSTM/GRU适合帧数少的数据如果你有充足数据纯二维CNN也能做得很好但很多项目早期只有几十段动作此时序列模型往往是更稳的baseline。原因是距离像序列本质上是一维时间序列LSTM/GRU可以直接建模帧与帧的依赖单帧特征提取用少量全连接层或小CNN就够参数量远小于大二维CNN。选型逻辑我一般这么定样本段数少于1000优先LSTM或GRU样本在1000到5000之间CNN-LSTM混合结构最保险超过5000且动作模式复杂再考虑Transformer。Transformer的自注意力能捕捉长距离依赖但距离-时间图的序列长度通常在30到60帧自注意力的长程优势发挥不出来反而更容易在小样本上过拟合。如果项目后续要同时处理距离像和多普勒像微多普勒多模态输入时Transformer的优势才会明显。3.3 一个能跑的最小分类模型CNN-LSTM的PyTorch实现下面这个模型是我在早期实验里拿来当baseline的结构简单调整空间大import torch import torch.nn as nn class RangeProfileCNN_LSTM(nn.Module): def __init__(self, seq_len32, range_bins64, hidden_size64, num_classes6): super().__init__() # 空间特征提取时间方向核为3距离方向核为5 self.conv nn.Sequential( nn.Conv2d(1, 16, kernel_size(3, 5), padding(1, 2)), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d((2, 2)), nn.Conv2d(16, 32, kernel_size(3, 5), padding(1, 2)), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d((2, 2)) ) self.lstm nn.LSTM(32 * (range_bins // 4), hidden_size, batch_firstTrue) self.classifier nn.Linear(hidden_size, num_classes) def forward(self, x): # x: [batch, 1, seq_len, range_bins] batch_size x.size(0) x self.conv(x) # [batch, 32, seq_len//4, range_bins//4] x x.permute(0, 2, 1, 3).contiguous() # [batch, seq_len//4, 32, range_bins//4] x x.view(batch_size, x.size(1), -1) # [batch, seq_len//4, 32*(range_bins//4)] out, _ self.lstm(x) # [batch, seq_len//4, hidden_size] out self.classifier(out[:, -1, :]) # 取最后一个时间步 return out逻辑说明输入是[batch, 1, seq_len, range_bins]通道数为1相当于灰度图。第一个卷积层在时间方向用3×5的核距离方向感受野比时间方向大符合前面说的物理尺度原则。两层卷积加两层池化后时间维和距离维各缩小4倍所以seq_len32变成8range_bins64变成16。随后把通道维度拼到特征维得到每帧8条长度512的特征向量送入LSTM。最终分类器只取LSTM最后一个时间步的输出。参数说明hidden_size是LSTM隐状态维度64在6类动作分类中是够用的太小欠拟合取256以上在小样本时容易过拟合。seq_len和range_bins必须与预处理输出的尺寸一致。如果距离单元数是128需要调整池化层或加一层卷积否则range_bins无法被4整除时会报维度错误。训练时用交叉熵损失、Adam优化器、学习率1e-3batch size 32。这个结构在4到8类动作、每类200到500个样本的规模下准确率能作为强力baseline后续再往上提。3.4 训练配置学习率、batch、早停的设置训练深度学习分类模型配置比模型结构更影响结果。我先说一组默认值Adam优化器初始学习率1e-3batch size 32训练最多60个epoch早停patience设为10验证集loss连续10个epoch不下降就停止并回滚到最佳权重。学习率调整用ReduceLROnPlateaufactor设为0.5patience设为5。类不平衡是动作分类的常态。跌倒样本少、走路样本多时直接用交叉熵会让模型偏向多数类。一个低成本做法是给交叉熵传weight参数权重按类别样本数的倒数设置再归一化。如果数据不均衡严重先把类别权重加上再考虑过采样不要一上来就复制跌倒样本塞进训练集复制样本只会让模型记住重复模式泛化帮助有限。另外固定随机种子很重要。雷达数据采集成本高样本量通常不大模型对初始化很敏感。在代码开头设置torch.manual_seed(0)和np.random.seed(0)保证实验可复现不然你改了数据预处理很难判断效果变化到底是预处理引起的还是运气引起的。4. 训练、评估与部署分类结果怎么才算真的能信4.1 数据集划分按“动作事件”而不是按帧切这是整个训练流程里最重要、又最容易做错的一步。假设你连续录制了10段“走路”每段2分钟滑窗切分后一共得到2000个样本。如果按样本随机划分训练集和验证集同一段走路的相邻窗口会同时出现在两边验证集准确率会虚高20个百分点以上。正确的做法是按动作事件划分一次连续采集是一个事件同一个事件的所有滑窗样本必须整体进入训练集或验证集。代码上用sklearn.model_selection.GroupShuffleSplit或GroupKFold很好处理groups就是事件编号。按事件划分后验证集才反映模型面对“没见过的一段动作”的表现。如果项目还涉及多个人Group应该是“人事件”的组合例如person001_event003这样才能避免同一个人在不同天的数据泄漏。4.2 评估指标准确率之外的混淆矩阵与F1动作分类场景下准确率是最不直观的指标。一个错误方向是跌倒样本只占5%模型把所有样本都判成走路准确率也有95%但这个分类器毫无用处。真正要看的指标是按类别的Precision、Recall和F1以及整体的宏平均F1。跌倒检测尤其看重Recall因为漏报一次跌倒可能意味着一次未救治如果Recall不够宁可多一点误报让看护人员去复核。混淆矩阵要按“真实动作×预测动作”打印并标注行和列的动作名。观察混淆矩阵时重点看两个方向一是“跌倒”被误判成了哪些动作常见是坐下和下蹲二是“走路”和“跑步”这类周期性动作之间的混淆说明模型对动作频率不敏感这时需要检查帧率或滑窗长度。多分类任务里宏平均F1比准确率更能反映模型在每个类别上的均衡表现报告时建议同时给出。4.3 从训练曲线判断模型有没有学进去训练日志里只记准确率是不够的。我会记录每个epoch的训练loss、验证loss、训练acc、验证acc这四条曲线能暴露大部分问题训练循环可以这样组织for epoch in range(60): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() logits model(x_batch) loss criterion(logits, y_batch) loss.backward() optimizer.step() val_loss, val_acc evaluate(model, val_loader) scheduler.step(val_loss) print(epoch, loss.item(), val_loss, val_acc)如果训练loss下降验证loss先降后涨说明过拟合需要加强数据增强或增大dropout。如果训练loss和验证loss都下不去说明模型容量不够或输入特征有问题不要盲目加深网络先回到预处理看距离像有没有对齐、归一化是否正确。如果训练acc高、验证acc低除了过拟合赶紧检查是不是数据泄漏——按帧划分常常会表现出这种症状。还要警惕一种情况loss在某个值附近震荡不动比如跌倒在二分类里loss卡在0.693附近这通常是类别不均衡或模型输出恒为某一类先看混淆矩阵。4.4 部署时把预处理原封不动搬过去模型训练完预处理环节最容易在部署时被改坏。训练时你在Python里做了去直流、MTI、重心对齐、归一化部署到边缘设备时如果用不同参数重新搞一遍结果对不上很正常。我的习惯是把预处理流程封装成一个类训练和推理都用同一个类禁止在部署端重写一份。需要固定下来的参数包括距离像的长度R、MTI的滤波系数、重心对齐的target_center、归一化用的min和max。归一化尤其要注意训练时是按样本min-max归一化的推理时也要按当前这个推理样本自己算min-max而不是用训练集的全局统计量除非训练阶段明确做过全局归一化。如果部署平台是嵌入式设备输入尺寸seq_len和range_bins必须与训练时一致动态输入形状会触发不必要的重编译在设备上表现成“第一次推理特别慢”。5. 避坑时间距离像分类最常见的6个翻车现场5.1 现象距离像没对齐模型学到的是“人在哪个距离”而不是“人在做什么”现象训练准确率很高但把雷达挪到房间另一个位置准确率断崖式下跌。原因目标在距离窗中的位置随位置变化模型学到了位置特征。解决在预处理里做重心对齐目标中心统一到固定距离单元数据增强时再做距离方向随机平移逼模型忽略位置信息。5.2 现象静态杂波不滤除换个房间测试崩溃现象在A房间训练时准确率超过90%到B房间部署只有60%。原因A房间的桌椅、墙壁形成的静态杂波被模型当成背景特征。解决用动目标显示对慢时间做高通滤波把静止目标去掉同时把训练数据的采集环境做多样化至少包含两个不同房间或两种家具布局。5.3 现象同一个人的数据同时进训练和验证跨人测试全废现象随机划分时验证准确率88%换一个从没参与训练的人测试准确率掉到55%。原因按帧划分导致同一连续动作的样本泄漏。解决用按事件划分进一步按“人”留出评估时做跨人评估保证测试集里的人完全没出现过。5.4 现象用整段动作的平均距离像当输入时序信息被抹平现象模型把“坐下”和“跌倒”几乎区分不开。原因对整段动作的距离像取平均距离维度上的静态展宽保留了但时间维度上的变化被抹掉而坐下和跌倒的区别恰好在快速下移过程。解决改用滑窗距离-时间图做输入或至少保留时间维不要把序列压成平均向量。5.5 现象训练时滑窗1秒测试时滑窗3秒推理结果对不上现象离线评测F1不错部署后某些动作识别结果不稳定。原因训练和推理的滑窗窗长、步长不一致模型看到的时间范围与训练分布不同。解决把窗长、步长、帧率一起写进模型配置文件部署时严格复用如果在线推理因为是流式的用固定大小的缓存队列滑窗不要用“积累到整段动作结束再判断”。5.6 现象跌倒样本太少模型把“跌倒”全判成“坐下”现象训练集里走路5000条、站立4000条、跌倒只有120条结果混淆矩阵里跌倒那一列几乎全空。原因类别严重不均衡交叉熵被多数类主导。解决给损失函数加类别权重权重设为总样本数/(类别数*该类别样本数)把判断从“最大概率类”改成“跌倒类概率超过单独阈值”走一个手工的拒绝分类逻辑。6. 验证模型真本事跨人留一法、拒分类与CAM可视化6.1 跨人留一法一个动作分类器到底值不值得信数据里有多少人就做多少次实验每次留出一个人其余人训练、留出的人测试最终准确率取平均。这是衡量动作分类器泛化能力的底线实验。代码上用LeaveOneGroupOut非常直接from sklearn.model_selection import LeaveOneGroupOut logo LeaveOneGroupOut() for train_idx, test_idx in logo.split(X, y, groupsperson_ids): model.fit(X[train_idx], y[train_idx]) accs.append(model.score(X[test_idx], y[test_idx]))如果样本里有10个人跨人留一法平均准确率不到70%那说明模型学的是特定人的姿势习惯而不是通用动作模式。此时优先加数据增强幅度抖动、时间伸缩再看是否需要降低模型容量。6.2 拒绝分类给不认识的动做一个出口现实里总会遇到训练集之外的动作比如弯腰捡东西、打哈欠。多分类Softmax会把未知动作硬分到最近的类。解决办法是给每个类设概率阈值所有类概率都低于阈值时判为“未知”。阈值用验证集调跌倒检测的阈值可以单独调低例如0.4保证漏报少。6.3 CAM可视化看模型到底在距离-时间图的哪个区域想确认模型是不是真的在看“动作”而不是看杂波可以用Grad-CAM。做法是取最后一层卷积的特征图对目标类别的梯度做全局平均池化得到每个通道的权重再对特征图加权求和并过ReLU得到与输入同尺寸的热力图。将热力图叠加到距离-时间图上能看到模型在做“跌倒”分类时重点看的时间段和距离范围。如果热力图集中在目标运动轨迹附近解释成立如果热力图散在静态区域说明预处理没做干净回到第2章重新检查。我自己的习惯是每个项目先跑通“按事件划分的baseline 跨人留一法 一张Grad-CAM热力图”再把时间花在调网络结构上。这三个东西花掉的时间不超过一个下午但能省掉后面无数个“模型怎么调都上不去”的夜晚。希望这份笔记能帮你在做基于时间距离像的人体动作分类时少走几条弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站