基于Python的电梯轿厢异常行为检测系统实现
2026/9/16 14:57:26 网站建设 项目流程

简介:围绕电梯安全监控场景的乘客异常行为检测示例代码,主要服务计算机视觉入门者、智慧安防方向学生与智能楼宇系统开发者。该资源聚焦视频监控与AI识别,通过Python脚本实现视频读取、行为特征判断与异常警告,可覆盖吸烟、丢垃圾、过度拥挤、爬门、阻止关门等典型危险行为,有助于在轿厢小样本环境中快速搭建检测原型。压缩包内共2个文件,包括一个Python脚本和一份Markdown说明文档,包体仅4KB,轻量透明;脚本集中实现基于视频帧的检测主流程,文档则说明项目结构与运行方式,两者配合可帮助读者快速跑通示例并理解各模块作用。目前已有192人学习/浏览,读者既能获得基础行为检测代码框架,也能借此验证异常行为算法思路,为后续接入真实电梯控制系统或扩展深度学习模型提供参考起步,适合课程设计、快速原型验证或算法效果演示等场景。

1. 从一段监控视频里,如何判断电梯里的人在打架还是摔倒

电梯轿厢是一个极其特殊的监控场景:空间狭小、光照复杂、人脸因俯仰角度严重变形,传统基于人脸或全身关键点的行为识别模型在这里经常失效。而电梯安全事故中,异常行为往往是前几秒决定后续处置窗口,比如吸烟触发火灾、强行扒门导致电梯急停、乘客倒地无人发现。这篇资源围绕一套 Python 实现(video.py 为核心)的电梯轿厢内异常行为检测方案展开,覆盖从视频流接入、目标检测、行为分类到告警联动的完整链路,适合安防集成商、物业智能化改造团队以及做边缘视频分析的开发者参考。与通用人体行为识别不同,这里需要把模型、触发策略、误报抑制放在同一套电梯约束条件下来设计。

2. 电梯场景下的视频数据接入与预处理

电梯轿厢监控的特殊性在于摄像头角度固定、遮挡频繁、光线突变(门开闭瞬间),这意味着对视频帧质量的要求比普通室内监控更高。以常见做法来看,这一环节通常包含 RTSP 视频流接入、抽帧策略、轿厢区域裁剪、隐私遮罩四件事,核心代码可以在 video.py 里直接复现。

2.1 基于 OpenCV 的 RTSP 视频流读取与断线重连

很多工程里直接用 cv2.VideoCapture 拉流,但电梯摄像头常年运行,网络抖动和摄像头重启非常常见。更稳妥的方案是封装一个重连机制:设置读帧超时、缓存最近一次正常帧、在 I 帧到来时才送入模型,避免半帧花屏直接导致误检漏检。

import cv2 import time class ElevatorStream: def __init__(self, rtsp_url, timeout=5, max_retries=10): self.rtsp_url = rtsp_url self.timeout = timeout self.max_retries = max_retries self.cap = None self.last_valid_frame = None def connect(self): self.cap = cv2.VideoCapture(self.rtsp_url) self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.cap.set(cv2.CAP_PROP_FPS, 15) if not self.cap.isOpened(): raise ConnectionError("failed to open rtsp stream") def read_frame(self): ret, frame = self.cap.read() if ret: self.last_valid_frame = frame return frame return self.last_valid_frame stream = ElevatorStream("rtsp://192.168.1.100:554/stream1")

这段代码在断流时返回最后一帧有效画面,避免模型侧因输入全黑帧或绿屏帧产生误报。生产环境中,应当在读帧失败时追加重连计数,连续重连达到 max_retries 就重启整个视频管道,而不是只重试单次 read()。摄像头的 GOP 设置也直接影响效果,一般建议 I 帧间隔不超过 2 秒,否则从断线恢复后的首帧检测延迟会很高。

2.2 抽帧调度与检测频率解耦

电梯轿厢内乘客异常行为的判定并不需要每帧都跑模型,一方面算力受限,另一方面大多数动作是瞬态的,关键帧反而比连续帧更利于分类。通常做法是:视频流每 0.1 秒读取一次,目标检测每 3 帧执行一次,行为分类在检测到人且持续 1.5 秒后才开始。通过频率解耦,可以显著降低边缘设备(如 Jetson Nano、RK3588)的负载。

FRAME_INTERVAL = 3 MIN_ACTIVE_FRAMES = 15 # 1.5s @ 10fps frame_count = 0 active_human_frames = 0 while True: frame = stream.read_frame() frame_count += 1 if frame_count % FRAME_INTERVAL != 0: continue detections = person_detector(frame) if detections: active_human_frames += 1 else: active_human_frames = 0 if active_human_frames >= MIN_ACTIVE_FRAMES: classify_behavior(frame, detections)

