电梯内电瓶车检测表面看是一个标准的目标检测任务实际做下来你会发现它几乎是安防场景里最“别扭”的角落之一。前段时间我基于YOLOv8搭了一套电梯轿厢的电瓶车识别链路覆盖数据采集、模型训练、中英文双语推理和实时告警并且在几条不同光线条件的电梯监控片段上做了验证。这篇文章就把这条链路完整拆开为什么选择YOLOv8、数据集怎么攒才不会翻车、训练参数怎么调、中英文双版源码如何实现以及部署时文档里不会写的那些细节。做这套方案之前我原本想得很简单电瓶车嘛一个目标检测模型标几千张图训练一跑接上摄像头就完事。真正动手才发现电梯轿厢这个环境几乎把目标检测里典型的坑都占全了俯视广角、强反光、遮挡、光线突变、小目标、红外黑白画面还有一大批长得像电瓶车的东西在旁边捣乱。这篇文章我会把每一步踩过的坑和最终采用的做法都写清楚给准备做类似电梯禁入检测、楼道违停检测、小区安防改造的朋友一个可以直接参考的完整路线。1. 电梯轿厢这个场景先看清需求再谈模型开始选型之前我花了比较多的时间分析电梯里实际会遇到的情况因为电梯场景和普通道路监控的差距非常大直接套通用检测思路会吃大亏。电梯监控一般安装在轿厢顶部的一个角落镜头向下俯拍很多老旧电梯用的是广角甚至鱼眼镜头。这种安装方式带来的第一个问题是视角畸变电瓶车靠近摄像头的时候车体会被拉得很大很变形靠近电梯门的时候又会被压缩成一个小目标。同一个目标在画面不同位置尺度能差出好几倍。第二个问题是光线电梯门一开楼道亮、轿厢暗自动曝光会来回跳不少电梯夜间切红外模式画面直接变成黑白颜色信息全部丢失。第三个问题是强反光电梯不锈钢壁和镜面装饰会把电瓶车的倒影清清楚楚地照出来模型很容易在倒影上又框一个目标。第四个问题是遮挡早晚高峰人挤人的时候电瓶车经常只露出半截车身很多目标检测模型对这种部分可见的情况召回很差。接着看需求。物业真正想要的不是“见到电瓶车就报个警”而是“有人推电瓶车进电梯时能可靠地发现并触发梯控”。这意味着两件事漏检不能有误报也不能多。漏一次电瓶车就上楼了安全管理形同虚设误报太频繁保安每天跑断腿过两周整个系统就被关掉了。所以在后续训练里我始终把召回率Recall放在第一位同时努力压低误报而不是只看mAP涨了多少。选型上我用的是YOLOv8。理由很直接工程化省心。Ultralytics这个库把数据加载、训练、验证、导出、预测全部封装好了一套命令行就能从零训练到模型导出模型从n到x分了几个档低算力的AI盒子和普通服务器都能找到合适的档位推理结果导出ONNX、OpenVINO、TensorRT都很方便接边缘设备不费劲。社区资料也足够多踩坑时能搜到很多讨论。不同模型档位的选择可以简单参考这个经验模型特点适用设备yolov8n速度最快精度略低低算力边缘盒子yolov8s速度和精度比较平衡我优先推荐普通边缘设备yolov8m/l精度更好但推理压力大服务器或高算力设备电梯里其实只有一个目标类别检测任务并不复杂不用上很重的算法实时性反而更重要。我的建议是默认从yolov8s开始如果你的部署设备算力很低再退到n。2. 数据集工程一半是采集一半是“不标什么”电梯场景的数据集是整条链路里最花时间的部分。很多人直接拿公开数据集的motorcycle和bicycle类来训效果普遍不好公开数据集大多是侧视、车身完整、光照良好而电梯里是俯视、畸变、遮挡、夜间黑白迁移之后模型会频繁漏检和误检。2.1 公开数据集能用但只能当辅助我的做法是先在公开数据集里筛出俯视角度、推行状态、车身部分遮挡的图片作为预训练阶段的辅助数据让模型先见一见“各种姿态的车”。核心数据一定要自己采。自己采不一定要真的去改装电梯。最有效的办法是找一台实际运行的电梯把相机或者手机固定在大约1.8米到2.2米的高度用广角模式向下俯拍连续录几天的上下行视频。不用特意摆拍日常出入的真实场景反而对模型最有帮助。我还会顺手在楼道、单元门、地下车库入口这些同样俯视角度的地方补一些素材目的不是识别这些区域而是让模型适应不同的地面材质、墙面颜色和灯光色温免得换个电梯环境就崩。数据量上不用追求夸张按我的经验有目标的正样本1500到2500张负样本再配2000张左右基本能跑到一个可用的基线。关键是多样性不同品牌车型、不同车身颜色、不同遮挡程度、白天黑夜红外都要覆盖。2.2 标注策略单类、遮挡、倒影与负样本标注这个环节最容易出问题我来说几个容易被忽略的点。类别我只设了一个ebike。一开始有人建议我分成电动自行车、电动踏板车、折叠车三个类实测下来并不可取——数据量不够强行细分会把模型的学习精力分散还容易互相误判。统一成一类模型只需要回答“是电瓶车”还是“不是电瓶车”任务简单准确率反而更高。人车重叠的时候标注框要紧贴可见车身不要把推车的人框进去太多车身被完全挡住的部分不要标。特别要注意的是电梯里经常有不锈钢壁和镜面倒影倒影一定不要标。如果标了倒影模型就会学着把倒影当成真车到时候车在门外电梯里的倒影反倒触发报警非常尴尬。另外严重模糊、目标小到几乎看不清的图我建议直接删掉不要硬标。这种样本只会教给模型错误特征弊大于利。负样本是电梯场景的重中之重。最容易误报成电瓶车的有轮椅、婴儿车、自行车、三轮车、行李推车、清洁推车、装修手推车。这些物体在俯视角度下轮廓和电瓶车太像了。我早期测试时模型对轮椅和婴儿车的误报率很高后来把大量这类图片作为背景图片放进训练集专门让模型学习“这些不是目标”误报显著下降。2.3 划分、格式与增强数据准备好之后统一转成YOLO格式每张图片对应一个txt文件内容是归一化的类别和坐标0 0.5234 0.4456 0.2134 0.3210我这里只有一个类别所以每行开头都是0。如果图片里没有目标就不要生成txt文件它会自然作为负样本参与训练。训练集和验证集按8:2划分另外单独留出几十张完全不参与训练的图等模型训完再做最终测试避免验证集被无意中“记住”。数据增强方面Ultralytics默认开了Mosaic、随机翻转等这是好事。但如果你的数据集不大建议把Mosaic概率适当调低或者观察训练曲线发现过拟合就再降。针对电梯夜视摄像头我会额外做一种增强随机把部分训练图转成灰度、降低亮度、加一点噪声模拟红外黑白效果。这样模型不会只依赖车身颜色来识别目标夜间表现会好很多。3. 训练全过程配置文件、训练命令与指标解读数据准备好以后训练本身反而简单但要懂得看指标和迭代方向。3.1 训练前的配置与模型选择先写一个数据配置YAML指向你的数据集路径和类别定义# elevator_ebike.yaml path: ./elevator_ebike train: images/train val: images/val names: 0: ebike注意这里没有写ncUltralytics会根据names自动推断类别数。模型权重我建议用yolov8s.pt作为起点而不是随机初始化。预训练权重已经学到很强的通用特征迁移到电瓶车检测上收敛又快又稳。如果你最后要跑很弱的边缘设备可以先用s训练之后再用训练好的权重作为预训练换成n模型继续微调几轮这样比直接拿n从零训练效果更好。3.2 训练命令与超参说明训练命令看起来很简洁yolo detect train dataelevator_ebike.yaml modelyolov8s.pt epochs200 imgsz640 batch16 device0几个关键参数我解释一下。epochs设150到200Ultralytics默认有早停机制验证集指标连续多轮不涨会自动停止不用太担心训过。imgsz默认640如果你发现电梯门附近的小目标漏检明显可以试着提到960对小目标更友好但训练显存和推理耗时都会涨。batch根据显卡显存调整16到32都可以显存不足就降到8。训练过程中如果数据集量不大我会把数据增强的参数稍微调保守一点避免模型在小数据集上过拟合。尤其是hsv_h、hsv_s这些颜色增强电梯场景本身颜色变化不大过强的颜色扰动反而让模型学到不真实的特征。3.3 成果指标怎么评估R优先P也不能放弃训练结束后Ultralytics会在runs目录下生成results.png里面有loss曲线、Precision、Recall、mAP50、mAP50-95这几条曲线。电梯场景我最看重两个指标Recall和mAP50。Recall决定“推电瓶车进电梯的人有没有被漏掉”mAP50反映基本的框准不准。mAP50-95虽然更全面但对单类别的电梯任务来说要求偏高只要不是差得离谱不用太纠结。看指标的时候不要只盯着mAP。有一种典型情况mAP不错但Recall不高说明模型漏掉了一部分电瓶车这类模型直接上现场是不行的。我会对着验证集的badcase图看找出漏检的模式。常见的有红色车身在红色地砖上被漏掉、深色衣服的人在深色背景下和车融为一体、车身被门框挡住只露出一半。每发现一类漏检就补充对应的训练图这是迭代效率最高的方法。同样误报也要对着混淆矩阵看。如果电瓶车经常被分到“背景”说明模型对某些相似物轮椅、推车还没有分辨清楚这时不是调阈值能解决的要回到数据补充负样本。4. 中英文双版源码实现核心就是标签映射和中文渲染模型训好之后工程实现部分就是标题里说的中英文双版源码。其实模型本身不需要重新训练两遍权重是同一个变化的是推理脚本的标签映射、显示语言和注释说明。4.1 双版本设计一个脚本还是两个脚本我最终采用了“一个主脚本语言参数”的方式而不是做两个完全独立的文件。原因是后续要维护两套逻辑太容易出不一致一个脚本通过--lang zh或--lang en切换干净利落。工程目录大概长这样elevator_ebike/ ├── detect.py # 推理主脚本 ├── best.pt # 训练好的模型权重 ├── fonts/ # 中文字体文件 │ └── NotoSansCJK-Regular.ttc ├── elevator_ebike.yaml └── demo_video.mp4 # 测试视频如果你需要对外发布README和注释也做中英双版脚本本身保持一致。4.2 推理核心Ultralytics的检测结果怎么用推理主循环的关键是理解Ultralytics的检测结果结构。调用model(frame)之后结果对象里有一个boxes属性里面包含xyxy、conf、cls。我一般这样取from ultralytics import YOLO import cv2 import argparse ZH_NAMES {0: 电瓶车} EN_NAMES {0: ebike} def parse_args(): parser argparse.ArgumentParser(descriptionElevator ebike detection) parser.add_argument(--source, typestr, default0, helpvideo path or camera index or rtsp url) parser.add_argument(--weights, typestr, defaultbest.pt) parser.add_argument(--lang, typestr, defaultzh, choices[zh, en]) parser.add_argument(--conf, typefloat, default0.5) return parser.parse_args() def main(): args parse_args() model YOLO(args.weights) names ZH_NAMES if args.lang zh else EN_NAMES cap cv2.VideoCapture(args.source) while True: ret, frame cap.read() if not ret: break results model(frame, confargs.conf, verboseFalse) for result in results: for box in result.boxes: x1, y1, x2, y2 [int(v) for v in box.xyxy[0].tolist()] conf float(box.conf[0]) cls_id int(box.cls[0]) label f{names[cls_id]} {conf:.2f} cls_id和names的映射关系就是中英文切换的关键。这里有个容易忽略的细节box.cls[0]拿到的是类别索引不是字符串。模型本身并不知道自己检测的是“电瓶车”还是“ebike”名字全靠我们自己映射。中英文双版的核心就是两个字典ZH_NAMES和EN_NAMES推理逻辑完全不用动。4.3 中文标签渲染cv2.putText不行的替代方案在电梯监控画面上叠加中文标签最直接的做法是cv2.putText但OpenCV这个函数对中文支持很差画出来是乱码或者一排方框。原因是OpenCV底层用的字体渲染库默认不包含中文字形这个问题我用PIL桥接解决。基本思路是先用OpenCV画检测框然后把整帧图像从BGR转成RGB交给PILPIL用中文字体文件把文字画上去最后转回BGR。代码大致是这样from PIL import Image, ImageDraw, ImageFont import numpy as np FONT_PATH fonts/NotoSansCJK-Regular.ttc def draw_det(frame, x1, y1, x2, y2, label, langzh): cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) if lang en: cv2.putText(frame, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) return frame # 中文标签走 PIL 渲染 pil_img Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(pil_img) font ImageFont.truetype(FONT_PATH, 24) # 画一个背景条避免文字叠在复杂背景上看不清 bbox draw.textbbox((x1, y1 - 28), label, fontfont) draw.rectangle(bbox, fill(0, 255, 0)) draw.text((x1, y1 - 28), label, fontfont, fill(0, 0, 0)) return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)字体文件是这里的关键。Windows下可以直接用C:/Windows/Fonts/msyh.ttcLinux下安装Noto CJK字体后路径一般在/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc。部署到边缘盒子时记得把字体文件一起打包进去。性能方面PIL桥接比纯OpenCV绘制要慢一些我实测下来对帧率有影响但在普通PC上不至于卡顿。如果边缘设备性能吃紧可以只在检测到目标的那一帧做中文渲染其他帧只画框或者干脆业务中只发中文告警消息画面用英文或ID编号显示不让PIL参与每帧绘制。4.4 事件联动连续帧确认与告警回调实际使用中只把标签画到画面上远远不够电梯场景要想真正可用必须加事件联动。单帧检测结果直接报警会非常吵。比如某个角度下一辆儿童推车被误检成电瓶车只出现一帧立刻报警这就是一次误报。我采用的办法是连续帧确认连续3帧或5帧都检测到电瓶车置信度都超过阈值才判定为有效事件。这样单帧闪烁式的误检被过滤掉代价是会引入几百毫秒到一两秒的延迟但电梯场景完全可以接受。事件解除也有讲究。电瓶车离开画面后如果马上解除报警会出现“电梯门一关目标消失报警跟着消失”的抖动。我一般设置目标消失超过2秒才拉闸解除期间保持报警状态这样后续对接梯控“阻止关门”的逻辑才稳定。报警回调可以做成这样def on_alarm(started_at, ended_at, camera_id, confidence): payload { event_id: short_uuid(), camera_id: camera_id, start_time: started_at, end_time: ended_at, confidence: round(confidence, 3), type: ebike, lang_code: args.lang } # 推送到物业后台可以用 HTTP 接口也可以用 MQTT requests.post(http://your-backend/api/alarm, jsonpayload, timeout1)实际部署时报警延迟和误报之间需要你根据现场情况做权衡。如果物业要求“响了就必须有人”那就把阈值调高、确认帧数调多如果物业更在意“绝不能漏”那就把确认帧数降少多牺牲一些误报率也要保召回。5. 实测效果与部署避坑从“能跑”到“稳定用”的距离模型能跑出框和能在现场稳定运行中间隔着一大堆部署细节。我把自己实测和打磨过程中最值得说的部分放在这里。5.1 效果演示的做法与评估视角标题里写了“附效果演示”我拿到训练好的模型后会录几类典型视频片段白天正常光线、傍晚弱光、夜间红外黑白、早晚高峰多人遮挡分别跑一遍推理把输出保存成带检测框的MP4。同时在控制台输出每一帧的推理耗时方便评估实时性。评估的时候不建议只看平均帧率我观察的是一段时间内有没有明显的掉帧以及误报和漏报有没有集中在某一类场景。比如白天可能一切正常一到夜间红外模型把轮椅当成了电瓶车这种场景性问题光看数字是发现不了的一定要把每一条报警日志和对应视频帧拉出来看。我自己的测试体感是白天光线充足时yolov8s在这类电梯数据集上基本能做到稳定识别夜间黑白画面下表现会有所下降但如果训练时加了灰度模拟增强也能保持在一个可以用的水平。具体数字因数据集和场景差异会很大建议你以自己验证集的结果为准。5.2 边缘部署与模型导出优化训练好的PyTorch权重不能直接扔进边缘设备一般先导出成ONNX或者OpenVINO格式yolo export modelbest.pt formatonnx yolo export modelbest.pt formatopenvino导出的文件可以直接用ONNX Runtime或OpenVINO推理推理速度比PyTorch原生快很多。如果你的边缘设备是Intel平台OpenVINO是首选如果是NVIDIA的Jetson系列可以再试TensorRT。部署上还有一个容易被忽略的点摄像头取流。很多电梯用的网络摄像头走RTSP协议OpenCV的cv2.VideoCapture可以直接拉流但网络一抖就容易卡死需要在代码里做断线重连不能指望一次连接永久有效。另外不要每帧都新建VideoCapture对象这是很常见的新手错误开销巨大。小目标漏检方面除了把imgsz从640提到960我还会用固定ROI裁剪。电梯摄像头位置固定画面里只有轿厢内部是有效区域走廊里走过去的自行车根本不用管。裁剪掉无用区域等于变相放大目标再配合合适的阈值漏检会降低不少。5.3 现场最容易翻车的五个问题我把部署调试过程中最常遇到的问题整理成一个速查表方便你现场对照排查现象可能原因处理方式轮椅、婴儿车频繁误报负样本不足模型没见过这些相似物补充对应负样本重新训练或适当提高置信度阈值人推车进电梯时漏检人车重叠车在画面中占比太小imgsz提到960加ROI放大降低置信度配合连续帧确认夜间红外漏检模型依赖颜色特征红外黑白下失效训练时增加灰度、降低亮度、加噪增强镜面倒影框出两个目标标注时把倒影标成了目标重新标注剔除倒影追加倒影负样本限制检测ROI中文标签显示成方框缺中文字体文件或误用了OpenCV的putText改用PIL中文字体渲染确认字体文件路径存在除了表里这几类还有一个现场经常遇到的问题报警频率太高。系统刚上线时置信度阈值设在0.3几乎每天误报几十次我把阈值提到0.5以上再配合连续帧确认误报才降下来。阈值怎么选要结合验证集上的PR曲线和现场实际误报率综合判断不是越高越好。如果阈值太高漏检率会上升电梯场景承担不起这个风险。做完整套方案我最大的体会是模型结构只占整个项目三分之一的工作量数据和场景适配至少占一半。YOLOv8本身很容易训练出九十几的mAP但在那个看起来很高的数字背后真正决定现场好不好用的是负样本够不够、红外增强有没有做、报警去抖逻辑合不合理、中文字体有没有打到包里这些“杂事”。如果你也想做类似的项目我的建议是从你身边最熟悉的一部电梯开始先录几天真实视频哪怕只是用手机固定拍摄把日常进出场景攒下来然后边标注边训练边看badcase迭代几轮之后得到的模型一定比网上任何通用模型都更贴合你的场景。这个方法适合电梯检测也同样适合楼道违停、小区出入口管理这类视觉安防场景。