2026/10/17 3:31:46

零样本安防异常检测实战:巧用大模型将F1从0.09提升到0.64

零样本安防异常检测实战:巧用大模型将F1从0.09提升到0.64 做安防视频算法这些年我们很少在项目里主动提“零样本”这个词。客户要的无非就是“某某区域某某事件的准确率达到多少”背后的常规动作是标注、训练、调参、再迭代。但最近接了一个需求情况不太一样某园区要在短时间内给几条非机动车道和社区出入口的存量摄像头做异常事件预警事件类型包括人员倒地、车辆逆行、物品遗留、人群聚集。按传统路线要么人工采集标注一批数据训练一个专用模型要么直接找供应商买算法包两条路的时间都不乐观。于是我们决定试一条没怎么验证过的路直接用现成的多模态大模型做零样本异常检测。结果第一轮基线测试F1只有0.09基本等于瞎猜。后来花了两周时间把Prompt、输出格式、抽帧策略、目标裁剪、阈值校准整个链路重调了一遍F1慢慢被顶到0.64。整个过程踩坑不少今天把细节整理出来给准备在安防场景里用大模型的人做个参考。1. 一个“零样本”需求和一次不怎么好看的摸底1.1 为什么放着成熟路子不走要碰零样本安防异常检测有个很讨厌的特点真实场景里的异常事件是典型的“长尾分布”。人员倒地、车辆逆行这类事件一个点位一个月可能发生不了几次。要按传统方式训练一个专属模型光样本采集就很头疼得蹲点采集、翻历史录像、手动框选异常片段再做数据增强。而且不同点位的光照角度完全不同A点位的模型拿到B点位往往直接掉点每个点位都需要单独积累数据。对比一下三条路线差距就很清晰方案数据需求开发周期泛化能力主要瓶颈传统定制训练每个点位数百至上千张标注4到6周左右单场景精度高换场景易掉样本采集标注成本高购买标准算法包基本无1周内部署覆盖常见异常类别长尾定制类事件漏报明显大模型零样本不需要标注1到3天完成联调语义理解强类别可灵活切换输出稳定性、精确率需要额外调优客户这边给的时间窗口只有一周传统定制肯定来不及。算法包我们也评估过标准事件类别里没有“物品遗留”这种细粒度定义厂家说可以定制但报价和交付周期都谈不上理想。相比之下多模态大模型的优势正好打在长尾和快速响应上只要把事件描述写成自然语言模型理论上能理解“人躺在地面上”“包裹长时间无人取走”这类语义不需要为每个点位准备样本。当时团队内部也有反对意见核心顾虑是大模型输出不可控容易“一本正经胡说八道”用在安防这种需要可靠性的场景风险很大。这个顾虑后来被验证了第一轮测试就给了我们一记重锤。1.2 第一轮基线F10.09和“瞎猜”差不多基线测试我们做得比较快。选了该园区三个点位的历史录像抽了大约2000帧画面有异常片段也有正常片段标注好真值。推理侧直接部署一个开源多模态大模型输入就是单帧截图问题很简单“请判断这张图中是否存在异常事件回答是或否。”跑完统计结果惨不忍睹。整体76%的画面被模型判为“正常”其中真正包含异常的画面里只有少部分被正确命中同时还冒出了大量误报。模型经常输出“画面中人群拥挤存在安全隐患”“地面湿滑建议关注”这类话但业务上我们根本没有“地面湿滑”这个事件类型。把输出落成二分类标签计算F1只有0.09。这个结果我们需要拆解一下不是说模型笨而是我们的用法本身就有问题提问方式太开放。“有没有异常”天然暗示模型去当“安全提醒器”它会把一切稍微有点潜在风险的事情都指出来而不是聚焦到客户要的几类业务事件。全局图分辨率对小目标不友好。监控画面里一个人摔倒可能只占图像宽度的十分之一远处更小。大模型对整图做视觉编码时小目标特征被严重压缩模型确实“看不见”。事件定义不一致。我们说的“人员倒地”模型理解成了“人在地上”但业务要求的是“异常跌倒且身体贴近地面”模型没有收到这么细的约束。第一轮测试至少说明一件事零样本本身不是问题问题出在“怎么把业务知识翻译成模型能理解的任务描述”。F10.09一点都不可怕可怕的是不知道差在哪、也不知道下一步改什么。接下来要做的就是诊断清楚了动手调。2. 从“零样本”到“能用”Prompt与输出格式改造2.1 第一版Prompt踩了两个坑太开放、太自由第一次改进Prompt我们按当时市面上的最佳实践写了一个很“完整”的版本你是园区视频监控的安全分析员。请判断画面中是否存在异常事件。 异常事件类型包括人员倒地、人群聚集、车辆逆行、物品遗留、烟雾火焰、打架斗殴。 回答格式先输出“是”或“否”如果是再补充一句异常描述。 只描述画面中的可见事实不要推测不要编造。看起来没什么毛病但实测下来问题不少。首先是回答“否”的概率依然很高因为模型倾向给一个“安全”的答案哪怕画面里确实有人躺在地上它也会加上“可能是休息”“暂时无法判断”这类对冲性表述。其次是输出内容完全自由我们只能靠正则去扒关键词一旦模型换一种说法比如把“人员倒地”写成“有个人躺着”规则就接不住。这个阶段给我最大的教训是对大模型来说改了Prompt相当于改了模型本身。你不能指望它“理解”业务你得把业务翻译成一套它能稳定执行的指令。分析类输出对安防场景尤其不适用模型给出的推理过程经常是“因为有个人躺在地上所以可能是倒地事件”看起来合理但它没有给自己设定置信度我们也无法区分它是“看到了”还是“猜到了”。第一版Prompt还有个小细节其实对大模型影响特别大事件类型的排列顺序。模型对列表前面的项目会更敏感。我们一开始把“人员倒地”放在第一位结果是凡是有人坐地上、蹲地上、躺地上都会触发该类别误报集中爆发。后来把列表顺序按“画面特征显著性”重新排显著特征强的类别放前面——比如“烟雾火焰”的视觉特征很突出反而后面误判率低一些。2.2 结构化输出让模型写“数据”而不是写“作文”第二版Prompt我们彻底放弃让模型“自由发挥”改成强制走结构化输出。核心思路很简单Prompt里明确要求模型按JSON格式返回结果并且我们在代码层做Schema校验。模型输出不合法就重试重试还失败就按低置信度处理不参与后续判断。最终用的Prompt模板大概长这样你是园区视频监控分析系统的事件识别模块。 输入一张监控截图按以下步骤输出 1. 场景要素简要描述画面中出现的行人、车辆、物品、区域不要评价是否安全。 2. 事件判断对照下列业务事件列表逐项判断 - 人员倒地人物呈倒地姿态躯干明显贴近地面。 - 人群聚集多人在同一区域长时间聚集密度明显高于正常通行。 - 车辆逆行非机动车或机动车在单行方向反向行驶。 - 物品遗留包裹/箱体/背包等在画面中静止超过一定时间。 - 烟雾火焰画面中出现明显烟雾或明火。 若某项成立输出事件名称和一句事实描述若都不成立输出空列表。 3. 对每个事件输出目标在画面中的坐标范围 (x1,y1,x2,y2)。 4. 输出置信度 confidence取值 0 到 1。 只允许输出JSON不要输出其他文字。模型侧参数同步调整。temperature设成0.1基本关闭随机性do_sample关掉每次推理结果尽量一致max_new_tokens限制在256以内防止模型写作文把JSON截断。这些参数每个都很关键尤其temperature不调到足够低同一个画面模型很容易给出“是”和“否”两个版本的结果在异常检测里这是不可容忍的。配套的JSON Schema长这样{ scene: string, events: [ { type: string, description: string, bbox: [0, 0, 0, 0], confidence: 0.0 } ] }真正的变化发生在业务代码里我们以“事件名称是否在业务白名单内”作为第一层过滤器。模型输出“危险”“风险”这类词一律不纳入候选只有输出“人员倒地”“烟雾火焰”这样确切的事件名才进入精细化判断。再配合bbox和confidence字段让“判断逻辑”从模型手里接回规则手里这一步对大幅提升精确率有决定性作用。2.3 消融结果F1是怎么从0.09爬到0.36的同样是2000帧验证集只改Prompt和输出解析其它都不动结果变化非常明显版本召回率精确率F1开放问答基线0.170.060.09业务枚举结构化JSON0.310.440.36可以看到召回率提升有限但精确率翻了好几倍。这符合我们的预期结构化输出加白名单过滤主要解决的是“模型乱说话”的问题。那些“地面湿滑”“存在隐患”之类的误报被规则直接滤掉整体F1自然就上来了。不过这个阶段我们也很清楚0.36距离可用还差得远。漏报方面小目标的问题依然严重全局图上低分辨率的行人、远处树丛里的烟雾模型还是识别不到。时序方面单帧判断顶多算“疑似异常”连续多帧稳定输出才能说明真的发生了事件。所以下一步必须把“看图”升级成“看视频”。3. 把单帧变成片段时序聚合让模型“看到”视频3.1 抽帧、滑动窗口和聚合规则单帧推理有一个天然缺陷监控画面中存在大量因压缩噪声、角度变化造成的伪异常信号。比如车辆灯光在镜头里一晃某一帧看起来就像逆行人从蹲姿起立的过程中间帧可能被误判成倒地。解决这类问题单靠Prompt是不够的必须引入时间维度的二次判断。我们的做法并不复杂本质上是抽帧加滑动窗口投票。流程如下录像按0.5秒间隔抽帧存成JPEG减轻视频解码和后续传输负担。构建滑动窗口窗口长度取10帧即一个窗口覆盖5秒的内容。步长设为5帧相邻窗口有50%重叠避免事件正好卡在窗口边缘被漏掉。窗口内每一帧分别送大模型做结构化推理。对同一个事件类型做聚合统计只有当“事件出现帧数≥3”且“平均置信度≥0.5”时才判定该事件成立。为什么取0.5秒而非逐帧处理两个原因。一是推理成本的考虑逐帧送入模型意味着每路视频的计算量翻倍二是相邻帧的画面信息冗余度极高在0.5秒的粒度上人员倒地、车辆逆行这类动作特征不会丢。实际操作中我们还加了简单背景差分脚本做预过滤连续两帧若像素级差异极小则跳过该帧不送模型。这个预处理大约能减少30%左右的无效推理对批量处理场景非常划算。聚合规则里有一条很多人容易忽略事件不能是“断断续续”的。比如某帧被判为人员倒地但中间隔了四五帧全是正常这种大概率是模型误报或目标短暂遮挡。我们要求符合条件的事件帧必须是邻近连续的比如窗口内第3、4、5帧都输出了该事件分散在第1帧和第9帧各出现一次不算。这个约束能有效过滤掉随机误报。3.2 目标裁剪二次确认让模型看“该看的地方”时序聚合解决了时间维度但空间维度的“小目标”问题还没解决。我们测试时发现一个画面宽度只有5%的行人倒地大模型即使注意到了bbox也经常框不准置信度给得也很犹豫。更离谱的是有些帧模型直接说“未发现人员”。这不是模型看不懂“倒地”是它在全局图上根本看不清这个小人。于是我们引入了目标裁剪二次确认的流程在大模型推理之前先用一个通用目标检测模型把画面中的人、车、包、箱体等主要目标框出来。对每个目标框区域做裁剪然后把裁剪后的局部图再送大模型判断。相当于先给大模型一个“视觉注意力提示”让它把眼睛放在目标区域内而不是在整张图上漫无目的地找。这个流程有几个细节必须注意目标检测模型只负责“找目标”不负责判定事件。它输出的候选框会包含很多正常行人车辆。这些候选框裁剪图会全部送入大模型由大模型判断该目标是否处于异常状态。裁剪图不能太小。如果检测框宽度小于96像素直接跳过因为放大后图像已经模糊到没有判断价值。当然这会带来漏报但摩擦成本是可控的。当检测框较大、占到画面比例超过50%时不再裁剪直接用原图避免裁剪后丢失上下文信息。安防场景如果涉及人脸区域在送入大模型前必须做人脸模糊处理。这个不是技术问题是合规底线问题尤其是云端模型隐私保护要做到前面。加了这个环节之后召回率有了肉眼可见的提升。之前全局图里漏掉的小目标裁剪后大模型基本都能给出判断。像“物品遗留”这种事件原本模型经常把包和背景混在一起裁剪后目标边界清晰误报和漏报同时减少。3.3 F1到0.54之后发现代价也变大了加入时序聚合和目标裁剪二次确认后整体F1从0.36升到了0.54。进步是实打实的但代价也在慢慢凸显。首先是推理延迟。当时我们用的多模态大模型单帧推理大约要0.8秒一个5秒窗口有10帧就需要8秒左右。再叠加目标检测、预过滤、结果聚合单窗口处理耗时接近10秒完全不具备实时预警条件。所以这个阶段我们明确了一个定位这套方案只能用于离线批处理或者在线场景下大幅降频的轮询不能指望它像传统算法一样跟视频流同步跑。其次是误报形态发生了变化。之前误报集中在“模型乱说”现在变成了“事件被判对但业务上不成立”。最典型的是人员倒地画面里有人坐在花坛边上休息姿态确实低矮连续多帧都被模型判定为“倒地”因为模型无法判断“这个人是在休息还是在摔倒”。这个问题让我意识到零样本大模型在语义理解上很强但业务规则的精细化程度还远远不够。后来我们在Prompt里补了一句“倒地姿态需包含躯干明显贴近地面且伴随姿态异常普通人坐姿休息不算”才压住一部分误报。但这也说明Prompt本身是有极限的。对于那些依赖长时间行为模式的事件比如“物品遗留”真正的判据是“物体在那个位置停留了很久且没有人员看管”。大模型在单帧上看不出来5秒的窗口也看不出来它只能凭“画面中有一个箱子没人管”来猜。这类事件在后面章节还会提到我们最终的解决方案是靠阈值校准和人工审核闭环来兜底。4. 阈值校准、锁边界最终把F1顶到0.644.1 按事件类别做阈值校准而不是用统一阈值模型输出的confidence能不能直接当作概率用答案是绝对不能。实测统计下来模型对“有事件”的判决平均置信度大约在0.77左右而对“无事件”的平均置信度也有0.52。换句话说模型普遍“自信”如果按0.5一刀切误报会非常高。置信度本身是有类别偏置的必须按事件类型单独校准。具体做法我们分三步从验证集里按事件类型统计预测置信度的分布画出精确率-召回率曲线。注意这里不能用accuracy因为正常画面占多数用准确率做指标会被正常样本淹没。在PR曲线上找每个事件类型的F1最优点作为该类型的最优阈值。设置“低置信重判”规则如果一个候选事件的置信度低于类别阈值但高于一个更低的“观察阈值”不直接丢弃而是送入人工审核队列。经过校准后各事件类型的最优阈值和对应F1如下事件类型最优阈值该类型F1人员倒地0.400.58人群聚集0.600.51车辆逆行0.500.62物品遗留0.450.47烟雾火焰0.550.33可以看到不同事件类别的F1差异很大。车辆逆行最好物品遗留和烟雾火焰的明显差一截。烟雾火焰的识别难度在于模型对烟雾的理解容易跟背景混淆比如光影、雾气、扬尘都会被误抓。这在批量场景里特别伤因为园区里偶尔有喷雾车经过几乎每次都能触发烟雾误报。后来我们给烟雾类事件单独加了“连续窗口内火焰/烟雾区域面积变化率”的规则才算把误报压下来。把这些阈值统一应用到验证集整体F1从0.54到了0.64。这个数字在绝对意义上并不高但考虑到这是零样本场景没有标注一张训练样本已经算是一个可以交差的里程碑。团队内部复盘时的一致看法是阈值校准带来的提升主要来自“少做错”而不是“做得对”。异常检测里误报的代价往往比漏报更大因为误报会消耗安保人员的注意力多来几次假警报整个系统就会被信任为“狼来了”。4.2 剩余失败案例0.64为什么是当前上限到了0.64之后我们尝试继续往上顶但收益越来越小。对着失败样本逐帧看结论会发现剩下的漏报和误报基本都集中在三类夜间低照度场景。目标亮度低、对比度差目标检测模型在夜间召回本身就下降严重。大模型拿到的裁剪图细节严重不足出现大量漏报。目标遮挡严重。人物被栏杆、车辆、绿化带遮挡超过30%时模型能检测到“人体”但无法判断姿态倒地事件的核心判据因此缺失。事件定义的细粒度冲突。排队和人群聚集在画面特征上几乎无法区分坐着休息和人员倒地在低分辨率画面下也很难判断。这三类问题本质上是“图像信息不够”的问题不是Prompt能解决的。我们也尝试过把窗口拉长到10秒结果误报反而上升因为长时间窗口更容易把不同事件拼凑在一起。把目标检测模型换更强版本也有帮助但计算开销上涨明显性价比不高。这个阶段我个人的体会是F1到0.64以后与其继续跟模型较劲不如停下来想想怎么把这个输出质量放进业务流程里。零样本场景的价值是“快”和“宽”它的精度天花板本来就是客观存在的。真到要用时给大模型配一个“传统算法人工审核”的兜底框架才是一个可落地的完整方案。5. 部署形态、成本和那段“0.64就够了”的复盘5.1 离线批处理和在线预警的取舍最终交付时我们没有把大模型做成一个实时视频流分析服务——推理延迟摆在那单帧0.8秒的耗时做实时流分析完全不现实。整个系统拆成了两个通路离线批处理是主链路。定时任务从存储系统拉取最新的录像文件按开头说的流程走抽帧、预过滤、目标检测、大模型推理、滑动窗口聚合结果写入事件库。这个通路适合“事件发生后数分钟内有提示”的场景比如物品遗留、夜间巡逻复盘、历史录像追溯对实时性要求不高但可靠性要求高。消费级推理卡一张就够实测处理耗时大约是视频时长的1.5倍左右4路视频并行处理需要排队跑批任务放在夜间低峰时段执行。在线预警是辅助链路。实时RTSP拉流后先用轻量目标检测模型过滤画面。只有画面中存在目标且相邻帧差异明显时才把当前帧送大模型。5秒窗口聚合后推送事件。这个通路的延迟大约在20秒级别做不到秒级响应但是对“人员倒地”“烟雾火焰”这类高优先级事件20秒完全在可以接受的范围内。部署资源这块没有想象中那么吓人但也没有第三方宣传的那么轻松。一张消费级推理卡跑离线批处理4路视频并行已经需要排队了。成本大头不在模型推理本身而在目标检测预处理和目标裁剪后的大模型重复请求。一次异常检测可能要对每个目标框执行多次模型请求事件越多单路计算量越大。所以我们在前端加了一个限制只有画面全局推理已经出现“疑似事件”的帧才会触发目标裁剪二次确认否则直接跳过。这个小优化让整体推理量下降了大约40%。5.2 人审队列和反馈闭环零样本也需要“兜底”0.64的F1放在实际业务里意味着每100个真实事件里仍有相当一部分会被漏掉同时会有一些误报混进来。安防场景不能拿这个精度直接触发派单处置但我们做了一个很实用的事把大模型的输出当成“人工智能预审员”所有候选事件先走规则过滤再进人工审核队列。具体长这样Web端列出一张事件卡片每张卡片包含事件截图、事件类型、大模型描述、目标检测框、置信度。审核员只需要操作三个按钮确认、忽略、误报反馈。确认的事件自动升级为正式告警并通知安保人员忽略的事件打上“已人工排除”标记后归档误报反馈会记录触发原因回写到一个反馈表里。这个反馈闭环虽然不参与模型训练但我们会用它持续调整规则和阈值。比如某一天审核员频繁反馈“排队人群被误判为聚集”我们就可以把人群聚集的阈值调高或者修改Prompt里的聚集定义。几周运营下来这个习惯救了我们很多次业务侧的语义变了规则跟着改系统就不会跑偏。不得不承认这套方案跟传统定制训练出来的专业模型相比精度还是差了一档。但它的优势是覆盖度广、上线快、可持续迭代。我们团队后来的共识是把大模型放在传统技术栈的上游当“语义理解器”目标检测模型做“视觉锚点”规则系统做“最终决策”三者各司其职才能把大模型的能力真正用进安防场景。最后说一点个人经验。很多人听到“零样本异常检测”第一反应是“不用标注白嫖一个大模型就行”实际做下来完全不是这么回事。零样本省掉的是“标注和训练”环节但省不掉“把业务翻译成任务”、省不掉“输出质量治理”、省不掉“阈值与规则设计”。F1从0.09到0.64表面上看是Prompt和流程优化带来的本质上是在大模型外面套了一层又一层的工程护栏才让它的能力变得可控、可用。这套思路放在其他业务场景里也成立模型当引擎规则当方向盘工程化才是车轮。