这段逻辑的关键在于,MIN_ACTIVE_FRAMES 控制的是人的持续存在时长,用来过滤电梯门开、乘客路过等瞬时场景,防止行为分类器对噪声帧做出动作。参数需要结合电梯运行状态动态调整:轿厢静止时阈值可以调低到 10 帧,轿厢运行时调高到 20 帧,因为运行时的抖动会让检测框剧烈跳变。

2.3 轿厢区域裁剪与透视校正

电梯摄像头画面边缘往往是轿厢壁和门外走廊,直接送入模型不仅增加计算量,还会把候梯厅的走动者误判进轿厢行为。更合理的做法是在系统初始化阶段手工标定轿厢区域,用四边形四个顶点裁剪出有效区域;如果摄像头安装有俯角,再做一次透视变换,把轿厢底板拉成近似矩形,减少后续目标检测框在边缘位置的位置偏移。

处理项推荐做法作用
区域裁剪四边形 mask 标注轿厢内壁范围排除轿厢外干扰
透视变换保留 3x3 变换矩阵校正俯拍变形
抽帧频率检测 10fps,分类 5fps降低边缘设备推理压力
隐私遮罩人脸区域高斯模糊满足隐私合规要求

透视换后要注意检测框坐标的映射关系。如果只在裁剪图上做检测,需要把结果框反向映射回原始视频坐标,才能在录像回放时准确叠加报警框。常见的错误是直接在裁剪图上画框后保存,导致录像中的报警框与真实位置偏差明显。

3. 异常行为识别算法选型与核心实现

电梯轿厢内的异常行为覆盖吸烟、斗殴、倒地、强行扒门、人数异常拥挤等,这不是一个模型能统一解决的,需要在架构上拆成「目标检测 → 目标跟踪 → 行为分类 → 规则判定」四层。对于这个项目而言,video.py 中已经实现了前三层的基础逻辑,关键要理解每一层的选型理由和参数边界。

3.1 为什么不用纯姿态估计方案

行业里不少团队一开始选用 OpenPose 或 MediaPipe 做人体关键点提取,再根据关键点夹角判断跌倒或打架,但电梯场景最终会暴露几个硬伤:轿厢内多人互相遮挡时关键点连接错误率极高;俯拍视角下膝盖、脚踝等关键点大量被身体挡住;夜间或低照度下关键点置信度普遍低于 0.5,模型输出价值很低。因此更实际的主干方案是两个模型串行:先 YOLOv8 做人框检测,再对单人框内图像做轻量化行为分类。

3.2 基于 YOLOv8 的乘客检测与 Semi-SORT 跟踪

YOLOv8 在这里只负责定位人框。检测置信度阈值建议设置在 0.45 到 0.5 之间,过低会出现椅子、背包造成误检,过高会漏掉蹲下或躺倒的乘客——他们的外观特征跟站立时差异很大。跟踪部分使用 SORT 的简化版本,对每个检测框维护 ID 和质心轨迹,用于后续行为分类的状态粘连。

from collections import OrderedDict class SemiSORT: def __init__(self, max_age=8, min_hits=3): self.max_age = max_age self.min_hits = min_hits self.tracks = OrderedDict() self.next_id = 1 def update(self, boxes): current_ids = set() for box in boxes: cx = (box[0] + box[2]) / 2 cy = (box[1] + box[3]) / 2 matched_id = self._match(cx, cy) if matched_id is None and len(self.tracks) < 16: self.tracks[self.next_id] = {"cx": cx, "cy": cy, "age": 0, "hits": 1} matched_id = self.next_id self.next_id += 1 else: continue current_ids.add(matched_id) self.tracks[matched_id]["cx"] = cx self.tracks[matched_id]["cy"] = cy self.tracks[matched_id]["age"] = 0 self.tracks[matched_id]["hits"] += 1 self._aging_and_purge(current_ids) return [tid for tid in current_ids if self.tracks[tid]["hits"] >= self.min_hits]

这里的 max_age=8 指的是在连续 8 帧没有匹配到任何检测框时,才删除该跟踪 ID。电梯内人被完全遮挡的情况很难超过 0.5 秒,8 帧余量足够等待同一人重新出现。min_hits 的意义是避免零散误检短期噪声产生 ID 抖动。

3.3 行为分类模型与 3D-CNN 的输入构造

对单个人框做行为分类时,不能只用单帧图片,因为吸烟和抬手、斗殴和拥抱在单帧上几乎没有区分度。常见做法是采集 16 帧连续图像序列,构成 16x224x224x3 的张量,输入一个轻量 3D-CNN(如 X3D-XS 或 SlowOnly-R18)输出行为类别概率。这段逻辑在 video.py 中体现为对跟踪 ID 的历史帧队列进行采样:

