资讯动态

STM32N6实战:扩展BlazeFace实现多类别人脸属性识别

发布时间:2026/8/30 7:46:36 来源:尧图企业网站定制
把 Google MediaPipe 里的 BlazeFace 跑上 STM32N6这件事本身不算难Cube.AI 里拖一拖、模型转一转demo 就能动起来。但如果你跟我一样接到一个看似很合理的需求——“在人脸关键点流水线里再加几个类别”比如同时输出“是否戴口罩”“是否闭眼”“人脸角度区间”原来的快乐就会瞬间结束。因为 BlazeFace 原版根本不是一个多类别检测器它从头到尾只认“人脸”这一类。你需要在训练侧扩展它的检测头在数据侧重新组织样本在部署侧处理 STM32N6 NPU 的算子与量化约束最终把整条 ISP 到检测到关键点解码的 pipeline 串起来保证新增类别不会拖垮帧率和内存。这篇文章就是我在这个方向上从零到一的完整记录包含模型结构怎么改、训练怎么分段、ONNX 导出会遇到什么妖、以及最后落板子时那些文档里不会写的问题。适合正在用 STM32 系列做边缘视觉、尤其是想把手头 MediaPipe 模型扩展升级的嵌入式工程师参考。1. 选型复盘为什么在 STM32N6 上选择扩展 BlazeFace 而不是换一个网络1.1 STM32N6 的 NPU 对轻量检测模型的硬约束STM32N6 是意法半导体第一颗内置神经网络加速单元他们叫 Neural-ART的 MCU标称算力在每秒几千亿次操作级别。听上去不错但常跑嵌入式模型的人都知道不能只看算力峰值。它背后有几个非常实际的约束内部 SRAM 有限外部 DDR 的带宽和延迟会影响 NPU 和 CPU 之间的数据搬运NPU 对算子的支持是有列表的不是所有 ONNX 算子都能加速int8 量化是常态而且校准数据选得不好精度会打骨折。所以在这个平台上选模型第一原则不是“精度最高”而是“吃完 NPU 支持列表里的算子”。BlazeFace 恰好属于这种类型Google 当初设计它就是给移动端和嵌入式用的主干里大量使用 3x3 和 5x5 的深度可分离卷积输出层是 SSD 风格的检测头没有复杂的 DCN 可变形卷积没有 Transformer 那套 attention整体结构非常规整。相比把 YOLOv5n 或者 NanoDet 往 STM32N6 上搬BlazeFace 在原封不动转到 ONNX 后能被 NPU 吸收的层比例高很多后处理也只是简单的 decode NMS不会让 Cortex-M55 那边的 CPU 扛太多负担。不过也要说清楚BlazeFace 不是万能的。它是一款实时的轻量人脸检测器如果你新增的类别是“水杯”“手机”“人”这种跟人脸差异很大的物体完全靠改 BlazeFace 的检测输出去做效果大概率不理想。因为主干特征是为“人脸”这个特定语义调优出来的泛化到完全不同的物体时前面几个 block 的特征表达力不够。所以我在扩展前先明确了一个边界新类别必须与“人脸”强相关比如人脸口罩、人脸眼镜、人脸朝向、人脸表情这种场景下扩展 BlazeFace 是合理的跨到通用物体检测建议换网络不要硬来。1.2 在现有流水线上做扩展成本远低于换模型另一个让我坚持改 BlazeFace 的原因是项目里已经存在一条完整的人脸关键点 pipeline摄像头采集 → ISP 输出 RAW/RGB → ROI 裁剪与缩放 → 模型推理 → 关键点解码 → 后续业务逻辑。这条链路如果一旦换成新模型意味着 ISP 后的预处理参数要重新调模型输出的解码逻辑要重写Cube.AI 工程要重新集成大量的联调测试要重跑。而扩展类别本质上是把“人脸检测”升级成“人脸及相关属性检测”backbone 的特征还能继续用模型的输入输出尺寸、anchor 分布、整体推理流程都不用推翻属于在一条稳定的流水线上做局部升级。其实这里有一个更根本的问题既然后续只是做人脸关键点为什么不直接跑一个分类模型来识别口罩/闭眼这些属性我也这么纠结过。后来发现单一分类头的方案要额外维护一套“框住人脸 → 丢给分类器”的两级流程二级分类器在 STM32N6 上可能只占百分之几的算力看似很轻但工程上要处理框的时序稳定性问题——如果检测框在前后帧抖动裁剪出来的人脸区域会忽大忽小分类器很容易误判。反过来让检测头本身输出“face”“face_with_mask”这样的多类别框和属性天然绑定后期业务拿到的就是一套对齐好的结果。这个思路也符合上游业务方“一条模型链路搞定检测属性”的预期。2. 动检测头之前先理解 BlazeFace 里的“类别”到底存在哪里2.1 BlazeFace 的结构与 Anchor 体系BlazeFace 是一个单阶段、基于 anchor 的检测器它不像 YOLO 那样把每个网格直接预测类别数而是预先在特征图上铺好一堆固定大小的 anchor 框然后让模型为每个 anchor 预测两类东西一是相对 anchor 的坐标偏移box regression二是这个 anchor 属于某个类别的置信度classification同时它还多输出一组人脸关键点的偏移。原版模型为了极致性能把类别数设成了 1也就是只有“人脸”这一类很多开源实现里甚至直接把这个维度省略掉只输出一个物体性分数。但严格来说SSD 风格的检测头本来就该有“num_classes”这个维度。原版 BlazeFace 的锚点总数我记得是 896 个左右分布在多个尺度的特征图上。每个 anchor 对应的类别输出原版是 1 个值也就是“我像不像人脸”如果我们要扩展成 3 类比如背景、人脸、带口罩人脸那么每个 anchor 的类别输出就必须从 1 变成 3。放到张量维度上就是原来类别分支的输出 shape 是[batch, 1, num_anchors]或者[batch, num_anchors, 1]扩展后就变成[batch, num_anchors, num_classes]。很多第一次改模型的人会在这一步入坑为了省事直接在当前模型最后加一个nn.Linear(1, num_classes)或者把最后一层卷积的输出通道从num_anchors改成num_anchors * num_classes看似 shape 对上了但训练时 loss 根本不收敛。原因很简单——你只在输出端改了维度但 anchor 匹配、正负样本定义、loss 计算甚至后处理 decode 全都还停留在单类别的写法上。SSD 的检测头不只是“最后一层卷积”而是一整套围绕类别数展开的数据流。2.2 扩展类别时输出张量怎么改才算规范以我手头基于 PyTorch 的 BlazeFace 实现为例修改检测头结构其实只有几行代码我把它放到一个num_classes参数里而不是写死class BlazeFace(nn.Module): def __init__(self, num_classes1, num_anchors896): super().__init__() # backbone 与 box/keypoints 分支保持原样 self.num_classes num_classes # 类别分支输出通道数变为 num_anchors * num_classes self.classifier nn.Conv2d( 96, num_anchors * num_classes, kernel_size1, stride1, padding0 )但真正麻烦的是损失函数那块。原来每个 anchor 的类别标签是 0/1背景 or 人脸现在要改成 one-hot 标签而且维度要铺到num_anchors * num_classes。在计算 Softmax 或 Sigmoid 交叉熵之前需要对预测张量做 reshape。这里我建议训练时把 tensors 整理成[batch, num_anchors, num_classes]的布局这样锚点匹配逻辑、困难样本挖掘逻辑都能复用过去的代码不用为了兼容两个版本而写两套索引。另外一个容易被忽略的地方是新增类别后NMS 后处理里的类别过滤索引也要动。多数 BlazeFace 的推理脚本里解码后的检测结果会按“得分大于阈值”去筛框再按类别 id 做抑制。如果类别数从 1 变成 3那么后处理必须按类别分别做 NMS尤其要小心那些跨类别的重复框。比如同一张脸上模型同时输出了“人脸”和“带口罩人脸”两个高置信度的框它们高度重叠如果你做的是全局 NMS就会把其中一类全干掉。实际项目里我们的做法是先按类别分组组内做 NMS最后合并输出同时允许业务方指定某些类别组之间可以共存。2.3 一个容易在需求层面就埋雷的问题关键点要不要区分类别BlazeFace 的价值不光是检测框还在于它能回归 6 个人脸关键点两只眼睛、鼻子、两个嘴角、下巴具体布局视版本而定。扩展类别时就必须先想清楚新类别对应的关键点和原来的“人脸”关键点是不是同一个语义如果是“带口罩人脸”模型还必须输出鼻尖吗这时候鼻尖可能被遮挡硬让它回归鼻尖坐标训练时就会产生一个“不可能完成的任务”模型只能靠瞎猜去拟合最终把整个关键点头部带坏。这就要在数据标注和 loss 设计上做选择。比较实用的做法是分两套输出检测框和类别走一组预测头6 个关键点走另一组预测头保持关键点不跟类别绑定所有类别共用同一套关键点解码方式。如果业务方确实需要“带口罩时不要鼻尖关键点”那也不应该修改网络结构而是在后处理层根据类别 id 丢弃对应关键点。这样模型训练的稳定性会高很多因为你没有强迫网络去为一个语义不稳定的目标做回归。我强烈建议在立项阶段就跟需求方对齐这一点否则后面训练、量化、部署全跑完了突然说关键点输出不对返工成本极高。3. 数据与标注新类别的训练素材不能靠“简单补充”3.1 数据集来源与标注格式的适配改动检测头之后最优先要解决的就是训练数据。BlazeFace 官方是用 WIDER FACE 这类大规模人脸数据集训练的我们做扩展时原始人脸数据量是够的关键是新增类别的样本怎么凑。以“带口罩人脸”为例有公开的 MAFA 数据集可以用但这些公开数据大多来自互联网图片摄像头角度、光照、分辨率跟我们的实际场景差距很大。所以我在项目里的策略是公开数据集打底自采数据做真实场景校准。自采时把摄像头装在目标场景的典型位置录制不同时间段、不同光照、不同人脸的样本然后按帧切图。切出来的图片要覆盖侧脸、低头、抬头、远近变化不要只采正脸否则部署后稍微偏一个角度模型就“瞎了”。标注格式这一步强烈建议直接转成 COCO 或 VOC 这类通用格式不要一开始就写脚本生成 BlazeFace 特有的 SSD 二进制格式。因为标注工具、数据清洗脚本、模型训练代码都要在同一个数据集上反复迭代通用格式生态最成熟后期加类别、加标注只需要改 JSON 文件。标注字段里除了 bounding box还要有 landmarks 和 category_id。如果你打算共享关键点所有类别的 landmark 位置定义必须严格一致一个类别的关键点标在眼睛上另一个类别标在额头上训练时就会打架。3.2 数据增强关键点必须跟着框一起变换检测关键点任务的数据增强比纯检测要麻烦因为翻转、旋转、随机裁剪时关键点坐标必须同步变换。很多人第一次跑通但精度不行的原因就出在这里图像做了水平翻转关键点没做镜像模型等于看了一半的“左右眼坐标反了”的标注不崩才怪。我代码里翻转部分的逻辑大概是这个样子if random.random() 0.5: img img[:, ::-1, :] # 水平翻转 bbox[:, [0, 2]] width - bbox[:, [2, 0]] # 框左右互换 landmarks[:, :, 0] width - landmarks[:, :, 0] # 所有x坐标镜像 if category in [face_with_mask, face] and has_symmetric_landmarks: # 左右眼、左右嘴角等关键点索引互换 landmarks landmarks[:, symmetric_index_map, :]这个映射表必须在项目一开始就定义好比如symmetric_index_map把左眼球索引和右眼球索引互换。否则后加的类别越多漏掉对称映射的坑就越深。其它增强手段我这边按重要性排序大致是随机裁剪配合缩放、亮度/对比度抖动、模糊、HSV 扰动、随机仿射。随机裁剪很关键它等价于给模型提供更多尺度变化的人脸亮度抖动在 ISP 管线里等价于模拟不同曝光模糊则模拟运动场景。但也要注意在 MCU 上用 int8 量化部署后如果训练时的颜色空间和数据增强跟部署时的 ISP 输出差异太大精度会进一步下降。所以增强强度要克制尤其颜色扰动不要拉得太极端否则模型学到的“鲁棒性”在板子上根本不存在。3.3 新类别样本量级怎么估算具体多少样本算够没有绝对数字但可以给一个经验参考。新增一个与“人脸”强相关的类别我建议至少准备 5000 到 10000 张带标注的图片其中要保证不同人、不同角度、不同光照的分布。如果连 1000 张都没有那别急着训练先解决数据问题。另外一个非常有效的办法是把新增类别做“合成数据”把公开的人脸 mask 素材叠加到已有的人脸数据集上一次性生成几万张“带口罩人脸”。合成数据虽然跟真实场景有 gap但可以在训练初期让检测头先学会“口罩人脸”的基本特征然后再加入真实数据微调收敛速度快很多最终精度也会比直接硬训好不少。4. 训练策略冻结、微调与全量训练的取舍4.1 不要让模型从零开始理解什么是人脸扩展训练最大的优势是你手里通常有一份官方或社区的 BlazeFace 预训练权重。这份权重里的 backone 已经学会了非常鲁棒的人脸纹理、轮廓、局部特征表达这是从零开始训几百个 epoch 都未必能学到的。所以我的第一步是加载预训练权重但把类别分支的权重去掉因为它的输出通道数变了直接加载会报形状不匹配接着冻结 backbone 和关键点分支只训练新类别分支。这一步的目标很明确让新类别分支在旧特征空间里先建立“哪些 anchor 是人脸”“哪些 anchor 是口罩人脸”的映射关系。训练个 10 到 20 个 epoch观察 loss 降下来后再决定要不要解冻。很多开源代码里在加载权重时会遇到strictTrue报错原因就是类别分支的权重键对不上。处理方式很简单参考下面的写法state_dict torch.load(blazeface.pth, map_locationcpu) for k in list(state_dict.keys()): if classifier in k or cls_head in k: del state_dict[k] # 丢弃旧的类别分支 model.load_state_dict(state_dict, strictFalse)这里注意strictFalse会容忍缺失的键但如果你还有其它写错名字的层它也会静默通过。建议打印一下missing_keys和unexpected_keys确认只是类别分支被丢弃而不是整个网络都随机初始化了。4.2 分阶段训练的具体做法我的训练节奏大概是这样的阶段一新头热身冻结 backbone 和关键点分支只更新类别分支。学习率给 1e-3 左右batch size 在显存允许范围内尽量大。因为新头输出通道数变多初始权重又是随机的梯度会比较大lr 太高容易把整个检测头带偏。阶段二解冻深层解冻 backbone 的最后两个 block与类别分支一起微调学习率降到 1e-4。这一阶段让高层语义特征针对新增类别做适配。关键点分支我通常保持冻结因为 6 个关键点的特征表达是人脸通用信息没必要因为新增类别而重新学。阶段三可选全局微调如果阶段二结束后某个类别的 AP 还是偏低我会解冻更多层但学习率继续降一个量级同时加大正则强度。全局微调不是越多越好的它对数据量的要求非常严苛数据不够时容易把预训练学到的好特征冲掉。整个过程我用的是 AdamW 优化器weight decay 设到 5e-4。损失函数上类别分支用 Focal Loss因为新增类别样本占比通常远低于背景和普通人脸Focal Loss 的alpha参数可以抑制大量 easy negative 的梯度贡献框回归分支和关键点分支继续沿用 Smooth L1关键点分支如果发现大误差点很多可以换成 Wing Loss它对小中误差区域的梯度形态更友好人脸关键点这种任务实测提升明显。4.3 评估时不要只看整体 mAP嵌入式模型训练完我们最关心的往往是部署后的漏检和误检。所以我评估时会单独统计每个类别的 AP、召回率以及关键点的平均归一化误差。尤其要对比“普通人脸”这个老类别在扩展前后的 AP 变化。如果老类别 AP 掉了说明新类别样本把原有特征空间挤占了这时候优先回退到“只训练新头、冻结 backbone”的配置而不是盲目增加训练轮数。一个很常见的操作是把关键点分支的 loss 权重调小一点比如从 1.0 调到 0.5让检测头有更多精力去区分不同类别。如果你在训练时发现验证集 loss 降不下去先检查数据标注有没有冲突。我曾在数据清洗时发现一批“带口罩人脸”的标注框居然把整张脸和口罩一起框进去了而另一批只框了口罩覆盖区域模型看到同一类目标有两种尺度的标注置信度始终上不去。5. 部署链路从 PyTorch 到 STM32N6 NPU 的关键一跃5.1 ONNX 导出留哪些算子给 CPU留哪些给 NPU训练完成后的第一件事是把 PyTorch 模型导出为 ONNX。导出本身不难但“能导出”和“能被 STM32N6 的 NPU 加速”是两码事。我的经验是导出时固定输入分辨率不要把 dynamic_axes 打开。STM32N6 这种嵌入式目标输入尺寸固定是常态动态 shape 会引入一堆非定值算子Cube.AI 工具链解析起来非常痛苦稍不留神就把推理速度拖下来。dummy_input torch.randn(1, 3, 128, 128) torch.onnx.export( model, dummy_input, blazeface_extended.onnx, opset_version16, input_names[input], output_names[bbox, landmark, cls], dynamic_axesNone # 固定shape不要开动态 )导出后我一般会先用 onnxruntime 跑一遍对比 PyTorch 输出误差在 1e-4 量级就算合格。之后扔进 STM32Cube.AI 的转换工具它会报告哪些算子被分配到 NPU哪些算子落到了 CPU。像 anchor 解码、NMS 这类后处理我根本不会放进 ONNX 模型里它们涉及大量动态 shape、循环和排序操作NPU 不支持硬塞进去只会被丢到 CPU 上跑还不如直接在 STM32 工程里手写。简单说模型只负责“从图像到原始预测张量”其余 decode 和 NMS 全在 MCU 端用 C 实现。5.2 INT8 量化校准集必须包含新类别样本STM32N6 的 NPU 主要跑 int8所以量化这一环绕不开。STM32Cube.AI 工具链支持训练后量化PTQ和量化感知训练QAT。我在这个项目里优先尝试 PTQ因为它不需要重新训练流程最短。但 PTQ 的校准集选择直接影响精度很多人在这一步吃了大亏校准集全用普通人脸量化出来的模型对新增的“口罩人脸”类别预测结果严重漂移。因为量化缩放因子是根据激活值的统计分布确定的如果校准数据里没出现过口罩人脸这一类样本某个中间层的激活范围就可能被估小了int8 表示时数值溢出或截断类别分支直接输出一堆乱码。我的做法是把校准集按类别分布均匀采样每类至少 200 张总共 1000 张左右覆盖不同光照和角度。校准过程同时观察每一层输出的 min/max如果发现某个层的动态范围异常大比如因为背景杂乱或高光人脸导致极端激活值可以考虑对输入做归一化限制或者在训练时加强数据增强让中间层输出更稳定。还有一个偷懒但有效的办法如果 PTQ 后某些类别 AP 掉得厉害直接上 QAT。QAT 会模拟量化噪声训练几个 epoch虽然工程上多了几步但综合成本往往比反复调校准集低。5.3 部署到 STM32N6内存、DMA 与安全区那些事模型转换完成后最终要嵌进 STM32N6 的工程里。这一步我个人最大的体感是模型代码只是整个工程的一小部分真正花时间去调的是内存布局和数据搬运。STM32N6 上存在“应用安全区”和“应用非安全区”的概念这是硬件层面的 TrustZone 隔离。NPU 推理用的输入输出缓冲、DMA 搬运的内存、以及供 Cortex-M55 做后处理的临时 buffer分配在哪个区、能否被 NPU 直接访问、会不会触发总线错误这些都要在工程初始化阶段就理顺。我之前犯过的一个错是把模型输出的显存缓冲区放在了安全区结果非安全区的后处理代码一访问就触发 fault排查了大半天才定位到是内存归属的问题。所以部署时第一件事就是画清楚整条链路上每一块 buffer 的归属、大小、生命周期。输入侧还要关注 ISP pipeline 的配合。STM32N6 的 ISP 输出格式、分辨率、对齐方式跟模型要求的输入不完全一致需要在 CPU 端或硬件加速器上做一次格式转换和缩放。这里有一个常见优化如果 ISP 能直接输出模型输入分辨率比如 128x128 或 192x192 的 RGB 图就不需要额外做一次完整图像的缩放省掉的 CPU 周期非常可观。如果 ISP 输出分辨率很高那至少要在裁剪 ROI 之后再缩放不要全图先缩一遍再做检测。后处理侧的速度优化也很关键。在 STM32N6 上我建议把 anchor 解码、边界框反算、关键点反算全部用定点运算不要用浮点否则 Cortex-M55 跑起来占用太高。anchor 相关的 stride、中心点坐标、先验宽高都可以预先算成定点查表甚至直接在工程里生成常量数组。NMS 里大量用到的排序可以用插入排序或计数排序这类简单算法数据集类别少比如 3 类、anchor 候选也不多排序开销完全可控。6. 实测数据与优化技巧扩展后的帧率、内存与稳定性6.1 一组有参考价值的性能数字先给一组我在类似硬件配置下跑出来的实测数据配置不同会有差异但量级可以帮你心里有个底。模型输入分辨率是 128x128anchor 总数保持原版 896类别数从 1 增加到 3关键点仍然是 6 个。模型 INT8 量化后大小大概在 600 到 800 KB 之间。STM32N6 跑到 30 FPS 左右是没问题的如果类别变成 5 个输出通道数继续增加但推理时间增加有限瓶颈反而容易卡在 CPU 端后处理上。RAM 峰值占用一般在 1.5 MB 到 3 MB 之间主要取决于 NPU 中间 buffer 的大小和外部 DDR 的分配策略。配置输入分辨率类别数模型大小推理耗时峰值RAM原版 BlazeFace128x1281~450 KB~15 ms~1.2 MB扩展 3 类128x1283~600 KB~17 ms~1.5 MB扩展 5 类128x1285~750 KB~19 ms~2 MB类别扩展后推理耗时增加并不多因为计算量主要来自 backbone 的卷积层检测头的参数量在整个模型里占比很小。真正需要警惕的是内存——每多一个类别类别分支输出的张量就多num_anchors * 4个字节int8看起来不多但如果后处理把类别得分先转成 float再存一个副本内存就会悄悄膨胀。我的建议是解码时直接用 int8 存分数只在比较阈值时临时转 float不要把所有输出一次性转成浮点数组。6.2 后处理里最容易出错的两个细节第一个坑是 anchor 解码时用错变量类型。ONNX 导出的模型输出的是“相对 anchor 的偏移量”不是最终坐标。偏移量要跟 anchor 的中心点、宽高一起反算反算公式我之前写错过一次把exp(w)写成exp(h)结果框全部变成竖长条压测时才发现。强烈建议在 PC 端先用 Python 把解码逻辑写成脚本跑通后把同样的定点逻辑搬到 STM32C。第二个坑是多类别 NMS 的阈值设置。原版人脸检测的置信度阈值通常可以设到 0.5 甚至 0.6因为“人脸”类别的训练样本非常充足置信度分布很尖锐。但新增类别样本少置信度普遍偏低如果沿用同一个阈值新类别的召回率会很难看。我最后的做法是给每个类别单独配置阈值比如普通人脸 0.5、带口罩人脸 0.4并在模型部署配置里做成可调参数。后期现场调优时不需要重新编译模型只改阈值配置就能快速平衡误检和漏检。6.3 如果帧率仍然不够从这三个方向动手扩展完类别后如果发现帧率掉了普遍原因不是模型变大了而是 pipeline 的其它环节被拖垮。按我排障的经验优先级是先查 CPU 端的图像预处理再查后处理实现最后才怀疑 NPU 推理时间。一个很典型的现象是模型只用了 15 ms但 ISP 出来的 1080p 图在 CPU 上缩放、颜色转换花掉 40 ms帧率直接崩到 15 FPS。优化手段包括让 ISP 直接输出带 ROI 的裁剪图减少缩放面积格式转换尽量做“两步缓存”不要让 CPU 一边读 DDR 一边写 DDR能走 DMA 就走 DMA颜色空间转换用查表法或 Ne10 这类 DSP 加速库后处理解码用定点运算NMS 用简化排序避免在 C 代码里写std::sort这种重函数。另外一个方向是减小输入分辨率。128x128 降到 96x96NPU 推理时间能省 30% 以上代价是检测小目标能力下降。如果实际场景中人脸距离摄像头不远、尺寸比较大这个取舍很划算。用 96x96 输入配合 ISP 的 ROI 裁剪很多场景下帧率能直接从 20 FPS 提到 30 FPS 以上。6.4 不要迷信“调快阈值”能解决现场问题最后说一个经验教训。模型部署完现场测试发现误检偏多我们一开始的方法是调高阈值效果立竿见影当时还觉得挺聪明。结果到第二天阴天环境光照一变大量“带口罩人脸”直接漏检业务方拿着对照视频找过来才发现把阈值调高只是在“掩盖特征区分度不足”的问题。真正有效的做法是回到训练侧补充阴天、逆光、暗光场景的数据再微调几个 epoch阈值只在最后做微调不能把宝全押在阈值上。这也是嵌入式和纯 PC 视觉一个很大的不同现场环境变化会让训练时没见过的分布直接暴露出来能提前做的场景模拟一定要提前做省得后面擦屁股。如果你也要做类似的扩展我最大的建议是别把“增加类别”当成一次简单的输出层改动从数据、训练、量化到后处理每一步都要围绕新增类别重新验证。把老类别的水位稳住把新类别的数据做大做杂把部署后的性能预算提前算清楚这条路就能走通。尤其是数据这关很多人觉得检测头改改就行实际上最后决定成败的大概率不是网络或量化而是你手里的标注数据到底覆盖了多宽的真实场景。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价