ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从一维距离像到时间距离像:毫米波雷达动作识别与跌倒检测CNN实现

从一维距离像到时间距离像:毫米波雷达动作识别与跌倒检测CNN实现 简介论文《基于时间距离像的人体动作深度学习分类》刊于《桂林理工大学学报》2019年第39卷第1期面向雷达人体动作识别、深度学习应用与数据分类研究等方向提出利用超宽带雷达获取高分辨率距离像并构建时间-距离像交由深度卷积神经网络进行特征学习与动作分类。针对传统微多普勒特征因躯干强回波掩盖而微弱模糊、难以稳定提取的问题该方法通过空间位置变化信息有效改善分类效果在挥手、打乒乓球、拍篮球等9种典型动作上平均分类精度达96.67%验证了深度学习结合时间-距离像的可行性与有效性。此PDF为单文件文献资料大小仅3.49MB目前已有101人学习下载内容包含完整论文正文、图表及参考文献列表适合作为深度学习、数据分析、雷达目标识别等方向的参考文献或专业指导资料。读者可借此了解超宽带雷达与DCNN结合的技术路线复现实验设计思路并可通过文末引用追溯相关领域的发展脉络为后续研究提供理论借鉴。1. 时间距离像不玄学一维距离像拼成二维图深度学习分类的门槛在哪做无接触跌倒检测时摄像头带来的隐私压力一直很大而雷达回波本身不包含可辨识的人脸纹理天然躲开了这条红线。把每个chirp的距离剖面按时间顺序堆叠起来得到的就是时间距离像TDI人体动作在这个二维图里表现为条纹的左右摆动和明暗变化。深度学习分类的输入从一维距离像变成二维时空图模型不再依赖手工提取的多普勒特征只需要在标注好动作类别的TDI上训练即可。这篇文章写给正在做跌倒检测、老人监护或安防入侵识别的工程师目标是让数据来源、窗口怎么切、模型怎么调一次讲透。2. 用毫米波雷达产出时间距离像雷达参数与数据构建2.1 距离像怎么生成从chirp到range profile的FFTFMCW雷达发射线性调频连续波回波与本地发射信号混频后得到一个中频信号它的频率正比于目标距离。对每个chirp的ADC采样点做一次FFT就得到“距离剖面”也就是一维距离像。这个一维数组里的每个元素表示对应距离bin上的反射能量。距离分辨率由带宽决定ΔR c / (2B)。例如带宽开满3.6GHz距离分辨率约4.2cm足够分辨人体躯干和四肢的相对位置。时间距离像则是把多个chirp的距离剖面按时间顺序堆成二维矩阵横轴是距离bin纵轴是chirp序号。有人习惯把距离放横轴、时间放纵轴也有人反过来这个不影响模型只要训练和推理保持一致即可。人体动作在矩阵里会形成斜向或摆动的纹理手臂抬起时强反射点从躯干中心向外移动下蹲时整个能量团在距离轴上先移近再移远。CNN要学的是这些随动作变化的时空纹理而不是单帧距离峰的位置。这个步骤不需要自己从零写信号处理链路。TI毫米波SDK、英飞凌的BGT60系列或者国产的毫米波开发板通常都直接给出range FFT之后的复数结果我们只需要取幅度并做堆叠。真正影响TDI质量的是雷达参数怎么设以及数据是用帧模式还是streaming模式导出。2.2 把range profile堆成TDI核心代码与参数表常见做法是把雷达输出的原始回波矩阵直接拿过来处理。假设radar_cube的形状是(num_chirps, adc_samples)即每一行是一个chirp的ADC采样数据用下面这段代码生成TDIimport numpy as np # radar_cube 形状: (num_chirps, adc_samples) # 来自DCA1000或毫米波SDK的原始回波通常是IQ复数 num_chirps, adc_samples radar_cube.shape num_range_bins 64 # 后续只保留前64个距离bin覆盖约2.7米视场 # 1. 对每个chirp做range FFT得到距离剖面 range_profile np.fft.fft(radar_cube, axis1) range_profile range_profile[:, :num_range_bins] # 2. 幅度提取 对数压缩 mag np.abs(range_profile) log_mag 20.0 * np.log10(mag 1e-6) # 3. 得到 (num_chirps, num_range_bins) 的时间距离像 tdi log_mag.copy() # 4. 归一化到 0~1方便显示和训练 tdi (tdi - tdi.min()) / (tdi.max() - tdi.min() 1e-6)代码逻辑不复杂。第一步按axis1做距离FFT得到的目标是每个chirp对应的距离能量分布第二步取幅值后做20倍对数压缩因为墙面等静态目标回波动态范围很大直接用线性幅度会让强目标主导整个图的对比度第三步把chirp维度保留下来作为时间轴第四步归一化。这段代码量不大看过python深度学习教程的人应该都能直接读懂。对应的雷达参数可以这样设下面的表是我在室内人体动作识别场景里验证过的一组参数常见取值对TDI的影响带宽 B3.6GHz距离分辨率约4.2cmADC采样率 Fs10MHz配合chirp斜率决定最大不模糊距离chirp斜率 S60MHz/us相同中频下对应的距离变近/变远chirp重复周期100us时间轴采样率10kHz距离bin数64约2.7m距离覆盖参数之间是联动的。最大不模糊距离Rmax Fs * c / (2S)用Fs10MHz、S60MHz/us算出来约25m室内动作识别不需要看那么远所以距离轴只取前64个bin正好覆盖雷达前方0.5m到3m左右。如果人的活动范围大就增加bin数或把chirp斜率调小一点但chirp斜率太低会降低距离分辨率需要同时加大带宽来补偿。提示如果雷达SDK已经直接输出了range profile就不要再自己对原始数据做一次FFT否则得到的是“距离谱的频谱”物理含义会乱掉TDI纹理也会被破坏。2.3 帧模式与streaming模式为什么低帧率做不了动作分类毫米波雷达有两种常见数据输出方式。frame-based模式下一帧内包含几十到几百个chirp帧与帧之间隔几十毫秒。比如一帧128个chirp、帧周期50ms算下来时间轴每秒只有20行。挥手一次大约0.5秒在TDI上只有约10行纹理被压缩到一个几乎看不清的条形CNN很难学出动作顺序。做动作分类要用streaming模式或者把frame里面的chirp当成连续流处理。DCA1000等采集卡可以连续导出chirp数据不按frame切分。这样chirp重复周期100us时每秒有10000行动作纹理在时间轴上有足够像素。实际建模时把连续chirp流直接作为TDI输入而不是先用frame平均后再拼接这是数据获取阶段最容易踩的第一道坑。3. 预处理与数据集划分窗口长度、MTI和样本平衡3.1 背景相消与人体区域对齐原始TDI里墙面、地面、桌椅的静态回波在时间轴上几乎不变形成一条条横向亮带。这些亮带如果不处理CNN很容易根据能量集中在哪个距离bin来判断“有人没人”而不是根据动作纹理分类等于让模型学了一个更简单的捷径。常见做法有两种采集一段空场景TDI作为背景估计在复数域相减或者沿时间轴做高通滤波也就是MIB和MTI的简化版。from scipy.signal import butter, sosfilt def mti_hp(tdi, cutoff1.0, fs10000): 沿时间轴的高通滤波抑制静态杂波。tdi形状: (T, R) sos butter(4, cutoff, btypehigh, fsfs, outputsos) out np.zeros_like(tdi) for r in range(tdi.shape[1]): out[:, r] sosfilt(sos, tdi[:, r]) return outcutoff取1Hz时能滤掉呼吸和静态物体带来的缓变分量保留0.2Hz到5Hz的人体动作主频。如果后面还要做呼吸检测cutoff要降到0.1Hz。滤波对每一列独立处理可以避免空场景背景随时间漂移的问题也不用额外采集一段纯背景数据。人体区域对齐是另一个容易忽略的步骤。同样做“跌倒”有人站在0.8m处有人站在1.5m处TDI上能量峰的位置完全不同。直接把整段距离轴喂给CNN模型会学到“目标绝对距离”而不是动作本身的纹理。我一般先在TDI上找能量最强的距离bin以它为中心截取固定64个bin。目标横跨多个强bin时取最大值那个bin往前16个、往后47个把人体包在窗口中间。这样换一个位置部署时输入分布不会发生剧烈漂移。3.2 滑动窗口生成样本与标签对齐TDI总长度可能是几秒甚至几十秒分类模型需要固定尺寸的短样本。一个动作从开始到结束坐下约0.6~1秒跌倒约0.4~0.8秒站起约1秒。我一般用1.28秒作为窗口长度步进0.4秒生成大量重叠窗口。如果chirp周期是100us1.28秒对应12800行把它压到128行再进模型。def make_samples(tdi, labels, win_frames128, stride16): tdi: (T, R)已归一化(0~1) labels: 与T行对应的帧级标签int 返回样本列表与样本标签 samples, sample_labels [], [] t 0 while t win_frames tdi.shape[0]: seg tdi[t: t win_frames] if seg.shape[0] ! win_frames: break # 如果win_frames不是128把时间维压到128行 if win_frames ! 128: seg seg.reshape(128, win_frames // 128, -1).mean(axis1) samples.append(seg) # 标签该窗口内多数帧的类别 seg_labels labels[t: t win_frames] sample_labels.append(np.bincount(seg_labels).argmax()) t stride return np.array(samples), np.array(sample_labels)这里win_frames需要是128的倍数我的习惯是先对整段TDI做padding补成128的倍数再切。stride越小相邻窗口重叠越多样本数越多但样本之间的相关性也越高。标签用多数投票是因为窗口边界可能把动作前后段切进来少数帧的干扰不应该决定整段样本的类别。注意不能把所有窗口随机打散后划分训练集和验证集。同一个动作被stride切出来的多个窗口内容高度相似随机划分会把几乎重复的样本同时放进训练和验证里导致验证精度虚高。正确做法是按人员划分即一个人的所有窗口只能出现在训练集或验证集这就是Leave-One-Subject-Out。3.3 类别不平衡怎么办动作识别数据天然不平衡跌倒样本每分钟只有几次而行走、静止几乎一直存在。直接用CrossEntropy训练时模型学会“全部输出行走”就能把loss压得很低。常见做法是先对少数类做重复采样或者用更小的stride为少数类多切几段。重复采样时不要连续复制同一个窗口而是用stride4、8多切几段再配合少量高斯噪声避免模型直接记住几段固定样本。损失函数层面把CrossEntropy换成FocalLoss更直接。FocalLoss在困难样本上分配更高梯度在简单样本上降低权重能有效缓解“跌倒”这类低频动作被淹没的问题。具体参数我放在第4章展开。另一个关键是评价指标不要只看accuracy重点看每个类别的precision和recall尤其是“跌倒”的recall。漏报一次跌倒比误报一次危险得多这个业务代价在模型设计时就要想清楚。4. 模型选型与训练参数小样本下CNN怎么调才不飘4.1 为什么用CNN小样本下LSTM和Transformer不占优TDI本质是二维图像卷积核天然适合提取“距离相近、时间相近”的局部纹理。LSTM和Transformer在长序列建模上更强但毫米波动作数据通常只有几千到几万个窗口远不够它们吃数据、吃正则。我试过把动作片段直接交给Transformer效果反而不如一个三层卷积原因在于TDI上的动作纹理高度结构化CNN感受野已经覆盖了足够的时空上下文。这和深度学习模型CNN识别恶意软件的思路同构——都是先把一维或二维模式组织成图像再由卷积核提取判别特征输入模态不同训练方法论一致。纯CNN也有缺点时序上下文需要靠多层堆叠来扩大感受野。因此输入的时间维不能压太狠。128×64的输入尺寸是我常用的底线再小比如32行的TDI动作纹理就彻底混在一起了。4.2 模型结构与超参128×64输入、卷积块、Dropout给出一个不依赖预训练权重的浅层CNN适合样本量不大的TDI任务import torch import torch.nn as nn class TDICNN(nn.Module): def __init__(self, num_classes6): super().__init__() self.net nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2, 2), # (16, 64, 32) nn.Conv2d(16, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2, 2), # (32, 32, 16) nn.Conv2d(32, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(), nn.AdaptiveAvgPool2d((4, 4)), # (64, 4, 4) ) self.head nn.Sequential( nn.Flatten(), nn.Linear(64 * 4 * 4, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes), ) def forward(self, x): return self.head(self.net(x))输入固定为(B, 1, 128, 64)128是时间维64是距离维。前两个卷积块用MaxPool把时间方向从128降到32、距离方向从64降到16第三层用AdaptiveAvgPool统一到4×4这样模型对输入尺寸的小变化不敏感也能容忍不同雷达配置下行数微调。全连接前加Dropout(0.3)样本少时防止FC层把特征写死。整个模型参数不到10万在几千到几万样本的TDI任务上比ResNet-18更稳ResNet-18那种深层结构在这个量级下很容易过拟合。训练代码和MNIST手写数字分类的PyTorch流程几乎一样只需要改一下dataloader的输入通道和标签数量。如果只做过基础分类项目可以直接套用之前的训练模板。4.3 训练策略增强、调度器与损失函数训练超参我一般按下面这组参数起步再根据验证集微调超参取值说明优化器AdamWlr1e-3, weight_decay1e-4调度器CosineAnnealingLRT_max40, eta_min1e-5Epoch50监控验证loss连续10轮不降就停Batch size32样本少时可以用16增强距离平移±4bin、时间拉伸0.9~1.1、高斯噪声σ0.01numpy中先做增强再转Tensor距离轴随机平移是这组增强里最关键的一项。它模拟人的位置在雷达视场内小幅漂移相当于让CNN学到“目标的相对运动纹理”而不是“某个绝对距离bin上的反射”。时间拉伸能模拟动作快慢变化但要控制幅度拉伸过大容易破坏动作节奏。高斯噪声主要用来增强抗干扰能力幅度太大反而会把微弱动作纹理盖住。损失函数默认用CrossEntropy类别不平衡明显时换成FocalLoss。FocalLoss在二分类形态下的核心是两个参数alpha控制正负样本权重gamma控制困难样本权重alpha0.75、gamma2.0是我在跌倒检测场景里常用的起点。它会在“行走”这类容易被正确分类的样本上降低梯度贡献让模型把注意力放到“跌倒”上。验证阶段坚持按人员切分。同一个人的多个窗口即使内容不同步态习惯、躯干形状也有相似性随机划分会把这种“身份特征”泄露给模型看似指标好换个人立刻打回原形。5. 避坑 / 常见问题TDI分类最容易翻车的六个点5.1 距离混叠人明明在1米TDI却在7米又出现一条现象人的能量同时出现在近处和远处模型被幽灵目标干扰分类准确率下降。 原因FMCW最大不模糊距离不够。ADC采样率或chirp斜率组合不当使目标回波中频超出可测范围发生频谱混叠折叠到另一个距离bin。 解决把最大不模糊距离调到大于实际视场。典型做法是提高ADC采样率或者降低chirp斜率再重新做range FFT。室内动作识别时距离轴只保留0.5~3m不必追求很远的探测距离。5.2 频谱泄漏静态强反射把肢体微动埋掉了现象TDI距离轴出现一条很粗的亮纹手臂和腿的摆动完全看不清训练时loss降不下去。 原因range FFT默认用矩形窗旁瓣只有-13dB。墙面回波比人手臂回波高几十dB强目标旁瓣把弱目标主瓣淹没。 解决range FFT之前给每个chirp加Hanning窗再做MTI。加窗后距离分辨率会变差一点主瓣变宽约1.4倍但肢体微动纹理的可见性提升非常明显这个交换是值得的。5.3 窗口长度只覆盖半个动作周期模型学到的是局部姿势现象“站起”动作总是被误判成“跌倒”。 原因窗口只有0.5秒而站起从下蹲到直立要1秒模型只看见了下蹲段就把这段TDI当成跌倒。 解决先统计每个动作类别的持续时间窗口取最长动作周期的1.2倍。TDI分类宁可窗口长一点让模型看到完整的“先下蹲再直立”顺序也不要为了增加样本数量牺牲时序完整性。窗口步进可以适当加大以控制样本总量。5.4 采集方向单一侧向数据在接线后掉精度现象训练集只有正对雷达的数据测试时人侧对雷达accuracy直接掉十几个点。 原因侧对雷达时人体前后摆动在距离维上投影大幅减小TDI条纹模式与正对时完全不同。模型学会的是“正面动作纹理”不是“动作本身”。 解决采集时按0°、45°、90°三个角度和两个距离各采一组。训练时加距离轴随机平移模拟位置漂移。如果采集条件有限至少不要在预处理时把所有样本都裁到完全居中保留一定的位置偏移作为增强。5.5 类别不平衡跌倒样本太少全模型只会输出“行走”现象验证集accuracy有90%但混淆矩阵里跌倒的recall几乎为0。 原因跌倒样本占比可能不到1%模型学到的决策边界是“全都预测行走”损失已经足够低。 解决FocalLoss 少数类重复采样。注意要多人多次采集跌倒而不是让一个人录大量跌倒样本。否则按人员切分时这个人的所有跌倒窗口会被整个切到验证集训练集里一个跌倒样本都没有。5.6 过拟合训练集漂亮验证集一路躺平现象训练集loss持续下降验证集loss在第10个epoch开始回升混淆矩阵里少数类逐渐被遗忘。 原因样本量小模型容量又偏大加上增强不够充分。 解决先砍模型宽度把每层的通道数从32/64/128降到16/32/64这种量级再做更强的距离平移增强最后观察训练集和验证集的类别recall变化发现某个类别开始掉就尽早早停。TDI任务上模型容量带来的收益远不如数据多样性这个坑值得反复提醒。6. 上线前怎么做轻量化导出与离线闭环验证6.1 离线闭环验证用预测平滑躲开单帧误判模型在单个窗口上输出分类但真实场景是连续时间流单窗口误判会造成输出抖动。我会拿一段1分钟的连续TDI数据回放每个窗口得到softmax向量然后用长度为3到5的中值投票平滑置信度低于0.5的输出标记为“未知”。from collections import Counter def smooth_hist(preds, k3): k3表示当前帧前后各取3帧做多数投票 return [Counter(preds[max(0, i-k): ik1]).most_common()[0][0] for i in range(len(preds))]k太小起不到平滑效果k太大会让动作切换变迟钝。我习惯在跌倒检测里用k3跌倒动作本身约0.5秒折合多个窗口平滑后既能滤掉单个误报又不影响报警及时性。6.2 轻量化导出ONNX与INT8量化注意点如果要把模型部署到边缘盒子pth文件需要导出成ONNX。导出时固定输入尺寸为(1,1,128,64)再用onnxruntime跑一遍测试输出确认与PyTorch结果一致。INT8量化要在目标设备上采集约100段TDI做校准集校准集的动作类分布要和真实场景接近否则量化后“跌倒”的精度会比“行走”掉得更多。这本质上还是类别不平衡问题在量化环节的重演。我现在拿到一批TDI数据第一件事不是训练模型而是先打开图看人体条纹清不清晰、有没有混叠、静态杂波是不是被压下去了。模型分类结果差的时候八成是数据采集或预处理出了问题而不是模型结构不够先进。这个习惯帮我省掉了很多无用功希望帮到你。本文还有配套的精品资源点击获取
返回列表