import numpy as np import torch class BehaviorBuffer: def __init__(self, buffer_len=16): self.buffer_len = buffer_len self.buffers = {} def push(self, track_id, frame_resized): if track_id not in self.buffers: self.buffers[track_id] = [] bucket = self.buffers[track_id] bucket.append(frame_resized) if len(bucket) > self.buffer_len: bucket.pop(0) def get_clip(self, track_id): bucket = self.buffers.get(track_id, []) if len(bucket) < self.buffer_len: return None clip = np.stack(bucket, axis=0) clip = torch.tensor(clip, dtype=torch.float32) clip = clip.permute(3, 0, 1, 2).unsqueeze(0) return clip

这个缓冲队列的容量直接与目标跟踪模块的 max_age 联动。如果 max_age 设置过短,跟踪 ID 频繁切换,行为分类器永远等不到 16 帧连续数据。所以实践中往往把 max_age 提高至 16 到 20 帧,确保单个乘客在画面中停留时间足够生成完整动作片段。

3.4 规则引擎与置信度融合

行为分类器输出的概率不能直接作为告警依据,需要叠加一个规则引擎来抑制误报。比如「斗殴」的触发条件可以写成:连续 5 个行为片段(每段 16 帧)中至少 3 个片段的斗殴置信度大于 0.6,同时两个乘客跟踪框的重叠度超过 30%,且跟踪框的移动速度异常(即出现了快速碰撞位移)。

FIGHT_THRESHOLD = 0.6 OVERLAP_RATIO = 0.3 MIN_FIGHT_CLIPS = 3 def is_fight_alert(track_behavior_history): recent_clips = track_behavior_history[-5:] fight_clips = sum(1 for clip in recent_clips if clip["fight_score"] > FIGHT_THRESHOLD) return fight_clips >= MIN_FIGHT_CLIPS

另一种值得直接实现的规则是倒地检测,单靠分类器把「倒地」和「蹲下」区分开非常困难,但加上几何条件就清楚了:人框的宽高比从小于 0.5 突变到大于 1.2,且框的质心高度下降超过 40%,同时保持这个状态超过 2 秒。这三个条件叠加,误报率可以压到很低,而单一模型无论训练数据多好都做不到这一点。

4. 异常告警输出与电梯控制系统联动

检测出异常行为只是第一步,真正交付给物业或电梯厂商时,需要考虑的是告警数据如何流转、如何避免重复告警、如何让电梯控制系统在必要时介入。这里采用前后端分离+消息推送的架构:video.py 端通过 HTTP/MQTT 输出结构化告警事件,管理后台负责事件存储和人工复核,Android 上屏端负责实时展示。

4.1 告警事件的数据结构与去重策略

告警数据结构应当包含事件类型、置信度、抓拍时间、轿厢编号、视频片段路径、乘客跟踪 ID 列表,而不是简单发一张带框的图片。去重策略使用时间窗口计数方式:同一跟踪 ID 在 30 秒内只允许触发一次同类型告警,防止模型连续多帧判定斗殴导致告警风暴。

import time import json class AlertManager: def __init__(self, dedup_seconds=30): self.dedup_seconds = dedup_seconds self.last_alert = {} def emit(self, event_type, track_id, confidence, metadata): key = f"{track_id}:{event_type}" now = time.time() if key in self.last_alert: if now - self.last_alert[key] < self.dedup_seconds: return None self.last_alert[key] = now alert_payload = { "type": event_type, "confidence": round(confidence, 3), "track_id": track_id, "timestamp": int(now), "elevator_id": metadata["elevator_id"], "clip_path": metadata["clip_path"], } return json.dumps(alert_payload)

这里注意不要把置信度当成唯一过滤条件。斗殴场景中模型置信度可能只有 0.55 左右,但加上重叠度和轨迹突变规则后已经是高置信的强信号。如果只在置信度上卡阈值,就得調到 0.8 以上,那几乎所有真实斗殴都漏掉了。

4.2 与电梯控制器的信号交互

电梯控制系统联动是电梯异常行为检测区别于普通视频识别项目的关键所在。以联动方式为例,检测到扒门行为时,通过串口/Modbus 向电梯主控板发送「暂停关门」指令;检测到乘客倒地时,发送「保持开门」指令并通知物业。这套逻辑必须放在电梯厂商提供的安全回路之外,不能直接切断门锁回路,否则就破坏了电梯原有安全标准。

import modbus_tk.defines as cst from modbus_tk import modbus_tcp def send_elevator_command(plc_ip, port, action_code): master = modbus_tcp.TcpMaster(plc_ip, port, timeout_in_sec=2) # action_code: 1=保持开门 2=暂停关门 3=急停 master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 0, output_value=[action_code]) master.close()

联动命令的触发条件必须是规则引擎输出,而不是行为分类器原始概率。原因很简单:分类器每 16 帧输出一次结果,而动作是一个时间过程,单次分类输出可能是噪声。实际操作中一般引入三级响应策略:置信度 0.5-0.65 仅上传事件记录;0.65-0.85 推送物业 App 并弹窗确认;大于 0.85 且满足规则条件才触发联动指令。

