简介:面向电梯监控场景的电动车与自行车识别项目,适用于毕业设计、课程设计、工程实训及竞赛等场景。项目基于电梯内视角数据集对YOLO预训练模型进行微调,同时提供检测与跟踪两种处理方式:检测方法逐帧标注目标实例,跟踪方法进一步去重,便于后续分析或告警联动。压缩包共包含134个文件,以Python源码、YAML配置、标注图片与测试图片为主,另有Dockerfile、Shell脚本、Jupyter Notebook教程、README及CSV结果记录等,支持快速搭建复现环境,整体体积约16.96MB,结构清晰、轻量易用。目前已有65人浏览学习,适合希望快速复现目标检测项目或在此基础上扩展功能的研究者与学生。资源内含完整工程文件与说明文档,代码经测试可正常运行,可借鉴设计思路撰写报告,还可基于现有模型调整数据集或训练参数,扩展识别更多目标类别。
1. 电梯监控视角识别电动车和自行车,难点不在模型而在数据
把电梯监控视角内的电动车和自行车识别当成一个普通目标检测任务来做,很多人的第一反应是直接挑一个现成模型、跑一遍公开权重就交差。实际上一旦把摄像头装到轿厢顶部角落,画面就会变成超过一百度的广角俯视:同一帧里既有轿厢内部,又有候梯厅一角,中间还夹着镜面不锈钢反光、LED 广告屏和不断开关的电梯门。公开模型在平视道路上学到的特征,到了这种视角下经常把自行车认成箱子、把电动车后轮直接从反光里漏掉。
做毕设、课设、实训、大作业和竞赛技术方案时,最稳妥的路线是把这类项目拆成三个独立模块:训练数据怎么采集和标注、模型怎么选和调、推理结果怎么变成一条能用的告警。我按自己做这类落地方案的习惯,把每一步的命令、参数和翻过车的地方都写清楚;跟着这套流程走,至少能少走一大半弯路。
2. 从监控视频到训练集:抽帧、清洗、标注与格式转换
训练数据是这类项目里最容易被低估的一环。电梯监控和常规道路监控的区别在于,摄像头机位相对固定、画面视角单一,但每个电梯的安装位置、轿厢材质、灯光条件都不一样。数据阶段如果只拿某一个电梯的几十段录像去标,后面训练出来的模型大概率只能在这个电梯里工作,换一栋楼就失效。先解决数据来源,再谈模型。
2.1 数据从哪来:监控录像授权采集与手机模拟顶置视角
正规一点的来源是学校宿舍楼、园区物业或自己实习单位的电梯监控录像,这类素材的画质、压缩格式和真实部署环境完全一致,是最理想的训练来源。拿到录像时要注意脱敏要求,视频里出现的行人面部、楼层按钮区域最好在抽帧后做模糊处理,避免后面答辩和展示时涉及隐私问题。
如果拿不到真实录像,常见替代做法是手机模拟采集。把手机支架固定在电梯顶部角落,用广角镜头对着轿厢门的方向拍四五段视频,每段三到五分钟,包含有人进出、有电动车推进推出、只有行人这几种情况。手机镜头和监控镜头在畸变程度上会有差异,但构图和拍摄角度足够接近,作为毕设数据也能讲得通。
数据量不建议只看帧数,而是看有效目标数:电动车和自行车各保留五百个以上的标注实例比较稳妥。采集时尽量覆盖不同电梯、不同时间段、开门和关门两种状态。公开数据集里虽然能找到大量自行车和摩托车的平视图像,但电梯俯视角度的样本很少,直接拿它们当主力训练集会带来比较明显的视角偏差,所以公开数据集最多用来做预训练或当负样本,不能替代自采数据。
2.2 用 FFmpeg 从监控视频抽帧:选帧策略与帧清洗流程
拿到原始监控视频后,第一步是用 FFmpeg 抽帧。电梯里人流量不大,不需要像道路监控那样高帧率抽帧,每秒一到两帧就够;电动车进电梯是个缓慢的过程,两秒一帧也能抓到关键状态,抽得太密反而会产生大量几乎相同的图像,让训练集出现严重的自相似过拟合。
ffmpeg -ss 00:00:00 -to 00:05:00 -i elevator_rec.mp4 \ -vf "fps=1,scale=1280:-1" -q:v 2 \ frames/frame_%05d.jpg逻辑说明:-ss指定开始时间,-to指定结束时间,可以先截取有目标出现的路段;fps=1表示每秒抽一帧;scale=1280:-1把画面宽度统一到 1280,高度按比例缩放,避免电梯监控常见的 D1 分辨率画面尺寸不统一;-q:v 2控制 JPEG 质量,数值越小质量越高。抽帧完成后需要人工过一遍,把完全没有目标的帧、画面卡在电梯门关闭状态的帧、被手或镜头遮挡产生的模糊帧删掉。
如果样本量仍然不够,还有一个小技巧:对同一段视频做多档抽帧,比如目标出现阶段用fps=3多抽一些,没有目标的阶段用fps=0.5跳着抽。抽出来的帧按电梯编号分目录存放,比如frames/ele_A、frames/ele_B,后面划分训练集和验证集就直接按目录切,而不是按帧随机切,这一步对防止数据泄漏很关键。
2.3 电动自行车和自行车的标注规范与 VOC 转 YOLO 脚本
标注工具我习惯用 LabelImg 或 AnyLabeling,输出格式保持 Pascal VOC XML。最关键的是标注规范必须在开工前定死,否则两个人标出来的框会完全对不上。电梯俯视视角下,电动自行车和自行车的区分依据有三个:是否有明显的电池仓、车身长度是否明显更大、踏板上是否有脚蹬结构。标注时框住整车轮廓,包含前后车轮,不要只框车身主体;目标被行人遮挡超过大概六成、无法分辨类别时,直接不标。
标注完成后,训练用的是 YOLO 格式的纯文本标注文件,所以需要写一个转换脚本,把 VOC XML 转成 YOLO TXT。
import os import xml.etree.ElementTree as ET from pathlib import Path CLASSES = ["bicycle", "electric_bicycle"] # 0: 自行车, 1: 电动车 def convert_voc_to_yolo(xml_path: str, out_dir: str): tree = ET.parse(xml_path) root = tree.getroot() img_w = float(root.find("size/width").text) img_h = float(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASSES: continue cls_idx = CLASSES.index(name) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_idx} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = os.path.join(out_dir, Path(xml_path).stem + ".txt") with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))逻辑说明:读取 XML 里的图片宽高,把xmin/ymin/xmax/ymax换成 YOLO 要求的归一化中心点坐标和归一化宽高。注意所有坐标都必须除以原始图片宽高,而不是缩放后的尺寸;如果之前用 FFmpeg 统一缩放过图片,XML 里的宽高也要跟着更新。转换完成后我会顺手跑一次检查脚本,把所有标注里坐标小于 0 或大于 1 的行打出来,这类错标在训练时会导致损失值异常跳动。
3. 模型选型与训练:YOLOv8 跑通电梯场景的最小流程
数据集准备好之后,模型层面的选择其实很明确:用 YOLOv8 系列。电梯监控场景目标数量少、类别只有两类、推理设备往往是普通 CPU 或老旧 GPU,用 YOLOv8n 或 YOLOv8s 就已经足够。更大的模型不是不能跑,而是在显存和推理延迟上都会带来额外负担,对这类小项目反而不好驾驭。
3.1 为什么预训练权重只能当起点:视角分布差在哪里
COCO 或 Pascal VOC 里虽然有自行车和摩托车,但这些目标大多是从平视角度拍的,背景是马路、人行道、停车场,目标形态和电梯俯视画面完全不同。电梯顶置摄像头看到的是车顶、电池仓、车筐和人的头顶,车身的侧面轮廓被压缩成一个接近俯视的矩形;再加上广角镜头带来的边缘畸变,同一辆车在画面中央和画面边缘的宽高比差异很大。
预训练权重在 COCO 上学到的特征依然有用,尤其是对车轮、金属反光表面等基础视觉元素的理解,但直接把没有微调的权重拿去做推理,效果通常会比较差。正确做法是把预训练权重当初始化起点,用自己的电梯样本做微调。这也意味着如果训练数据只有一两百帧,微调之后模型仍然可能记不住电梯视角下的目标形态,数据量不够时最值得投入的不是换模型,而是补样本。
模型选型参考这张表:
| 模型 | 参数量 | COCO mAP50-95 | 适合场景 |
|---|---|---|---|
| YOLOv8n | 约 3.2M | 约 37.3 | 树莓派、CPU 推理、低延迟要求 |
| YOLOv8s | 约 11.2M | 约 44.9 | 普通 GPU 或较高性能 CPU,精度优先 |
这个参数对比说明一件事:电梯监控这类目标不算小、类别不复杂,n 和 s 之间并没有明显的精度鸿沟;如果部署端只是做离线检测或者告警联动,s 会更稳,n 更适合需要实时预警的场景。
3.2 dataset.yaml 与训练命令:先跑通一遍再谈调优
训练前把数据集配置写成一个 YAML 文件,放在项目根目录下。训练集和验证集最好已经按电梯分开,比如训练集来自两个电梯,验证集来自第三个电梯,这样训练过程中的验证分数才有参考价值。
train: ./datasets/elevator/train val: ./datasets/elevator/val nc: 2 names: 0: bicycle 1: electric_bicycle然后执行训练命令:
yolo detect train \ model=yolov8n.pt \ data=elec.yaml \ epochs=100 \ imgsz=960 \ batch=16 \ device=0 \ project=./runs \ name=elec_v1 \ cache=True逻辑说明:model=yolov8n.pt表示从官方预训练权重开始微调;imgsz=960是对电梯这类中近距离目标比较合适的训练分辨率,比默认的 640 更能保留车轮、车筐等细节,又不会像 1280 那样显著拉高显存占用;epochs=100对几百到一千张的样本量已经足够,太多轮数反而容易过拟合;batch=16是 8G 显存能承受的范围,显存更大可以加到 32,但收益不大;cache=True把图片提前缓存在内存里,减少训练时的磁盘 IO 波动。
训练完成后不要急着看验证集 mAP,先打开runs/elec_v1/目录下的confusion_matrix.png和results.png。如果看到两类之间的混淆比较严重,或者验证集损失在训练后期不降反升,说明样本质量问题优先于继续增加训练轮数。
3.3 真正要调的四组参数:imgsz、增强强度、置信度和 NMS 阈值
首先说imgsz。电梯监控的画面如果原始分辨率很低,比如老旧小区的 704×576,强行用 1280 训练不仅显存压力大,还会让模型学到的目标尺度和实际推理不一致。我的习惯是先看原始画面里一辆电动车占的高度大概是多少像素,如果只有三四十像素,就把imgsz拉到 1280 并配合后面会讲的 ROI 方案;如果电动车的目标高度超过一百像素,960 就足够。
其次是数据增强强度。YOLO 默认开启的scale=0.5、hsv扰动等,对电梯场景不能直接拉满。电梯轿厢空间固定,车辆的尺度变化范围有限,过强的缩放增强会制造出大量“现实中根本不可能出现”的样本。训练时可以手动压低增强参数:
yolo detect train \ model=yolov8n.pt \ data=elec.yaml \ epochs=100 \ imgsz=960 \ scale=0.35 \ degrees=0.0 \ hsv_v=0.4 \ perspective=0.0002参数说明:scale=0.35限制缩放扰动范围;degrees=0.0关闭旋转增强,电梯监控画面永远保持水平和垂直,旋转后的目标形态是无效样本;hsv_v=0.4保留亮度扰动,用来模拟日光灯闪烁和白天黑夜变化;perspective=0.0002轻微模拟广角畸变,数值不要超过 0.001,否则图形变形太夸张。
最后是推理参数conf和iou。训练结束后做测试时,conf默认值是 0.25,电梯场景建议提到 0.35 到 0.45。原因很简单:电梯告警的误报代价比漏报高,把反光里的影子误判成电动车会触发不必要的告警。iou=0.5足够把同一辆车产生的多个重叠框合并掉,不需要单独调。
4. 电梯视角特有的精度优化:畸变、反光、小目标与告警联动
模型跑通以后,真正把项目做到能演示、能交付的部分在于针对电梯场景做的定向优化。电梯监控和普通安防最大的区别是画面构图极度稳定,目标运动路径也相对固定,所以很多通用的通用检测技巧在这里需要反过来用。
4.1 顶置俯视与墙角斜视:先判断镜头位再决定训练策略
不同电梯的摄像头安装位大致分成两类。一类安装在轿厢顶部角落,镜头正对着斜下方,画面上半部分是候梯厅,下半部分是轿厢内部,目标整体呈现接近垂直的俯视视角;另一类安装在门框上方,镜头斜向下倾斜四十五度左右,目标在画面里保留了更多的侧面形态。
这两类视角下的目标宽高比完全不同。如果训练数据全是顶置俯视,推理画面却是门框斜视,模型大概率会把车身拉长的形态当成异常目标处理。处理方法是先看镜头安装位,如果拿到的都是同一种视角,推理阶段遇到另一种视角时,可以用 OpenCV 做透视校正,把斜视画面近似转换成俯视构图再去检测,但这个方法在目标距离变化大时效果有限。
更稳的做法是在数据采集阶段就把两类视角都纳入训练集,哪怕数量不平衡,只要验证集能看见另一类视角的样本,模型就不至于在部署时完全失明。这个点看起来基础,却是换一个电梯就翻车的最常见原因。
4.2 小目标与低分辨率:imgsz 上限、ROI 裁剪与切块取舍
电梯监控录像经过 DVR 压缩后,画面细节损失往往比想象中严重。电动车在候梯厅里时离摄像头较远,目标占画面面积可能只有三四个百分点;一旦推进轿厢,目标面积会瞬间增大几倍。这导致模型要么对远景小目标漏检,要么对近景大目标的边框预测不稳定。
处理小目标最直接的手段是把推理分辨率提到 1280,但老旧 DVR 输出本身就是模拟信号转码来的,硬提分辨率不会凭空恢复细节。更实用的是 ROI 裁剪:电梯告警关心的是轿厢内部和门口那块区域,候梯厅远端本身就不该触发告警。推理前直接把画面裁剪成感兴趣区域,既减少干扰,也变相放大了目标。
import cv2 def crop_roi(frame, top_ratio=0.2): h, w = frame.shape[:2] return frame[int(h * top_ratio):, :]逻辑说明:顶置摄像头画面里,候梯厅通常集中在最上方,top_ratio=0.2表示裁掉顶部两成画面,保留剩余轿厢区域;这个比例需要根据自己监控画面的实际构图微调。注意裁剪之后,模型输出的坐标要换算回原始画面的坐标再用于告警逻辑,否则联动时定位会偏移。
切块推理在这个场景里不推荐。电动车是运动目标,切块会增加检测结果的拼接和跟踪复杂度,收益远小于直接放大推理分辨率。
4.3 由框到告警:面积占比、连续帧确认与楼层联动
检测模型输出的是目标类别和边界框,但电梯告警逻辑不能只看“检测到了电动车”这个信号。一个典型的误报场景是这样的:电梯停在一楼,门外有人推着电动车等电梯,候梯厅的画面上出现完整电动车,模型当然会检出;如果这时直接触发告警,就会在电动车还没进轿厢时误报。
所以我在告警逻辑里加两条约束:目标框面积占整帧画面的比例,以及连续帧确认。真正进入轿厢的电动车,框的面积占比会快速变大,通常超过百分之十五;而在候梯厅等待时,占比可能只有百分之几。连续帧确认则用来过滤光线闪烁、画面抽帧抖动导致的单帧误检。
FRAME_MIN_AREA_RATIO = 0.15 CONFIRM_FRAMES = 3 class AlertConfirmer: def __init__(self, frame_area: int): self.frame_area = frame_area self.count = 0 def feed(self, box_area: int): if box_area / self.frame_area >= FRAME_MIN_AREA_RATIO: self.count += 1 else: self.count = 0 return self.count >= CONFIRM_FRAMES逻辑说明:每次拿到检测结果时计算当前目标框面积和整帧面积的比值,连续三次高于阈值才认为告警成立;一旦中间出现一次不满足,计数清零。代码很短,但能挡掉相当大一部分“远距离目标干扰”和“单帧鬼影”造成的误报。如果项目还对接自动语音播报或电梯门禁,把确认状态输出成一条 MQTT 消息或 HTTP 回调即可,避免在检测脚本里写死硬编码联动逻辑。
5. 避坑手册:这个项目最容易翻车的五个场景
这个章节的内容来自实际做过类似项目的血泪经验。很多问题在训练曲线上看不出来,只有把模型拿到真实电梯环境里验证才会暴露。下面这五类问题出现频率最高。
5.1 换一台电梯就失灵:数据同源带来的过拟合
现象:训练和验证都是同一台电梯的录像,验证集 mAP50 到了 0.9 以上,看起来模型已经很好了;但拿到隔壁楼另一台电梯的监控画面里跑,漏检率突然变得很高。原因:数据全部来自同一个电梯,模型把轿厢的固定背景、灯光色温、地砖纹路都当成有效特征学进去了,换一个视觉环境立刻失效。解决:数据集至少覆盖两个不同电梯,或者用其中一个电梯的数据抽出来当验证集,观察分数是否明显下降。如果下降明显,补数据是第一优先级,调模型参数是第二优先级。
5.2 白天能用晚上崩:夜间帧不是简单加亮度扰动
现象:白天测试一切正常,到了晚上只有微弱灯光或红外补光时,电动车和背景的对比度大幅降低,模型频繁漏检。原因:很多项目偷懒只做亮度归一化和随机亮度增强,但夜间电梯监控往往是黑白画面,伴随红外噪点,第二天白天的彩色特征被完全替代。解决:实际采集夜间视频来补样本,或者把当天晚上的监控录像单独抽帧分析。做数据增强时,可以叠加高斯噪声和 CLAHE 局部对比度增强,但真正的夜间帧无可替代。训练时把白天和夜间数据按比例混合,不要全部分到同一个训练批次里。
5.3 电动车和自行车互相认错:标签体系与标注边界没定死
现象:验证集混淆矩阵里,bicycle和electric_bicycle两类互相错认的比例居高不下,手动看检测结果发现连人都不容易分清。原因:俯视视角下,电动车和自行车的侧面特征被压缩,没有统一标注边界;有人把踏板的电动自行车标成自行车,有人把有脚蹬的两轮车标成电动车。解决:一开始就写明标注规范,脚蹬存在与否、电池仓位置作为分类依据;如果项目验收不强制区分这两类,更推荐的方案是合并成一个大类two_wheeler,把识别问题简化成“有没有两轮车进电梯”,这在实际消防场景里常常更好用。
5.4 不锈钢反光和镜像把模型绕晕:靠 NMS 阈值救不回来
现象:轿厢镜面不锈钢壁板上会出现车辆和行人的虚影,模型在同一帧里检测到两三个重叠程度很高的目标框,或者把虚影认成真实车辆。原因:反光形成的边缘纹理和真实目标非常接近,单纯提高置信度阈值会连真实目标一起过滤掉。解决:一个有效的做法是把训练样本里明显是反光的区域做负样本挖掘,也就是专门截取没有车辆但有反光纹理的画面,标成 background;推理时则可以配合 NMS 的 iou 阈值适当调低到 0.4,把重叠框压掉。最根本的办法还是前面提到的 ROI 裁剪,让检测区域避开最严重的反光区块。
5.5 预训练模型在陌生视角下的误检:投入数据之前先做一轮盲测
现象:刚开始接触项目时,直接拿yolov8n.pt自带权重对电梯视频做推理,结果把消防箱、灭火器、拖把、安全窗全部框出来。原因:COCO 类别里有大量室内物品,电梯画面里的柜子、箱子在平视视角下看起来确实和一些交通工具相似。解决:在标注之前先跑一轮盲测,把输出结果导出成视频,人工看一遍哪些误检是真实常见干扰物。这一步能帮标注阶段提前圈定难例,也能用来评估是否需要把某些反复误检的干扰物加进负样本集合。
6. 评估集设计与落地告警:从 mAP 到真实拦截
6.1 评估集按电梯、时段、机位分层,指标分开看
评估阶段不要只给一个总 mAP。电梯项目的真实边界条件是安装位置和光照,所以评估集至少要按“训练电梯之外的新电梯”“夜间时段”“开门关门两种状态”三个维度划分。每一层单独算 mAP50、误报率 FPR 和每类别的漏检率。这样的表格放在毕设或竞赛报告里,比单张 PR 曲线有说服力得多。
| 评估子集 | 帧数 | mAP50 | 电动车误报率 | 说明 |
|---|---|---|---|---|
| 同电梯白天 | 300 | 0.95 | 2.0% | 理想状态 |
| 新电梯白天 | 300 | 0.78 | 6.3% | 观察泛化能力 |
| 新电梯夜间 | 300 | 0.61 | 11.0% | 夜间的问题所在 |
重点关注新电梯夜间那一行,多数项目的短板会在这里暴露。
6.2 告警回调最小实现:置信度、面积占比与连续帧确认
部署时把检测、确认、告警三层拆开,顺序是模型推理、面积占比过滤、连续帧确认、触发告警。前面给的AlertConfirmer已经覆盖了确认环节,实际使用时还需要在处理每一帧时把原始坐标映射回显示画面。我一般会在回调函数里保存触发告警前三帧的画面,拼接成一张三联图,方便事后审计误报原因。
这个项目值得做的原因也在这里:数据采集、微调、告警联动三块工作要求明确,难度适中,技术细节足够支撑一场答辩。我最早做的一版直接拿 COCO 权重去跑电梯视频,结果第二天就被反光里的拖把上了一课;现在每次接到类似项目,我都会先问一句数据从哪来、视角跟部署是不是同一个。先把这条搞清楚,后面至少省一半返工时间,希望帮到你。
本文还有配套的精品资源,点击获取