
1. 这个项目想解决的真正问题异常检测不是“多了一个模型”项目做完已经有一段时间了。经常有人问我用 Nano edge ai 做异常检测和拿普通摄像头录像到底有什么区别我的回答总是区别不在于分辨率而在于“检测”让录像从被动记录变成了主动判断。先说这个项目针对的场景一个无人值守设备间里有一台长期运行的机柜指示灯在正常情况下有固定的亮灭规律柜门偶尔被打开也可能有非相关物体出现在镜头前。过去这种场景的通用做法就是录满一周视频出问题之后再去翻录像。现在我希望异常发生后的几秒内就能在本地得到判断哪个指示灯不对、镜头前多了一个目标、柜门状态是否异常。当时我手里正好有一块 Nano edge ai 开发板4GB内存功耗不高放在设备间的角落很合适。而且这类板卡自带推理加速硬件模型只要转换对了速度提升空间非常大。这个项目名字叫“基于Nano edge ai的异常检测小项目”但真的做完之后发现异常检测的难点从来不在模型而在于你如何定义“异常”以及如何让检测结果稳定可复用。1.1 一个不算复杂的现场无人值守设备间这个设备间的部署路径是摄像头固定在一个对角位置视野里有一台机柜、一个配电箱入口、一块操作面板。正常情况下画面构成基本不变图标、面板位置都是稳定的。一旦出现下列情况之一我就认为“异常发生”对象不应该在画面中。比如有人进入画面、有杂物放下、有工具掉落。应该亮着的指示灯不亮或者应该熄灭的指示灯却亮了。操作面板上的数字跳出正常范围比如温度超过阈值。这种异常检测具有很强的现场属性。工业流水线上的异常通常比较单一像这种“什么都有可能发生”的监控场景反而是决定技术路线时最先要处理的问题。如果拿一个通用物体检测模型直接对每一帧做80类目标识别会发现完全不匹配你不需要识别“人、自行车、狗”只需要回答“这画面跟正常状态像不像”。1.2 为什么选择边缘端推理而不是先把视频传回云端很多人的第一反应是这种判断为什么不直接用云服务我把两者做了实测对比。先算带宽。设备间使用的是边缘路由上行带宽很多时候只有几百KB到1MB/s长时间推流并不现实。最常出现的1分钟短视频压缩后大约十几MB发给后端已经需要10秒以上。如果在本地做检测只把触发事件的5秒片段传给后端数据量可以缩小到原来的1/20左右。再算延迟。在 Nano edge ai 板卡上一帧从摄像头读取到模型输出结果大约是20到30毫秒如果走云端考虑弱网排队即使一切顺利也需要400毫秒以上。对“延迟不敏感”的异常检测其实400毫秒也能接受但弱网断开时本地依然可用是云方案给不了的保障。最后是隐私。设备间画面虽然不涉及个人隐私但使用者不希望任何录像持续上传外部网络。录像大量留在本地只上传事件截图这种“最小化上传”在部署时几乎成了硬性要求。所以整个项目的基调从一开始就确定模型必须部署在 Nano edge ai 板子上云端只做辅助。2. 异常检测的方案选择先想清楚“异常”到底是什么形式这个项目最值得写下来的不是代码而是方案设计时的一次次取舍。异常检测这个名词看着简单真正落到一块只有4GB内存的边缘板上会逼你把每一步都想清楚。2.1 两条主流路线的适用性差别异常检测在技术上有两条非常不一样的路。第一条是有监督检测或分类。你要先把“正常”和“异常”整理成可训练的标签然后让模型学会区分。优点是指标明确、结果好解释缺点是异常样本很难收集。第二条是无监督重构典型实现是自编码器。训练时只给正常画面让网络学习这类画面的压缩规律推理时如果画面无法被重构说明它不是正常样本。优点是异常样本收集成本极低缺点是解释性差且很依赖阈值校准。如果你要复现类似项目我建议先用自己的数据做个实验正常画面100张、异常画面10张。目标检测在有10张异常图的情况下依然可以训练因为每张图有几个异常框相当于几十个正样本而自编码器面对设备间这种“新物体出现即可感知”的场景重构误差往往表现得不够直观。但我最后仍然放弃了自编码器路线。原因不是效果而是板端调参太痛苦。自编码器最常卡住的地方是重构误差阈值这个阈值会随亮度改变、随季节变化、随摄像头位置微小偏移而漂移根本没有一个稳定的参考。目标检测则不同只要置信度卡得合理输出非常直观模型给出矩形框你可以用框的位置和大小做后续判断。2.2 单类目标检测方案到底怎么定项目最终把任务收敛成了单类目标检测类别只设一个异常目标。正常情况下目标检测模型输出的类别是“人”“车”“狗”这种有语义的类别我这里只有一个“defect”类别。凡是画面中不应该出现的东西统一标成defect。模型不关心它具体是背包、掉落的扳手还是环境影响产生的光斑只要它和训练时见过的背景差异足够大模型就能框出来。选择单类检测还有一个原因可以在样本极少的情况下用“背景合成”来造数据。因为是单类我不需要精确区分不同异常类型只要把任意异形物体贴到正常画面上标注时全部标成defect。这让数据准备难度下降了一个量级。另外我还加了一个辅助检测维度表盘和指示灯状态。这部分单独用一个轻量分类模型对固定区域里的像素变化做判断。主模型负责“画面里多了什么”辅助模型负责“固定区域状态是否不对”。两者在板端并行推理总量仍然很轻。下面是整体方案的简单对照功能模块技术线路输出内容板上耗时画面异物检测单类目标检测异常框坐标 置信度约22ms指示灯状态检测轻量分类正常/异常约6ms综合告警规则去抖告警类型 截图小于1ms3. 数据怎么来一周正常画面与两张真实异常图很多人误以为异常检测需要特别庞大的异常库。实际上在这个具体项目里异常可以被精确制造重点反而在数据混合的方式。3.1 实际采集条件太受限我连续采集了一周的正常画面得到约1.2万张日常截图。异常画面呢只有两起真实事件一次柜门被打开一次有杂物出现在操作面板旁。如果靠真实异常来做训练根本不可能。所以训练异常样本全部是靠人工“演”出来的。我拿各种物件工具包、矿泉水瓶、纸箱、反光板放到摄像头下不同位置录成视频然后抽帧、标注。最有效的做法是分三类场景去演移动闯入。一个物体从画面边缘出现然后再从画面另一侧离开。对应柜门打开或者有人走过。静止异物。物体出现在原本完全空旷的位置上并在那里停留。对应现场遗落工具。状态变化。指示灯或面板数字发生变化。通过脚本每秒改变模拟状态制造几百组正常/异常对照。这三类正好覆盖最终业务上会看到的异常形态。用不到两周时间我得到了大约1.1万张训练图。3.2 合成数据是真正的生产力为了让训练样本更丰富我用合成方式把正常背景和异常物体做随机拼合。第一步准备一组干净的纯背景图拍摄时不放任何多余物体注意不要有倒影和动态阴影。 第二步准备几十张异常物体抠图也就是路边拍摄的、透明背景素材。 第三步用随机位置、随机旋转、随机缩放把物体贴进去同时把物体标注框生成为归一化标签。这个流程放在普通脚本里一分钟能生成上千张图。虽然合成图缺少真实光照一致性但用来做单类检测的第一版模型已经足够。合成样本还有一个好处就是能控制数量比例。我把“真实异常”和“合成异常”混合确保训练集中异常目标保持每张图0到2个的密度避免模型在训练时把“有异常”和“有物体”强行绑定。3.3 训练脚本的核心结构整个训练流程可以简化成下面这段代码重点不是框架本身而是数据管道的组织方式import random from pathlib import Path def build_train_set(background_dir, object_dir): samples [] for bg_path in Path(background_dir).glob(*.jpg): for _ in range(4): # 每张背景生成4张训练图 image load_image(bg_path) boxes [] for obj_path in random.sample(list(Path(object_dir).glob(*.png)), random.randint(0, 2)): x, y, w, h random_place_on_image(image.size, obj_size(64, 64)) blend_object(image, obj_path, (x, y, w, h)) boxes.append((x, y, w, h, defect)) samples.append((image, boxes)) return samples这样一个简单循环就能生成大量带标注的异常图。但要注意如果合成时目标永远放在同一种背景上模型就会偷懒靠背景差异去识别目标。所以我在混入背景时还会随机裁掉目标周围的一圈像素加一定程度的模糊逼迫模型学目标本身。3.4 初版训练结果模型学会了背景没学会缺陷第一次训练用了当时觉得很合适的配置只训练了60轮就停了。验证集精度居然到了0.96。我兴高采烈把模型放上板子结果在真实场景里一测除了偶尔检测出柜门其他异常全部漏掉。复盘时发现一个特别典型的坑训练集里绝大多数“异常图”都是把同一个矿泉水瓶贴在不同背景上模型很快就学会了跟“背景对比”它记住的不是矿泉水瓶而是“背景应该长什么样”。到了真实场景背景变化、光线变化它就不知道什么是异常了。解决方式是在数据混合上做文章。异常物体库里加入了十几种不同形状、颜色、尺寸同时把每个异常目标所在位置的背景随机裁掉、模糊、翻转让模型不能依赖背景差异来偷懒。这样再训练验证集精度仍然不低但真实场景的检出率明显同步了。这次经历给我留下一个判断标准模型在验证集上的分数如果异常地高先怀疑是不是训练数据本身存在“作弊通道”。4. 上板部署与性能优化从每帧120毫秒降到22毫秒训练和部署是两件完全不一样的事。如果直接拿训练机上的模型跑板卡会发现速度很难看甚至不如一台普通办公电脑。4.1 环境版本请一开始就锁定板卡上用官方系统镜像安装好后第一时间要确认两件事系统自带的Python版本以及板端推理引擎支持的模型格式。常见错误是用最新版深度学习框架训练出来的模型转换到板端格式时提示不兼容最后被迫重新训练。我的做法是先在训练机建一个隔离环境安装与板卡推理引擎匹配的版本然后再训练。版本锁定之后板端转换率非常顺利。我自己踩过的一个坑在训练机上升级了推理库版本转换后模型跑到板端前几次推理正常后面偶尔出现内存错乱。反复排查后才发现版本不一致回退到对应版本问题就消失了。这类问题不建议去查内存日志耗时且线索很少最直接的解决办法就是重新严格锁定一个版本组合。4.2 模型量化与推理加速训练好的模型权重默认是32位浮点。板端推理引擎默认把权重压缩到半精度甚至8位整数。对绝大多数异常检测项目来说8位量化会带来1%到3%的精度损失但速度提升非常明显。我实测了四种状态结果很有参考价值运行环境模型格式单帧推理耗时备注训练机 CPUfp3285ms静态图不占GPU训练机 GPUfp3212ms结果仅做参照Nano edge aifp32120ms直接搬运未优化Nano edge aifp1640ms半精度精度损失极小Nano edge aiint822ms8位量化速度最高从120毫秒降到22毫秒意味着板端的吞吐量从大约8 FPS提升到45 FPS。实际扣掉摄像头读取和绘制标注稳定在37到40 FPS。对一个固定机位异常检测而言这已经远远够用。4.3 别忽略多进程调度板卡推理引擎是并行计算但摄像头读取、图像缩放、结果显示都是在CPU上完成的。如果所有逻辑放在一个进程里推理引擎跑得快CPU排队一样能把总耗时拖回去。我的做法是把采集、推理、告警拆成三个线程采集线程只负责读取最新的一帧推理线程只负责模型计算告警线程只负责判断是否触发。采集与推理之间用一个一帧大小的环状缓冲保证永远处理最新帧不会因为某帧卡住而堆积。这套结构在项目后期特别有用。告警逻辑加重也不影响推理速度因为线程之间有队列解耦。如果你的场景里还有多个摄像头这个结构可以平滑扩展成每路一个采集线程共享同一个推理引擎实例。5. 一块电源灯亮却不出画面的板子排障全过程这是项目里最值得记录的非技术问题也最容易被新手忽略。5.1 症状开机正常摄像头没有数据装好系统、接上USB摄像头在终端里读取设备列表时能看到摄像头节点。但开始采集视频时程序会阻塞在读取画面这一步一直返回空帧。更奇怪的是板卡的指示灯全部正常系统负载正常日志里也没有明显的报错。5.2 排查链条按步骤复现第一步先确认摄像头本身在另一台电脑上能用排除了硬件损坏。 第二步换了一个USB口此时摄像头能够出画面但在持续运行3分钟后再次丢帧说明不是固定口损坏。 第三步把程序里的“自动采集线程”暂时关闭改用最简单的一帧一帧读取画面仍然只在启动后前几帧正常。 第四步查看系统运行日志发现几次视频流尝试都伴随“带宽占用已达上限”的提示。这里的带宽不是网络带宽而是USB部分解析出错导致的占用提示。 第五步用排除法关闭板卡默认的某个系统服务后摄像头立刻恢复稳定。再启动该服务问题复现。原因锁定。真正的坑在于这个默认服务会周期性检查外部设备并触发一次短暂的重新枚举摄像头在那一瞬间被整条总线一起断开。解决方法是把这个服务设置为不随系统启动并在采集脚本启动前做一次设备探测。这类硬件兼容问题不会出现在缩短的验证流程里但实际部署时非常常见。5.3 修复后的验证修复之后我连续跑了一周期间没有再出现丢帧。每次摄像头断电重启后需要在脚本里触发一次重新握手代价很小但稳定性提升明显。这段排障说明了一个经验边缘推理项目里硬件的坑往往会比模型的坑先出现。不要一拿到板子就直接跑算法先用最简单的采集示例长时间运行一晚把硬件稳定性问题解决掉再进入模型调试。否则后面很难判断是模型问题还是硬件问题。6. 告警模块从一帧检测到“可以执行动作”的判断检测模型输出的是一个个带置信度的矩形框但它们不是直接触发告警的开关。模型和告警之间一定还需要一层业务逻辑。6.1 连续帧去抖异常检测误报主要来自三种情况灯光闪烁、镜头对焦瞬间模糊、移动物体快速经过时模型给出半置信度框。单帧置信度大于0.5的框如果直接触发一晚上会收到几十条假告警。我采用的方法很简单在内存里维护一个长度为7的滑动窗口每个检测框进入窗口后只有当连续3帧都出现且框的中心位移小于原框宽度的20%时才确认。这样既保留了对短暂异常比如快速闯入的响应能力又过滤掉了绝大部分随机误报。6.2 告警时的动作设计确认异常后我设计了三个动作保存告警截图记录前后各2秒视频。写本地告警日志包含时间戳、检测框坐标、置信度。通过 HTTP 请求发送一条通知给后端服务后端再决定是否需要推送。一开始某些场景会把设备间正常巡检也误报因为里面走动的对象会造成运动。后来我把告警逻辑做了拆分移动闯入告警与指示灯状态告警分开两种告警去抖策略不同。指示灯状态这种静态异常用更长的持续确认窗口移动闯入则用短窗口避免巡检人员每次路过都触发。6.3 关于阈值不建议一味调高在调优阶段我试过把置信度从0.45提升到0.75告警次数明显减少但漏检率同步上升。后来采用的方法是识别阈值保持在0.5不变但针对具体区域单独加入“敏感区”规则。比如重要按钮所在的固定区域只要检测框与区域交并比大于0.3就用更低阈值0.35做复核。这种方法比全局调阈值要有效得多。这部分的经验是异常检测不是一次到位的事情它需要和现场一起演出来。模型交付后还要通过一个星期的真实运行来统计误报比率根据日志再反向调参。一个能上线跑的异常检测系统一定不是训练完那一刻就稳定而是现场数据反馈回来的第2到第3周才稳定。7. 项目复盘异常检测最重要的经验清单最后整理一小份复盘清单方便直接“抄作业”。7.1 不用追求“通用的异常”要追求“现场可解释的异常”我见过太多项目栽在尝试预测所有异常类型上。边缘端算力有限数据也有限能解释的肯定是先被定义的。先把场景里的异常划分为移动闯入、静止异物、状态变化三类再决定用哪个模型这个顺序不能反。7.2 数据比例比模型参数更容易成为瓶颈如果异常样本只有几十张不要急着上大模型。先把样本扩充到几千张再用轻量模型训练效果反而比强模型配小样本好。尤其是合成数据一定要控制好背景多样性和目标多样性的比例。7.3 部署环节至少预留一周时间训练出一个模型只需1到3天但环境适配、端口调试、告警去抖往往才是消耗最大的一环。不要低估现场环境的差异。花一个晚上跑硬件稳定性测试是非常划算的投资。7.4 关于后续扩展这个项目后续如果要扩展我会把单类检测扩充成“当日状态热力图”用长时间统计结果反推设备间活动规律。另一个方向是接入更多路摄像头把多路推理结果聚合到一个统一看板里。但在那之前当前这套“采集、推理、告警”的基础链路已经足够让人快速进入边缘AI实战状态。如果让我重新做一次我仍然会优先保证这条链路完整再去追求更花哨的模型结构。