1. 从“人盯屏幕”到“AI盯风险”:这套系统到底在解决什么问题
工地、厂区、化工园区这些地方,安全管理的痛点从来都不是“没有制度”,而是“制度落不了地”。我见过太多现场:安全员三班倒盯着十几路监控画面,眼睛看花了也盯不过来;等发现有人没戴安全帽、有人翻越围栏、有人违规动火,往往事故苗头已经过去了。更别提那些“事后查录像”的场景——出了事再翻几十个小时的录像,黄花菜都凉了。
“无忧安环 AI 视频分析”这个项目,核心就是用 AI 视频分析技术,把被动的事后追责变成主动的实时预警。它做的事情说起来不复杂:接入现有的摄像头视频流,用 AI 模型实时识别画面里的安全隐患——没戴安全帽、没穿反光衣、抽烟、打电话、人员离岗、区域入侵、烟火检测等等——一旦发现违规,立刻推送给管理人员,同时把违规截图和短视频存下来当证据。
这套东西适合谁来参考?如果你是工厂、工地、园区的安全管理人员,想了解 AI 视频分析到底能干什么、怎么落地,这篇文章能给你一个完整的认知框架。如果你是做 AI 应用开发的工程师,想搞清楚视频分析系统的技术选型和工程细节,里面关于模型选型、推理优化、误报处理的部分应该对你有用。哪怕你只是对 AI 视频分析这个方向感兴趣,想看看一个真实项目是怎么从零搭起来的,也能从实操流程和踩坑记录里拿到不少干货。
我前后参与过三个类似的安环视频分析项目,从最早的“传统 CV 算法硬怼”到后来的“深度学习模型 + 业务规则引擎”,踩过的坑比写过的代码还多。下面就把这套系统的设计思路、技术细节、实操流程和避坑经验,完整地拆一遍。
2. 整体架构设计:为什么不是“一个模型走天下”
2.1 核心设计思路:分层解耦,各司其职
很多人一上来就想搞一个“万能模型”,输入视频帧,输出所有安全隐患。我试过,这条路走不通。原因很简单:不同隐患的识别逻辑差异太大了。安全帽检测是目标检测问题,烟火检测是颜色纹理加运动特征的问题,人员离岗是时序判断问题,区域入侵是空间关系判断问题。硬塞进一个模型,要么精度崩掉,要么推理速度慢到没法用。
所以这套系统的架构设计遵循一个原则:分层解耦,各司其职。整个系统分成四层:
- 视频接入层:负责拉取 RTSP 视频流,做解码和抽帧。支持海康、大华、宇视等主流厂商的摄像头,也支持 GB28181 协议接入。
- AI 推理层:这是核心,跑各种检测模型。每个模型只负责一类或几类相近的隐患识别,比如“安全帽+反光衣”一个模型,“烟火”一个模型,“人员行为”一个模型。
- 业务规则层:模型输出的是原始检测结果,比如“画面里第 3 个人没戴安全帽”。业务规则层负责判断这个结果是否构成“违规”——比如这个人是不是在作业区域内?是不是在规定的作业时间内?连续多少帧都检测到才算数?
- 应用展示层:违规事件推送到 Web 端和移动端,支持实时告警、历史查询、统计报表、证据留存。
这种分层的好处是:模型可以独立升级,业务规则可以灵活配置,视频接入可以按项目现场情况调整。不会因为换了一个摄像头品牌,整个系统就要重写。
2.2 模型选型:YOLO 系列为什么成了首选
目标检测模型的选择,直接决定了系统的精度和速度。我对比过 Faster R-CNN、SSD、YOLO 系列和 DETR 系列,最后选了 YOLO 系列作为主力检测模型。原因有三:
第一,速度优势明显。安环场景对实时性要求高,通常要求 25fps 的视频流至少做到每秒 5-10 帧的推理速度。YOLO 系列在同等精度下,推理速度比 Faster R-CNN 快 3-5 倍。用 TensorRT 加速后,YOLOv8s 在 T4 显卡上能做到 200fps 以上,完全满足多路视频并发推理的需求。
第二,精度够用且可调。安环场景的检测目标相对固定——安全帽、反光衣、人员、烟火、车辆等,类别数不多。YOLO 系列在这种“少类别、高密度”场景下表现很好。而且 YOLO 提供了 n/s/m/l/x 多个尺寸,可以根据现场算力灵活选择。边缘设备用 YOLOv8n,服务器端用 YOLOv8m 或 YOLOv8l。
第三,生态成熟。YOLO 系列的训练工具链、部署工具链、社区资源都非常丰富。从标注工具(LabelImg、Roboflow)到训练框架(Ultralytics),再到部署方案(TensorRT、ONNX Runtime、OpenVINO),整条链路都有成熟方案,不需要自己造轮子。
注意:YOLO 系列版本迭代很快,选版本时不要盲目追新。YOLOv5 和 YOLOv8 是目前工业落地最稳的两个版本。YOLOv9、YOLOv10 虽然论文指标好看,但部署工具链和社区支持还在完善中,生产环境慎用。
2.3 视频接入方案:RTSP 拉流与抽帧策略
视频接入这块,看起来简单,实际上坑很多。摄像头输出的 RTSP 流,直接用 OpenCV 的VideoCapture拉,在实验室环境没问题,到了现场就各种断流、花屏、延迟累积。
我的做法是:用 FFmpeg 做拉流和解码,OpenCV 只做图像处理。FFmpeg 对 RTSP 协议的支持更稳定,可以设置超时重连、缓冲大小、传输协议(TCP/UDP)等参数。具体命令大概是这样:
ffmpeg -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.100:554/Streaming/Channels/101" -f rawvideo -pix_fmt bgr24 -vf fps=5 -s 1280x720 -这条命令的意思是:用 TCP 协议拉取 RTSP 流,输出原始视频帧,像素格式 BGR24,每秒抽 5 帧,分辨率缩放到 1280x720。抽帧率设为 5fps 是一个经验值——安环场景不需要 25fps 全帧分析,5fps 足够捕捉到人员移动和违规行为,同时把推理压力降到原来的五分之一。
分辨率缩放到 1280x720 也是权衡的结果。原始 1080p 或 4K 视频直接送进模型,推理速度会大幅下降,而且很多小目标(比如远处的安全帽)在缩放后反而更容易被检测到,因为模型训练时用的就是类似尺度的图像。
2.4 业务规则引擎:让 AI 输出变成“可执行的告警”
模型输出的原始检测结果,比如[{"class": "person", "bbox": [100, 200, 300, 500], "confidence": 0.92}, {"class": "no_helmet", "bbox": [150, 180, 250, 280], "confidence": 0.87}],这只是一堆数字。业务规则引擎要做的是:判断这个“没戴安全帽的人”是不是在作业区域内、是不是在作业时间内、连续多少帧都检测到了、置信度是否超过阈值。
我设计了一个简单的规则配置 DSL(领域特定语言),用 JSON 描述规则:
{ "rule_name": "未戴安全帽检测", "model": "helmet_detection", "conditions": [ {"field": "class", "operator": "eq", "value": "no_helmet"}, {"field": "confidence", "operator": "gte", "value": 0.75}, {"field": "zone", "operator": "in", "value": "construction_area"}, {"field": "time_range", "operator": "in", "value": "08:00-18:00"}, {"field": "consecutive_frames", "operator": "gte", "value": 3} ], "action": { "type": "alert", "level": "high", "notify": ["sms", "app_push", "web_socket"] } }这条规则的意思是:当检测到“未戴安全帽”类别、置信度大于等于 0.75、目标在施工区域内、当前时间在 08:00-18:00 之间、连续 3 帧都满足条件时,触发高级别告警,通过短信、App 推送和 WebSocket 实时通知。
consecutive_frames这个条件特别重要。没有它,模型偶尔一帧的误检就会触发告警,一天下来几百条误报,安全员直接就把系统关了。设成 3 帧,意味着连续 3 帧(在 5fps 抽帧下大约是 0.6 秒)都检测到才算数,误报率能降一个数量级。
3. 核心细节解析:从数据标注到模型部署的完整链路
3.1 数据标注:质量比数量重要十倍
AI 视频分析系统的上限,在数据标注阶段就决定了。我见过太多项目,模型训练 loss 降得很漂亮,一到现场就各种误报漏报,根因都是标注数据有问题。
安环场景的数据标注,有几个关键点:
第一,标注类别要定义清楚。“没戴安全帽”和“戴了安全帽但帽子颜色不对”是两回事。“人员倒地”和“人员蹲下”在画面上很像,但安全含义完全不同。标注前必须把类别定义写成文档,所有标注人员统一标准。我通常会做一个“标注手册”,每个类别配 5-10 张典型图片,标注人员先试标 100 张,我检查合格后才开始正式标注。
第二,负样本要足够。只标注“没戴安全帽”的图片,模型学到的可能是“人头”特征,而不是“没戴帽子”特征。必须加入大量“戴了安全帽”的负样本,让模型学会区分。负样本和正样本的比例,我一般控制在 2:1 到 3:1 之间。
第三,场景多样性要覆盖。白天、夜晚、晴天、雨天、逆光、顺光、不同摄像头角度、不同分辨率,都要有标注数据。我吃过亏:模型在白天数据上训练,到了晚上红外模式下,安全帽检测精度直接从 95% 掉到 60%。后来补了 2000 张夜间红外标注图,精度才拉回来。
第四,标注工具选型。小规模标注用 LabelImg 就够了,开源免费。大规模标注(超过 1 万张)建议用 CVAT 或 Label Studio,支持多人协作、标注审核、导出多种格式。如果预算充足,Roboflow 的在线标注和自动预标注功能能省不少时间。
实操心得:标注完成后,一定要做一次“交叉验证”。让标注人员 A 标 100 张,标注人员 B 也标这 100 张,对比两者的标注结果。如果一致率低于 90%,说明标注标准有问题,需要重新培训。这个步骤花不了多少时间,但能避免后面几周的返工。
3.2 模型训练:从预训练权重到现场微调
数据准备好了,训练反而是相对标准化的流程。我用的是 Ultralytics 的 YOLOv8 框架,训练脚本大概长这样:
from ultralytics import YOLO model = YOLO("yolov8m.pt") # 加载预训练权重 results = model.train( data="safety_dataset.yaml", epochs=100, imgsz=640, batch=16, device=0, workers=8, optimizer="AdamW", lr0=0.001, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3, patience=20, augment=True, mosaic=1.0, mixup=0.1, copy_paste=0.1, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=10.0, translate=0.1, scale=0.5, shear=2.0, perspective=0.0, flipud=0.0, fliplr=0.5 )几个关键参数的解释:
imgsz=640:输入图像尺寸。安环场景建议用 640,兼顾精度和速度。如果小目标多(比如远处的人),可以调到 1280,但推理速度会下降 3-4 倍。batch=16:批次大小。根据显卡显存调整,T4 16G 显存跑 YOLOv8m 用 batch=16 没问题。patience=20:早停耐心值。如果 20 个 epoch 验证集精度没有提升,就停止训练,避免过拟合。mosaic=1.0:Mosaic 数据增强。把 4 张图拼成 1 张,能显著提升小目标检测能力。但训练后期建议关掉(设成 0),让模型适应真实图像分布。mixup=0.1、copy_paste=0.1:其他数据增强手段,对安环场景的小目标检测有帮助。
训练完成后,看几个关键指标:mAP50、mAP50-95、precision、recall。安环场景一般要求 mAP50 达到 0.85 以上,recall 达到 0.90 以上。recall 比 precision 更重要——漏报一个违规,可能出安全事故;误报一个,安全员多看一眼,成本低得多。
3.3 模型优化:剪枝、量化与 TensorRT 加速
训练出来的 PyTorch 模型,直接部署推理速度往往不够。需要做几步优化:
第一步,模型剪枝。用torch.nn.utils.prune或者 Ultralytics 自带的剪枝工具,把不重要的权重剪掉。我一般剪掉 20%-30% 的通道,精度损失控制在 1% 以内,推理速度能提升 30% 左右。
第二步,量化。把 FP32 权重转成 FP16 或 INT8。FP16 量化几乎无损,推理速度提升 1.5-2 倍。INT8 量化精度损失稍大(1%-3%),但速度提升 2-3 倍。安环场景建议用 FP16,精度更稳。
第三步,TensorRT 加速。这是最关键的一步。把 ONNX 模型转成 TensorRT engine,推理速度能再提升 2-3 倍。转换命令大概是这样:
trtexec --onnx=yolov8m.onnx \ --saveEngine=yolov8m_fp16.engine \ --fp16 \ --workspace=4096 \ --minShapes=images:1x3x640x640 \ --optShapes=images:8x3x640x640 \ --maxShapes=images:16x3x640x640--optShapes=images:8x3x640x640表示优化批次大小为 8,这是根据实际并发路数设定的。--workspace=4096是 TensorRT 的工作空间大小,单位 MB,设大一点能让 TensorRT 有更多优化空间。
经过这三步优化,YOLOv8m 在 T4 显卡上的推理速度能从原始的 60fps 提升到 250fps 以上。这意味着单张 T4 卡可以同时处理 10-15 路 5fps 的视频流分析。
3.4 误报抑制:从“狼来了”到“精准告警”
误报是安环 AI 视频分析系统最大的敌人。我见过一个项目,上线第一周每天产生 2000 多条告警,安全员直接把系统关了。误报来源主要有几类:
第一类,模型误检。把墙上的安全帽海报识别成真人戴安全帽,把树影识别成人员。这类误报靠增加负样本和调整置信度阈值来解决。
第二类,场景误判。人员在非作业区域没戴安全帽,但系统配置的规则是“全区域检测”。这类误报靠业务规则引擎的“区域过滤”来解决——只在作业区域内触发告警。
第三类,时序抖动。模型在连续帧之间检测结果不稳定,一会儿检测到一会儿检测不到。这类误报靠“连续帧确认”来解决——连续 N 帧都检测到才触发告警。
第四类,重复告警。同一个人没戴安全帽,在画面里待了 5 分钟,系统产生了 300 条告警。这类误报靠“告警去重”来解决——同一个目标在时间窗口内只告警一次。
我设计了一个“误报抑制流水线”,把上述四种抑制手段串起来:
def suppress_false_alarms(detections, rules, history): # 1. 置信度过滤 detections = [d for d in detections if d.confidence >= rules.confidence_threshold] # 2. 区域过滤 detections = [d for d in detections if is_in_zone(d.bbox, rules.zones)] # 3. 连续帧确认 confirmed = [] for d in detections: track_id = match_track(d, history) if track_id and history[track_id].consecutive_count >= rules.consecutive_frames: confirmed.append(d) # 4. 告警去重 final_alerts = [] for d in confirmed: if not is_duplicate_alert(d, history, rules.alert_cooldown): final_alerts.append(d) return final_alerts这套流水线跑下来,误报率能从每天 2000 条降到 50 条以内,安全员才愿意用。
4. 实操过程:从零搭建一套可运行的安环视频分析系统
4.1 环境准备与依赖安装
先说硬件。最小可运行配置:一台带 NVIDIA 显卡的服务器(T4 或 3060 以上),16G 内存,500G 硬盘。如果要接入超过 10 路视频,建议用 T4 或 A10 显卡,内存 32G 以上。
软件环境:
# 创建虚拟环境 conda create -n safety_ai python=3.10 conda activate safety_ai # 安装 PyTorch(根据 CUDA 版本选择) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics pip install ultralytics # 安装 OpenCV pip install opencv-python opencv-python-headless # 安装 FFmpeg(系统级) sudo apt-get install ffmpeg # 安装 TensorRT(需要 NVIDIA 官方包) pip install tensorrt # 安装 Web 框架 pip install fastapi uvicorn websockets # 安装数据库驱动 pip install pymysql redis注意:TensorRT 的安装比较麻烦,需要和 CUDA、cuDNN 版本严格匹配。建议直接用 NVIDIA 官方的 Docker 镜像
nvcr.io/nvidia/tensorrt:23.09-py3,省去版本兼容的烦恼。
4.2 视频流接入与抽帧服务
视频接入服务我写成了一个独立的 Python 进程,每个摄像头一个线程,负责拉流、解码、抽帧、送推理队列:
import cv2 import subprocess import numpy as np from queue import Queue from threading import Thread class VideoStream: def __init__(self, rtsp_url, fps=5, width=1280, height=720): self.rtsp_url = rtsp_url self.fps = fps self.width = width self.height = height self.frame_queue = Queue(maxsize=10) self.running = False def start(self): self.running = True self.thread = Thread(target=self._capture_loop, daemon=True) self.thread.start() def _capture_loop(self): cmd = [ "ffmpeg", "-rtsp_transport", "tcp", "-i", self.rtsp_url, "-f", "rawvideo", "-pix_fmt", "bgr24", "-vf", f"fps={self.fps},scale={self.width}:{self.height}", "-" ] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) frame_size = self.width * self.height * 3 while self.running: raw_frame = process.stdout.read(frame_size) if len(raw_frame) != frame_size: # 断流重连 process.kill() process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) continue frame = np.frombuffer(raw_frame, dtype=np.uint8).reshape((self.height, self.width, 3)) if self.frame_queue.full(): self.frame_queue.get() # 丢弃旧帧 self.frame_queue.put(frame) def read(self): return self.frame_queue.get() def stop(self): self.running = False这段代码的关键点:用 FFmpeg 子进程拉流,而不是 OpenCV 的VideoCapture。FFmpeg 对 RTSP 断流重连的处理更可靠。frame_queue设成 maxsize=10,满了就丢旧帧,保证实时性。
4.3 推理服务与多路并发
推理服务是系统的核心。我用 FastAPI 起了一个 HTTP 服务,接收视频帧,返回检测结果。但 HTTP 传输图像效率太低,实际部署时改成了 gRPC 或者共享内存。
多路并发的关键是批处理。把多个摄像头的帧攒成一个 batch,一起送进 TensorRT 推理,能显著提升 GPU 利用率:
class InferenceEngine: def __init__(self, engine_path, batch_size=8): self.batch_size = batch_size self.engine = load_tensorrt_engine(engine_path) self.input_buffer = [] def infer(self, frames): # frames: list of numpy arrays if len(frames) < self.batch_size: # 填充到 batch_size frames = frames + [np.zeros_like(frames[0])] * (self.batch_size - len(frames)) batch = np.stack(frames, axis=0).astype(np.float32) / 255.0 batch = np.transpose(batch, (0, 3, 1, 2)) # NHWC -> NCHW outputs = self.engine.run(batch) return self.postprocess(outputs) def postprocess(self, outputs): # 解析 TensorRT 输出,做 NMS,返回检测框 detections = [] for i in range(self.batch_size): boxes = outputs[i][:, :4] scores = outputs[i][:, 4] classes = outputs[i][:, 5] keep = nms(boxes, scores, iou_threshold=0.5) for idx in keep: if scores[idx] >= 0.5: detections.append({ "bbox": boxes[idx].tolist(), "confidence": float(scores[idx]), "class": int(classes[idx]) }) return detections批处理大小设为 8 是一个经验值。太小了 GPU 利用率上不去,太大了延迟会增加。8 路视频、每路 5fps,总共 40fps 的输入,batch=8 意味着每 200ms 处理一批,延迟完全可以接受。
4.4 业务规则引擎与告警推送
业务规则引擎我用 Python 实现了一个轻量级的规则匹配器。规则配置存在 MySQL 里,启动时加载到内存,支持热更新:
class RuleEngine: def __init__(self, rules_config): self.rules = rules_config self.track_history = {} # 跟踪历史,用于连续帧判断 self.alert_history = {} # 告警历史,用于去重 def evaluate(self, detections, camera_id, timestamp): alerts = [] for rule in self.rules: if not self._match_conditions(detections, rule, camera_id, timestamp): continue for det in detections: if not self._match_detection(det, rule): continue track_id = self._get_track_id(det, camera_id) if not self._check_consecutive(track_id, rule): continue if self._is_duplicate(track_id, rule, timestamp): continue alert = { "rule_name": rule["rule_name"], "camera_id": camera_id, "timestamp": timestamp, "detection": det, "level": rule["action"]["level"] } alerts.append(alert) self._record_alert(track_id, rule, timestamp) return alerts def _check_consecutive(self, track_id, rule): required = rule["conditions"].get("consecutive_frames", 1) history = self.track_history.get(track_id, []) return len(history) >= required def _is_duplicate(self, track_id, rule, timestamp): cooldown = rule["action"].get("alert_cooldown", 60) # 默认 60 秒 last_alert = self.alert_history.get(track_id) if last_alert and (timestamp - last_alert) < cooldown: return True return False告警推送支持三种方式:WebSocket 实时推送到 Web 端、App 推送(通过极光或个推)、短信通知(通过阿里云短信服务)。WebSocket 推送的代码大概是这样:
from fastapi import WebSocket class AlertNotifier: def __init__(self): self.connections = [] async def connect(self, websocket: WebSocket): await websocket.accept() self.connections.append(websocket) async def broadcast(self, alert): for connection in self.connections: try: await connection.send_json(alert) except: self.connections.remove(connection)4.5 证据留存与查询回放
每条告警都要留存证据:违规截图、前后 10 秒的短视频、检测框坐标、置信度、规则名称、时间戳。截图和视频存到 MinIO 或阿里云 OSS,元数据存到 MySQL。
查询回放功能支持按时间范围、摄像头、规则类型、告警级别筛选。前端用 Vue 或 React 做一个简单的管理界面,左边是摄像头列表,中间是实时画面,右边是告警列表。点击告警,弹出证据截图和短视频。
实操心得:证据留存要注意存储成本。1080p 的截图每张约 500KB,短视频每段约 2MB。如果每天 100 条告警,一年就是 36500 条,存储需求约 90GB。建议设置自动清理策略,比如只保留最近 6 个月的证据,更早的归档到冷存储。
5. 常见问题与排查技巧实录
5.1 模型精度不达标:从数据到参数的排查清单
模型上线后精度不达标,是最常见的问题。我整理了一个排查清单,按优先级排序:
| 排查项 | 检查方法 | 常见问题 | 解决方案 |
|---|---|---|---|
| 标注质量 | 随机抽 100 张标注图人工复核 | 漏标、错标、类别混淆 | 重新标注,统一标准 |
| 数据分布 | 统计训练集和测试集的类别分布 | 某些类别样本过少 | 补充少数类样本 |
| 场景覆盖 | 对比训练集和现场画面的差异 | 缺少夜间、逆光、雨天数据 | 补充对应场景数据 |
| 输入尺寸 | 检查训练和推理的 imgsz 是否一致 | 训练 640,推理 1280 | 保持一致 |
| 置信度阈值 | 调整阈值看 precision-recall 曲线 | 阈值过高导致漏报 | 降低阈值,增加连续帧确认 |
| 模型容量 | 对比 YOLOv8n 和 YOLOv8m 的效果 | 模型太小,欠拟合 | 换更大模型 |
| 训练轮数 | 看 loss 曲线是否收敛 | 训练不足或过拟合 | 调整 epochs 和早停 |
我遇到过一个典型案例:安全帽检测在测试集上 mAP50 有 0.92,到了现场只有 0.65。排查后发现,测试集都是白天高清画面,现场有大量夜间红外画面和低分辨率老摄像头画面。补了 3000 张对应场景的标注数据后,现场精度恢复到 0.88。
5.2 推理速度慢:从 GPU 利用率到批处理的优化路径
推理速度慢,先看 GPU 利用率。如果 GPU 利用率低于 50%,说明瓶颈不在 GPU,而在数据预处理或后处理。如果 GPU 利用率接近 100%,说明模型太大或 batch 太小。
优化路径按优先级:
- 开启 FP16 或 INT8 量化:速度提升 1.5-3 倍,精度损失可控。
- 使用 TensorRT 加速:速度提升 2-3 倍,这是最有效的手段。
- 增大 batch size:GPU 利用率从 50% 提升到 90%,吞吐量提升近一倍。
- 降低输入分辨率:从 1280 降到 640,速度提升 3-4 倍,但小目标精度会下降。
- 模型剪枝:剪掉 20%-30% 通道,速度提升 30%,精度损失 1% 以内。
- 多卡并行:如果单卡不够,用多张卡分担不同摄像头的推理任务。
注意:不要一上来就降分辨率。安环场景的小目标(远处的人、安全帽)对分辨率很敏感。先试 TensorRT 和 FP16,实在不够再考虑降分辨率。
5.3 误报太多:从规则配置到模型迭代的抑制策略
误报太多,安全员会直接弃用系统。我总结了一套“误报抑制组合拳”:
第一拳,提高置信度阈值。从 0.5 提到 0.7 甚至 0.8。recall 会降一些,但 precision 会大幅提升。安环场景宁可漏报少数,也不能让安全员被误报淹没。
第二拳,增加连续帧确认。从 1 帧提到 3-5 帧。单帧误检被过滤掉,真实违规因为持续存在会被保留。
第三拳,配置检测区域。只在作业区域、危险区域触发告警。非作业区域的人员活动不告警。
第四拳,告警去重。同一个目标在 60 秒内只告警一次。避免同一个人没戴安全帽在画面里待 5 分钟产生 300 条告警。
第五拳,模型迭代。把误报截图收集起来,标注成负样本,重新训练模型。这是最根本的解决办法,但需要持续投入。
我做过一个统计:只做第一拳和第二拳,误报率能降 70%;加上第三拳和第四拳,误报率能降 95%;持续做第五拳,误报率能降到每天 10 条以内。
5.4 视频流不稳定:断流、花屏、延迟累积的解决
视频流不稳定是工程落地中最烦人的问题。常见现象和解决方案:
断流:FFmpeg 进程崩溃或 RTSP 连接断开。解决方案是加心跳检测和自动重连。我写了一个监控线程,每 10 秒检查一次 FFmpeg 进程是否存活,如果挂了就重启。
花屏:解码错误导致画面出现马赛克。通常是网络丢包引起的。解决方案是改用 TCP 传输协议(-rtsp_transport tcp),牺牲一点延迟换取稳定性。
延迟累积:画面越来越慢,最后延迟几十秒。原因是消费速度跟不上生产速度,帧队列越积越多。解决方案是设置队列最大长度,满了就丢旧帧。安环场景不需要每一帧都分析,丢帧不影响告警。
夜间红外切换:摄像头在傍晚自动切换到红外模式,画面变成黑白,模型精度下降。解决方案是训练时加入红外模式的数据,或者配置摄像头在固定时间切换,避免频繁切换。
5.5 系统资源占用过高:CPU、内存、显存的优化
系统跑起来后,资源占用过高会导致其他服务受影响。优化手段:
CPU 优化:FFmpeg 解码是 CPU 密集型的。如果 CPU 占用过高,可以用 GPU 解码(-hwaccel cuda),把解码任务交给显卡。或者降低抽帧率,从 5fps 降到 3fps。
内存优化:帧队列、跟踪历史、告警历史都会占内存。设置合理的过期时间,定期清理。跟踪历史保留最近 5 分钟,告警历史保留最近 1 小时。
显存优化:TensorRT engine 加载后会占用显存。YOLOv8m FP16 engine 大约占 1.5GB 显存。如果显存不够,换 YOLOv8s 或 YOLOv8n,或者用 INT8 量化。
我实测过一套配置:T4 显卡(16G 显存)、16G 内存、8 核 CPU,接入 12 路 1080p 视频、5fps 抽帧、YOLOv8m FP16 推理,CPU 占用约 40%,内存占用约 8G,显存占用约 6G。这个配置留有余量,可以稳定运行。
6. 这套系统还能怎么扩展
安环视频分析只是起点。同样的技术架构,稍微调整模型和规则,就能扩展到其他场景:
扩展一,人员行为分析。增加“人员倒地”“人员打架”“人员聚集”“人员离岗”等行为识别模型。这些模型需要时序信息,可以用 SlowFast 或 TimeSformer 等视频理解模型。
扩展二,设备状态监测。增加“设备冒烟”“设备漏油”“仪表读数异常”等检测模型。这类场景需要高分辨率输入,可以用 YOLOv8x 或专门的小目标检测模型。
扩展三,车辆管理。增加“车辆违停”“车辆超速”“车辆未冲洗”等检测模型。需要结合车牌识别和测速算法。
扩展四,多模态融合。把视频分析和音频分析结合起来。比如“人员呼救”可以通过音频检测,“设备异响”可以通过声纹分析。多模态融合能显著提升告警准确率。
扩展五,边缘计算部署。把推理服务部署到边缘设备(如 Jetson Orin),在摄像头端完成分析,只把告警结果传到中心服务器。这样能大幅降低带宽需求和中心服务器压力。
我在实际项目中发现,安环视频分析系统的价值不仅在于“抓违规”,更在于“数据沉淀”。积累半年到一年的告警数据后,可以做安全风险热力图、违规趋势分析、重点区域识别,为安全管理提供数据支撑。这才是这套系统最大的长期价值。