ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Python的电梯轿厢异常行为检测系统实现

2026/9/17 17:04:22 拓冰建站 浏览量
基于Python的电梯轿厢异常行为检测系统实现 简介围绕电梯安全监控场景的乘客异常行为检测示例代码主要服务计算机视觉入门者、智慧安防方向学生与智能楼宇系统开发者。该资源聚焦视频监控与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, timeout5, max_retries10): 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_age8, min_hits3): 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_age8 指的是在连续 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_len16): 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, axis0) clip torch.tensor(clip, dtypetorch.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_seconds30): 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_sec2) # 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 anullsrcr8000:clmono -rtsp_transport tcp -i rtsp://admin:password192.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(formatonnx, imgsz640, halfTrue, simplifyTrue) # 转换到 TensorRT engine # trtexec --onnxyolov8n.onnx --saveEngineyolov8n.engine --fp16半精度 FP16 转换一般足够。只有当设备为 Jetson Orin Nano 这类算力紧张的硬件时才会考虑 INT8而且需要准备至少 500 张电梯内抓拍帧做校准。INT8 后头部检测框在暗光下会略有收缩调试时需要把 NMS 的 IoU 阈值由默认的 0.45 提高到 0.5减少单人多框重叠残留。5.2 暗光环境下的预处理参数参考暗光场景通常不建议直接调高曝光补偿因为会导致运动拖影更严重。更有效的做法是通过图像增强提升输入质量同时把暗光帧单独用于训练一个 low-light enhancement 分支而不是修改统一的预处理管线。参数项日间建议值夜间建议值曝光补偿00.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 流水线每次修改模型权重、跟踪参数或规则阈值后自动执行一遍就能在电梯现场踩坑之前拦截掉大部分回归问题。最后的建议是把这个回归视频集和参数基线表一并交付给使用方后续电梯维护团队更换摄像头型号或调整安装角度时可以自行参照基线重新标定不必每次都找算法工程师到场。本文还有配套的精品资源点击获取