4.3 告警视频回传与本地录像双写

电梯摄像头网段通常与物业办公网隔离,视频流不能直接远程访问,因此本地边缘盒子需要承担录像回传功能。推荐使用 RTSP 推流到本地 NVR 的同时,把告警片段按 MP4 格式写入独立存储目录,并通过 SCP 或 OSS SDK 上传到中心服务器。项目交付时最容易忽略的是告警片段的时间戳校准,摄像头和边缘盒子时间不同步会导致事后溯源时录像对不上。

ffmpeg -f lavfi -i anullsrc=r=8000:cl=mono -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101" -t 10 -c:v copy -c:a aac -y /data/alerts/20240612153045.mp4

这段命令从 RTSP 流截取 10 秒视频并转封装为 MP4,适用于告警触发前的预录像回补。这里用 -rtsp_transport tcp 强制走 TCP 传输,避免 UDP 丢包导致的视频花屏。提前 5 秒加缓冲是常规做法,可以把命令里的偏移用音视频同步参数替换,但直接在 ffmpeg 层做内存环形缓存更可靠,具体做法是持续拉流写 ts 切片,告警时把前后切片合并。

5. 边缘部署性能调优与电梯环境特异性验证

算法和逻辑链路确定后,工程落地质量几乎全部取决于部署时的参数调优。电梯环境的特殊性在于:夜间轿厢灯光较暗且经常产生频闪、电梯门开闭导致亮度剧烈变化、老式电梯使用大功率电机造成视频干扰条纹、部分轿厢使用广角镜头产生边缘畸变。这些会比模型本身更快地拖垮检测效果。

5.1 推理引擎选择与人框检测的量化策略

YOLOv8 在边缘设备上直接跑 FP32 精度通常难以保证实时性,常见做法是先用 ONNX 导出模型,再用 TensorRT 或 RKNN 做 INT8 量化。量化的一个关键前提是校准数据集必须从电梯实景中采集,而不是用 COCO 的通用图片。如果跳过这一步,量化后每百帧可能会多出 5-10 个人框抖动,直接影响后续跟踪的稳定性。

import torch from ultralytics import YOLO model = YOLO("yolov8n.pt") model.export(format="onnx", imgsz=640, half=True, simplify=True) # 转换到 TensorRT engine: # trtexec --onnx=yolov8n.onnx --saveEngine=yolov8n.engine --fp16

半精度 FP16 转换一般足够。只有当设备为 Jetson Orin Nano 这类算力紧张的硬件时,才会考虑 INT8,而且需要准备至少 500 张电梯内抓拍帧做校准。INT8 后头部检测框在暗光下会略有收缩,调试时需要把 NMS 的 IoU 阈值由默认的 0.45 提高到 0.5,减少单人多框重叠残留。

5.2 暗光环境下的预处理参数参考

暗光场景通常不建议直接调高曝光补偿,因为会导致运动拖影更严重。更有效的做法是通过图像增强提升输入质量,同时把暗光帧单独用于训练一个 low-light enhancement 分支,而不是修改统一的预处理管线。

参数项日间建议值夜间建议值
曝光补偿0+0.3
对比度增强1.01.15
检测置信度阈值0.450.35
行为分类阈值0.60.55

夜间检测置信度阈值需要降低,原因在于黑暗环境下车身噪声干扰导致的人框置信度整体下降。但降低阈值必须配合 3.4 节的规则引擎过滤,否则门外的反光人影会被误判为乘客。

5.3 自动化回归脚本与误报率基线

部署完成后,建议建立一组固定的模拟场景视频用于回归验证:单人站立、多人拥挤、蹲下系鞋带、倒地、快速挥手、门外有人经过、灯光熄灭、电梯运行抖动。每次调整模型或阈值时,回放这些视频统计检出率和误报率。这个项目的交付验收通常以 48 小时连续运行误报次数不超过 3 次为标准,回归脚本可以用 video.py 的命令行参数接入:

python video.py --source test_scenarios/fight_01.mp4 --device cpu --threshold 0.45 --log-level debug

回归脚本里应把每类场景的预期结果写进断言。例如 fight_01.mp4 的预期是在第 12 到 25 秒之间至少触发一次斗殴告警,且不早于第 10 秒。把这类断言接入 CI 流水线,每次修改模型权重、跟踪参数或规则阈值后自动执行一遍,就能在电梯现场踩坑之前拦截掉大部分回归问题。最后的建议是,把这个回归视频集和参数基线表一并交付给使用方,后续电梯维护团队更换摄像头型号或调整安装角度时,可以自行参照基线重新标定,不必每次都找算法工程师到场。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询