简介:基于毫米波雷达的无线感知智能化算法设计,完整毕业设计源码与论文文件打包为zip。面向智能家居应用场景,方案提出将特征队列提取与任务输出需求解耦的系统架构,利用人体骨架关键点、锚定框等通用中间特征统一不同任务,相比端到端网络具备更好的鲁棒性与可移植性;同时针对Detection Transformer模型进行毫米波领域创新性修改,并完成仿真数据生成、真实数据采集与解析。压缩包共91个文件,以Python源码(py/pyc)、Jupyter Notebook、训练过程及数据分布图(jpg)、论文PDF和说明文档为主,整体约13.16MB,目录涵盖code、datasets、models、util等模块,含数据加载器、模型构建、训练评估与可视化脚本,便于直接运行或二次开发,且结构清晰。已有100人学习下载,适合需要完整参考实验流程与论文写作框架的读者。
1. 毫米波雷达无线感知:这套毕设源码把动作识别做成了两段流水线
智能家居里的毫米波雷达无线感知,最常翻车的点不是模型不够深,而是换个房间、换个人,准确率就掉一截。直接拿点云做端到端动作分类,模型记住的是训练集里的站位和速度分布,一到新场景就露怯。这套毕设源码没堆更强的分类网络,而是把“人在哪、在做什么”拆成两段:先用 DETR 输出通用锚定框(BBOX),再基于这个中间特征做动作判别。
项目核心是把 Detection Transformer 针对毫米波雷达做了适配,包含完整可训练的 DETR 实现、真实雷达数据加载器、训练引擎、IOU 评估脚本和毕业论文。要解决的是点云稀疏、噪声大的条件下,动作识别精度不够高的问题。源码目录里模型、数据集、评估、画图脚本分得清清楚楚,README 也标了依赖,跑起来不需要东拼西凑。
你在做毫米波雷达、无线感知或 DETR 相关课题的话,这套资源值得对照论文读一遍。下面从技术路线、数据流、训练参数和踩坑记录逐层拆。
2. 为什么端到端分类会撞墙:解耦架构与 DETR 适配方案
2.1 端到端网络的瓶颈:任务耦合是精度上不去的根源
雷达无线感知里最常见的 baseline 是:把点云体素化,喂给 CNN,输出动作类别。这个流程在单一固定场景下很漂亮,但它有一个结构性问题——特征提取和任务输出是耦合的。backbone 是被动作分类标签直接监督的,学到的是“对这个数据集、这套站位、这个速度区间有效”的特征,而不是“人形目标在雷达坐标系里的通用表征”。
我拆过不少类似项目,这类模型的典型症状是:A 房间测试准确率 90% 以上,换到 B 房间,雷达安装高度差了 20 厘米,准确率直接跌到 70%。原因不是网络容量不够,而是模型把“人站在哪、往哪个方向走”当成了分类依据。另一个问题是扩展性:今天要分走路、小跑、静止三个动作,明天要加跌倒检测,端到端的做法是重新标注、重新训练整个网络,动作类别一多,标注成本跟着爆炸。
所以项目论文里反复强调一个词:解耦。把“感知人形目标”和“判断具体动作”分成两个层次来建模。这个思路和计算机视觉里“检测 + 分类”的两阶段范式一脉相承,但落到了雷达点云上。
2.2 特征队列与输出解耦:锚定框如何统一多个动作任务
论文的核心贡献是提出了“特征队列”与“任务输出需求”解耦的架构。特征队列可以理解为一组通用中间特征——人体骨架关键点、锚定框、或者目标中心的 3D 坐标。下游任务需要的不是原始点云,而是这组中间特征。以这套源码为例,选的是锚定框(BBOX):先用模型预测出人形目标在雷达坐标系里的 3D 包围盒,再用这个包围盒去聚合点云特征,做动作判别。
这样做的好处有三个。第一,BBOX 预测是任务无关的,不管后面接的是动作分类、跌倒检测还是轨迹跟踪,前面这层不用动。第二,动作分类的输入从“整帧稀疏点云”变成“目标框内的结构化特征”,类别判断受位置偏移的干扰小得多。第三,可移植性强——同一个检测头,换一个环境只要重新微调,不用从头训练。
端到端分类和解耦架构的差别可以看这张对比表:
| 维度 | 端到端分类网络 | 特征解耦 + BBOX 中间层 |
|---|---|---|
| 换场景迁移 | 基本要重训 | 微调检测层即可 |
| 新增动作类别 | 重新标注全部数据 | 只加分类头和数据 |
| 特征可解释性 | 黑匣子,无法定位失败原因 | 先看 BBOX 是否准,再查分类 |
| 对点云稀疏度的敏感度 | 高,整帧稀疏直接掉点 | 低,目标局部特征相对稳定 |
这套架构里新加一个动作,理论上只动最后的分类层,检测层和特征队列都不需要改。这就是论文里说的“更鲁棒、更适用、更可移植”。
2.3 DETR 在雷达场景的适配:集合预测、匈牙利匹配与位置编码
选 DETR 而不是 Faster R-CNN 或 YOLO 做 BBOX 预测,是这套源码里值得琢磨的一个决定。雷达点云没有 RGB 纹理,anchor-based 检测器对 anchor 的尺寸预设非常敏感——人走近雷达时点云占的栅格数会变大,走远时变小,固定 anchor 很难覆盖这种尺度变化。DETR 是集合预测范式,不需要预设 anchor,直接用一组可学习的 object query 在解码器里迭代出目标框,天然避开 anchor 设计。
模型结构上,源码把 DETR 的四个关键件都拆出来了:backbone 负责把 BEV 栅格特征图提成高维特征;position_encoding 把雷达坐标系下的位置信息编码进特征;transformer 的 encoder 做全局上下文建模,decoder 用 object query 迭代出预测;new_matcher 用匈牙利算法在预测框和真实框之间做最优匹配,匹配成本由分类损失、L1 框距离和 GIoU 三部分加权组成。
DETR 在雷达场景必须做适配的点是位置编码。图像 DETR 的位置编码作用在像素坐标上,而雷达 BEV 栅格的横纵轴对应真实物理空间的距离和横向位置,单位是米。源码的 position_encoding.py 仍然用正弦余弦编码,但编码的坐标轴含义已经换成“物理坐标栅格”,这直接影响模型对目标相对雷达远近的感知能力。另一个适配点是 query 数量:图像目标检测一张图可能有几十个目标,而智能家居单雷达场景通常只有一到两个人,源码里 query 数量设得很小,避免大量空 query 占用匹配和不必要的计算。
3. 把训练跑起来:从雷达点云到 BBOX 的数据流与关键参数
3.1 真实数据长什么样:点云、JSON 标注与 BEV 栅格化
这套项目采集的是真实毫米波雷达数据,不是仿真数据。雷达输出的每个点通常包含四到五个维度:相对雷达的 x、y、z 坐标、多普勒速度、信噪比。标注文件是 JSON 格式,labeled_json_list.txt 里按行记录了所有标注文件的路径。每个 JSON 里至少包含三样东西:point 数组、bbox_3d 包围盒、action 动作标签。
我一般拿到一套雷达数据,第一步是去看 BBOX 标注的坐标系定义。radar 坐标系的 x 轴多是正前方距离,y 轴是横向,z 轴是高度,单位都是米。bbox_3d 可以用多种格式存——中心点 + 长宽高、或者两个对角点。训练前必须确认 DETR 输出的是归一化的中心点格式,还是反归一化回物理坐标后再算损失。这两者差一步转换,loss 的表现会完全不同。
雷达点云没法直接喂给 transformer,需要先栅格化成 BEV 特征图,也就是把三维空间投影到地面俯视图的网格上。源码里用的是三维体素信息,而不是丢掉高度的纯 BEV,这样动作判别能用到高度维度的变化——人蹲下和站立在 z 轴上的分布差异很大,对跌倒类动作尤其重要。
3.2 数据加载器 Radar_Real_dataloader_new.py 阅读笔记
数据加载器的核心是把原始点云变成多通道 BEV 栅格。下面是我按源码结构还原的核心逻辑,去掉了无关分支,方便讲参数含义:
# datasets/Radar_Real_dataloader_new.py 的核心逻辑(示意) import json import numpy as np import torch from torch.utils.data import Dataset class RadarRealDataset(Dataset): def __init__(self, json_list_path, grid_range=(-4, 4, -5, 5), grid_res=0.1, max_pts=512): # grid_range: BEV 栅格在 x/y 方向覆盖的范围,单位米 # grid_res: 栅格边长,0.1 表示 10 厘米一格 # max_pts: 单帧最多取多少点,超出截断,防止极端帧撑爆显存 with open(json_list_path, "r") as f: self.label_files = [line.strip() for line in f if line.strip()] self.grid_range = grid_range self.grid_res = grid_res self.max_pts = max_pts def _points_to_bev(self, points): # points 形状 (N, 4):依次是 x(m)、y(m)、z(m)、多普勒速度(m/s) xmin, xmax, ymin, ymax = self.grid_range H = int((ymax - ymin) / self.grid_res) W = int((xmax - xmin) / self.grid_res) # 三通道:占用密度、高度最大值、多普勒均值 occ = np.zeros((H, W), dtype=np.float32) hmax = np.zeros((H, W), dtype=np.float32) dopp = np.zeros((H, W), dtype=np.float32) for px, py, pz, v in points[:self.max_pts]: ix = int((px - xmin) / self.grid_res) iy = int((py - ymin) / self.grid_res) if 0 <= ix < W and 0 <= iy < H: occ[iy, ix] += 1.0 hmax[iy, ix] = max(hmax[iy, ix], pz) dopp[iy, ix] += v dopp = np.divide(dopp, occ, out=np.zeros_like(dopp), where=occ > 0) return np.stack([occ, hmax, dopp], axis=0) # (3, H, W) def __getitem__(self, idx): with open(self.label_files[idx], "r") as f: anno = json.load(f) points = np.asarray(anno["points"], dtype=np.float32) bbox = np.asarray(anno["bbox_3d"], dtype=np.float32) action = anno["action"] # "walk" / "jog" / "static" bev = self._points_to_bev(points) return torch.from_numpy(bev), torch.from_numpy(bbox), action三个通道的设计是有讲究的:占用密度通道区分“有没有人”,高度最大值通道区分“站姿和蹲姿”,多普勒均值通道区分“走动和小跑”——小跑的多普勒速度明显比走路高。如果之后要扩展跌倒检测,通常还会加一个 z 轴速度方差通道。
grid_res 是这里最值得调的参数。0.1 米一格在 8×10 米的范围内生成 80×100 的栅格,分辨率够用且计算量可控。如果调成 0.05,空间分辨率提升,但单帧计算量翻四倍,而且点云稀疏时很多格子是空的,收益有限。我一般先用 0.1 跑通流程,最后调优阶段再试 0.05,比较 IOU 的增量值不值这个计算成本。
3.3 模型装配:backbone、transformer、matcher 怎么协同
模型文件分散在 models/ 目录下,detr.py 是组装入口,backbone、transformer、position_encoding、new_matcher 各自独立。我习惯先从 detr.py 的 forward 看数据流,因为它定义了各个模块之间的接口约定:
# models/detr.py 的结构(压缩版) import torch.nn as nn from .backbone import build_backbone from .transformer import build_transformer from .position_encoding import build_position_encoding class Detr(nn.Module): def __init__(self, num_classes=4, num_queries=5): super().__init__() # backbone 输出通道固定成 256,之后 transformer 的 d_model 也是 256 self.backbone = build_backbone(out_channels=256) self.position_encoding = build_position_encoding() self.transformer = build_transformer( d_model=256, nhead=8, num_encoder_layers=6, num_decoder_layers=6 ) # 类别数含一个"无目标"类,实际动作类是走路、小跑、静止三类 self.class_head = nn.Linear(256, num_classes) self.bbox_head = nn.Linear(256, 4) # 输出归一化 [cx, cy, w, h] def forward(self, x): features = self.backbone(x) # 得到特征图,形状 (B, 256, H, W) hs = self.transformer(features, self.position_encoding) # hs 形状 (num_queries, B, 256) cls = self.class_head(hs) # (num_queries, B, num_classes) box = self.bbox_head(hs).sigmoid() # bbox 必须压到 0-1 return {"pred_logits": cls, "pred_boxes": box}这里有两个关键接口约束。第一,backbone 输出通道必须和 transformer 的 d_model 一致,否则拼接位置编码时维度对不上。第二,bbox 输出必须过 sigmoid 压到 0 到 1,因为后面匈牙利匹配里的 L1 距离是在归一化坐标下算的。很多自己改 DETR 的人会在这一步漏掉 sigmoid,导致 box 预测值跑到范围外,IOU 怎么都上不去。
new_matcher.py 实现的是匈牙利匹配,匹配成本是三部分的加权和:
# models/new_matcher.py 的匹配成本逻辑 # cost = cost_class * cls_weight # + cost_bbox_l1 * bbox_weight # + cost_giou * giou_weight匹配的作用是把 N 个 query 的预测和 N_gt 个真实框一一对应起来,训练时每个真实框只分配给一个 query,剩下的 query 要学会输出“无目标”。这套机制替代了传统检测里的 NMS 和 anchor 匹配,是 DETR 的核心。
3.4 训练与评估:loss 之外必须盯住 IOU 分布
训练入口在 main.py,训练循环在 engine.py。engine.py 里我重点看的是损失加权和反向传播逻辑:
# engine.py 的训练循环核心片段 def train_one_epoch(model, criterion, data_loader, optimizer, device): model.train() for samples, targets in data_loader: samples = samples.to(device) targets = [{k: v.to(device) for k, v in t.items()} for t in targets] outputs = model(samples) # outputs["pred_logits"]: (B, num_queries, num_classes) # outputs["pred_boxes"]: (B, num_queries, 4) loss_dict = criterion(outputs, targets) weight_dict = {"loss_ce": 1.0, "loss_bbox": 5.0, "loss_giou": 2.0} losses = sum(loss_dict[k] * weight_dict[k] for k in loss_dict.keys()) optimizer.zero_grad() losses.backward() optimizer.step()weight_dict 是最值得动手调的地方。原始的 DETR cosine 方案里 bbox 权重是 5、giou 是 2,小目标多的时候 giou 权重可以提到 3 或 4。注意这里 loss_bbox 用的是 L1,它对大目标和小目标的误差一视同仁,但雷达点云下距离远的目标天然框小,L1 误差被归一化后相对偏大,所以代码里通常要和 giou 配合——giou 对尺度不敏感,能平衡 L1 的尺度偏差。
评估脚本是 radar_eval_bbox.py,它做的事是把预测框和真实框的 IOU 全部算出来,再按动作分组统计分布。IOU 的计算本身很简单,但要注意坐标系的约定必须和训练时一致,否则评估出来的数字没有参考意义。项目里那几张“不同动作预测 IOU 分布图”就是用这个脚本的结果画的,比单独看 loss 曲线能说明问题得多——loss 是所有样本的平均,IOU 分布能看出哪个动作类别在做贡献、哪个类别在拖后腿。
4. 毫米波雷达训练避坑指南:五类高频翻车现场与排查记录
4.1 静止样本挤压运动样本,预测框整体偏移
**现象:**训练几千步后,走路和小跑的预测框还能大致贴合目标,但样本量更大的静止类别把所有 query 都往中心区域拉;可视化预测框时发现,静止类别的框普遍偏大,运动类别的框则偏小,整套 BBOX 预测出现系统性偏移。
**原因:**数据集里静止场景的采集成本低,样本量可能是运动类别的两到三倍。匈牙利匹配按全局最优配对,分类损失占大头时,模型倾向于把 query 都匹配到容易分类的静止目标上,运动类别的 query 被挤占。这是 DETR 在类别不平衡数据上的通病,不是雷达特有的,但雷达点云稀疏让这个问题更明显。
**解决:**在损失里对动作类别做加权,静止类别权重降到 0.5 左右,运动类别提到 1.5 以上;更直接的办法是在加载器里对三个动作做等比例采样,每个 batch 保证三类样本数量接近。我一般两种都做,等比例采样影响训练稳定性,损失加权影响最终精度,配合起来最省事。
4.2 loss 一路下降但 IOU 不涨,匹配成本失衡
**现象:**训练曲线很漂亮,loss 稳步下降,但 radar_eval_bbox.py 算出来的 IOU 中位数卡在 0.4 左右不动。预测框似乎找到了目标的大致位置,但边界始终对不准。
**原因:**loss 下降被分类损失主导了。分类任务相对好学,模型很快就学会把每个 query 分到正确的类别,剩下的 bbox 回归任务在总损失里占比太小,优化器没有足够的梯度去修正框的边界。典型的权重失衡——weight_dict 里 loss_bbox 权重太低,或者匹配成本里 bbox 部分占比太小,导致匹配阶段就把框位置误差放过了。
**解决:**先把 weight_dict 里 loss_bbox 从 5 提到 10,loss_giou 从 2 提到 3,观察 IOU 是否开始跟着 loss 走。如果还不行,去查 new_matcher 的匹配成本权重,确保匹配阶段对框位置有足够约束。判断标准是:匹配成本里 bbox 相关部分应该至少和分类部分同量级,否则匈牙利匹配只按照分类配对,框回归信号从一开始就是脏的。
4.3 训练曲线震荡不收敛,学习率与 query 数量耦合
**现象:**训练 loss 在几百步内快速下降后开始剧烈震荡,偶尔出现尖峰;换一个随机种子结果差异很大,同一个配置跑两次,最终 IOU 能差五个百分点。
**原因:**transformer 部分对学习率非常敏感。雷达数据量小,通常只有几千帧,backbone 用大学习率很容易把预训练特征冲掉;而 DETR 的 decoder 在训练早期会频繁调整 query 的目标分配,学习率太大时匹配结果不稳定,loss 自然震荡。另外 num_queries 设得比场景里真实目标数多很多时,大量空 query 会让分类头不断在“无目标”和“有目标”之间切换,也会放大震荡。
**解决:**把优化器换成 AdamW,transformer 部分学习率用 1e-4,backbone 学习率单独设成 1e-5,给 backbone 加一层 0.1 倍缩放。训练轮次不要少于 150 epoch,DETR 类模型是出了名的后期才收敛。num_queries 从 5 开始调——单雷达单人场景 5 个 query 够用且留了余量,调成 10 不会带来精度提升,只会增加空 query 占比和训练时间。
4.4 仿真转真实掉点,坐标对齐是最隐蔽的坑
**现象:**先用仿真数据训练,模型在仿真测试集上 IOU 超过 0.8;换到真实采集数据直接评估,IOU 掉到 0.4 以下,而且预测框整体往某个方向偏移。
**原因:**仿真数据标注的坐标系和真实数据标注的坐标系不一致。常见的情况是仿真里雷达坐标系原点在雷达本体,x 轴正方向是雷达朝向;真实采集时标注工具用了图像视角的坐标转换,或者 z 轴方向没有对齐。这个偏移很小,人工检查单帧根本看不出来,但匈牙利匹配对全局偏移极其敏感,所有框同时偏一个方向,IOU 会系统性下降。
**解决:**加载数据后先做一个坐标一致性检查——随机抽 50 帧,打印点云坐标的 min/max,和标注框的坐标范围对比,确认 x/y/z 的语义一致。再算一下真实数据里所有 bbox 中心点的均值和 std,和仿真数据的对应统计量对比。两个分布差异超过两倍,优先怀疑坐标变换而不是模型。我当时是卡了两天,最后发现仿真数据的 y 轴方向反了,翻转过来 IOU 直接涨了二十个点。
4.5 稀疏点云导致检测框抖动,时序平滑收尾
**现象:**模型在单帧上预测的 IOU 不低,但连续帧的可视化里预测框来回跳,位置抖动明显。走路这类连续动作,上一秒框在前方,下一秒跳到侧面,动作分类倒是没受影响,但框的稳定性没法用。
**原因:**雷达点云本身是稀疏的,单帧目标可能只有几十个点,帧与帧之间点云分布变化大。DETR 对每一帧独立做集合预测,没有时序约束,帧间抖动是正常现象。这不是模型 bug,是单帧检测的通病。
**解决:**在 post-processing 阶段对连续帧的 bbox 做指数滑动平均,alpha 取 0.3 到 0.4,比纯平均更能跟上动作变化。动作分类也建议用窗口投票——取最近 5 帧的分类结果做多数表决,能明显抑制单帧误判。这类后处理的代码不复杂,但效果立竿见影,是工程落地时性价比最高的一步。
5. 画图脚本怎么用:BBOX 分布、loss-IOU 与 UMAP 的排查链路
5.1 先画 BBOX 数据分布图,标注质量决定模型上限
项目 Fig 目录里那几张 BBOX 数据分布图不是摆设,它们是整个项目的“体检报告”。画BBOX分布图.py 做的事很简单:把每帧标注框的中心点按动作类别画成散点。我在跑任何训练之前都会先执行这一步:
# plotting/画BBOX分布图.py 的核心流程 import json import numpy as np import matplotlib.pyplot as plt with open("labeled_json_list.txt") as f: files = [line.strip() for line in f if line.strip()] centers = {"walk": [], "jog": [], "static": []} for path in files: anno = json.load(open(path)) b = anno["bbox_3d"] centers[anno["action"]].append([b[0], b[1], b[2]]) fig = plt.figure(figsize=(8, 6)) ax = fig.add_subplot(111, projection="3d") for action, color in [("walk", "C0"), ("jog", "C1"), ("static", "C2")]: data = np.array(centers[action]) ax.scatter(data[:, 0], data[:, 1], data[:, 2], s=2, alpha=0.5, label=action) ax.set_xlabel("x (m)") ax.set_ylabel("y (m)") ax.set_zlabel("z (m)") ax.legend() plt.savefig("bbox_dist.png", dpi=150)看这张图有三个重点:一是三类动作的空间覆盖范围是否重叠,如果走路全在左边、小跑全在右边,模型学到的可能不是动作差异而是位置差异;二是 z 轴分布有没有分层,静止和运动的 z 分布如果完全重合,说明动作判别只能靠多普勒通道;三是各类别点数有没有明显差距,这是后面类别不平衡处理的前置判断。
5.2 loss 与 IOU 关系图,判断训练是否真收敛
loss 曲线人人都会看,但 loss 下降不等于检测精度上升,这个项目专门画了 loss 与 IOU 关系图,两个指标叠在同一条时间轴上。核心判断逻辑是:loss 下降的同时 IOU 必须同步上升,如果出现“loss 平缓但 IOU 持续下降”,说明模型在过拟合分类任务,检测能力在退化。
训练过程的正常形态是:前 50 个 epoch loss 快速下降,IOU 从 0.2 涨到 0.6;50 到 100 个 epoch 两者增速放缓;100 个 epoch 以后 loss 进入平台期,IOU 可能还能缓慢涨几个点。IOU 平台期比 loss 平台期来得晚是 DETR 的正常现象,不要在 loss 刚平稳时就停训练,多等二十个 epoch 往往有惊喜。
这张图还有一个作用:判断要不要加载 checkpoint 继续训。如果 loss 还在降、IOU 也在涨,直接续训,不需要动超参数;如果 loss 降但 IOU 不动,回到第 4.2 节的权重排查流程,而不是盲目加训练轮数。
5.3 UMAP 特征可视化,检查特征队列是否可分
umap.jpg 是这套项目里最有意思的一张图,它把模型提取的特征用 UMAP 降维到二维平面,看三类动作在特征空间里的分布。我一般从 transformer 的 encoder 输出或者分类头之前的特征层取值,拉平成向量后交给 umap 库:
# plotting 里 UMAP 可视化的流程 # 1. 加载训练好的模型,提取中间层特征 # 2. 每帧得到一个特征向量,形状 (num_queries, 256),拉平成 1D # 3. 用 umap 降到 2D 后按动作类别着色 import umap reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, metric="euclidean") # feature_matrix 形状 (N_frames, feature_dim),每行是一帧的特征 embed = reducer.fit_transform(feature_matrix)UMAP 图能直接回答“特征队列是否解耦成功”这个问题。理想状态下,三类动作在 2D 空间里呈现三个清晰但不完全分离的簇——完全分离说明特征学到的是动作差异,重叠严重说明特征队列还是被位置或速度主导。如果三类动作在 UMAP 里按空间位置排布、而不是按动作类型聚集,那就要回到数据层面重新审视标注质量。
5.4 不同动作预测 IOU 分布图,定位薄弱动作
项目里“不同动作预测 IOU 分布图”用的是雷达评估脚本的输出,对每个动作类别分别统计预测框和真实框的 IOU 分布。这张图的用处是定位薄弱动作,而不是看平均分。平均 IOU 0.7 可能掩盖着“走路 0.85、静止 0.8、小跑 0.45”的极端情况。
我拿到这张图后的处理流程固定是:先看哪个类别的 IOU 中位数最低,再看这个类别的 BBOX 数据分布——是样本量太少,还是空间覆盖有盲区,或是多普勒特征本身就弱。确定原因后针对性地补数据或者调损失权重,而不是整体调参。这个“数据 → 模型 → 评估 → 归因”的闭环,比只看 test accuracy 调参靠谱得多,也是这套画图脚本真正的价值所在。
6. 进阶迁移:把 DETR 检测器扩到更多动作与多人场景
如果你用这套源码复现完走路、小跑、静止三个动作,下一步通常是把检测器往更多场景迁移。两种常见的扩展方向:增加动作类别,以及多目标场景。
增加动作类别是成本最低的迁移。把 detr.py 里 num_classes 从 4 改成 5(多一个“跌倒”类),把数据集标注补上对应类别,加载器里动作字符串映射表加一项,然后直接复用已有权重微调。我在做这类扩展时建议只微调分类头和 transformer 最后两层,backbone 冻结,学习率设成训练时的十分之一,这样既能保留已经学好的 BBOX 感知能力,又能快速适应新动作。要注意新类别的样本量不能太少,否则匈牙利匹配阶段新类别抢不到 query,训练完新动作识别率会很难看。
多人场景是另一个方向,改动集中在 query 数量和数据标注。单雷达场景下两人追跑,需要把 num_queries 从 5 调到 10 或 15,并且数据里要同时存在单人和双人帧,否则匹配阶段模型没见过“两个真实框同时出现”的情况。多人场景的类别不平衡更严重,建议把 bbox 损失的权重再往上提,因为两个人的框互相靠近时,分类损失区分度下降,只能靠框回归约束匹配。如果用的是 4D 毫米波雷达,点云里多出速度维信息,BEV 通道里还可以再加一个垂直向速度分量,对多人交错场景的区分效果比单纯调权重好得多。
迁移时我最常检查的一组参数如下表,每个参数改动后都先跑 30 个 epoch 看趋势再决定是否继续:
| 参数 | 位置 | 单人/三动作基线 | 多人/多动作建议 |
|---|---|---|---|
| num_queries | detr.py | 5 | 10~15 |
| num_classes | detr.py | 4 | 类别数 + 1 |
| loss_bbox 权重 | engine.py weight_dict | 5.0 | 7.0~10.0 |
| 学习率 | main.py | 1e-4 | 微调时 1e-5 |
| 栅格分辨率 | dataloader grid_res | 0.1m | 0.05~0.1m |
| 后处理平滑 alpha | 评估/推理逻辑 | 0.3 | 多人时 0.2 |
验证方法也建议换一套思路,不要只测离线 IOU。雷达感知最终要跑在嵌入式平台上,我在迁移完总是做一次推理耗时记录——单帧前向加后处理,目标是在 Jetson 级别的设备上达到 10 帧以上,达不到就降栅格分辨率、减 encoder 层数,优先保帧率再保精度。
这套源码给我最大的启发是 DETR 在雷达这类无纹理传感器上比想象中适用,只要把位置编码和特征通道按物理坐标重新设计,集合预测范式对稀疏点云反而比 anchor-based 检测器更稳。现在我每次做雷达感知训练,拿到新数据的第一件事就是先跑一遍 BBOX 分布图和 UMAP,确认数据和特征的底子没问题,再开始碰模型——这个习惯救过我太多次翻车现场。希望帮到你。
本文还有配套的精品资源,点击获取