电梯开关状态与人员进出检测数据集:2220张图,VOC+YOLO双格式,YOLOv8直接训练
发布时间:2026/10/2 18:44:58来源:尧图网络
简介面向电梯监控场景的计算机视觉目标检测数据集适合训练电梯开关状态与人员进出检测模型的研究者、算法工程师及竞赛爱好者。数据集对应2220张真实场景图片标注兼容Pascal VOC与YOLO两种格式覆盖电梯关闭、电梯内有人、电梯空置、电梯开门四个类别累计标注3330个目标框状态覆盖较全面。压缩包共2000个文件以VOC格式的xml标注文件为主并附使用说明txt整体约77.26MB便于快速下载并接入主流目标检测框架。目前已有548人学习下载既能用于模型训练、算法精度对比也可作为数据增强或迁移学习的参考样本。作者使用labelImg工具完成逐框标注保证标注准确合理不承诺训练精度使用时可根据场景筛选或补充样本。1. 电梯开关状态人员进出检测数据集2220张、4类目标VOC与YOLO双格式落地电梯场景的视觉检测最怕的不是模型不够大而是数据没对准任务。门扇半开时到底算开还是算关人站在轿厢门口犹豫时算进还是算出这类问题在通用目标检测数据集里根本没有答案。这份电梯开关状态人员进出检测数据集用2220张电梯实拍图把问题收敛成4类目标门开、门关、人员进、人员出同时给出VOCXML和YOLOtxt两种标注格式解压出来就能直接喂给YOLOv8或YOLOv5训练不需要自己写转换脚本。适合做电梯监控、智慧楼宇、门禁闸机联动这类项目的工程师也适合刚接触YOLO想拿一个有实际业务语义的数据集练手的人。下面先讲清楚这套数据集的内部结构再给出我实际跑过的训练流程和几个典型踩坑点。2. 数据集结构与标签设计目录骨架、类别定义与两种标注格式对照2.1 目录骨架JPEGImages、Annotations、labels 与 ImageSets拿到压缩包解压后第一件事不是急着开训练而是先看目录结构是否完整。这个数据集沿用了VOC风格的目录布局同时为YOLO训练加了labels目录电梯开关状态人员进出检测数据集VOCYOLO格式2220张4类别.7z ├── JPEGImages/ # 2220张原图jpg格式 ├── Annotations/ # 2220个VOC标注xml ├── labels/ # 2220个YOLO标注txt ├── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txt └── data.yaml # 部分版本自带没有就自己写JPEGImages、Annotations、labels三个目录一一对应每张jpg在Annotations里有一个同名xml在labels里有一个同名txt。xml和txt描述的是同一份标注只是序列化方式不同。VOC标准目录通常长成VOCdevkit/VOC2007那样这个包把labels平铺在根目录下属于YOLO训练常见的目录改法不影响使用。我拿到数据集的习惯是先跑一条命令核对三个目录的文件数是否相等。用ls JPEGImages | wc -l数图片再用同样的命令数Annotations和labels三个数字不一致就直接找发布者要完整包不要自己在缺标注的情况下硬补。2.2 四类标签的边界定义门状态与人员进出的判定基准标题写了4类别具体标签名以Annotations里name字段为准。常见命名是door_open、door_closed、person_in、person_out四种类别ID从0开始编号但不同发布者可能用open、close、enter、leave这类简写先确认再改yaml。这四类的边界在业务上是容易扯皮的我的判定基准如下类别ID标签名判定基准0door_open两扇门扇之间有可见缝隙能看到轿厢内部或井道1door_closed门扇闭合门缝不可见或小于2像素2person_in人员主体在轿厢外朝向门方向移动标注框住人的全身或上半身3person_out人员主体在轿厢内朝门外方向移动标注框住人的全身或上半身人员进出的判定在单帧静图上经常有歧义因为人站在门口犹豫时不好判断朝向。更稳妥的口径是看双脚位置和身体朝向进则身体朝内、脚在门槛外侧出则身体朝外。这个数据集的标注者已经做了统一口径但如果你要自己扩采数据必须先定死这个标准再让标注员动手否则后患无穷。2.3 VOC XML与YOLO txt两种格式的字段对照打开任意一个xml内容大致如下annotation folderJPEGImages/folder filename20240511_103242_001.jpg/filename size width1920/width height1080/height depth3/depth /size object namedoor_open/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin612/xmin ymin240/ymin xmax986/xmax ymax872/ymax /bndbox /object /annotation对应的YOLO txt一行一个目标格式是class cx cy w h四个坐标全部归一化到0到1之间0 0.4161 0.5148 0.1948 0.5852归一化换算公式是cx (xmin xmax) / 2 / widthw (xmax - xmin) / widthy方向同理。VOC的bndbox存像素值YOLO存归一化值转换过程最坑的就是除以图片宽高时的缩放问题。这里有个容易被忽略的细节txt里的class id顺序未必按字母序。发布者可能按自己的标注工具导出顺序编号所以不要凭xml里的name猜测txt第一列数字对应哪一类正确做法是遍历labels目录统计第一列取值再反推classes顺序。提示我一般把xml转成txt之后随机抽几张图把标注框画回去人工看一遍。这一步能同时验证坐标换算和类别对应比训练到一半才发现类别错乱省事得多。3. 用YOLOv8训练这个数据集数据集yaml、超参数选择与训练日志判读3.1 数据集yaml配置与路径组织数据集自带的data.yaml如果存在直接用不存在就自己建一份。yaml内容如下path: /path/to/电梯数据集 train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 4 names: 0: door_open 1: door_closed 2: person_in 3: person_outpath是数据集根目录路径训练机和验证机如果目录不一样记得改这里。train和val可以指向txt列表文件也可以直接指向图片目录。YOLOv8读取train.txt时会逐行把文件名拼到path后面如果txt里存的是JPEGImages/xxx.jpg这样的相对路径注意检查拼接结果是否正确。nc4必须和names的数量严格一致names的索引顺序必须和labels里txt文件的第一列数字对齐。这一条是类别错乱的头号来源错位之后你训出来的模型会把door_open当成person_in从loss上根本看不出来因为loss照样在降只有看混淆矩阵才能发现。注意改yaml时不要动names顺序。如果数据集发布者的类别顺序和你的业务顺序不同宁可改labels里的标注数字也不要改yaml里的names顺序因为labels有2000多个文件批量改数字比手调yaml更容易出错。3.2 训练命令与关键超参数选择环境装好ultralytics之后一条命令就能起训yolo detect train data./data.yaml \ modelyolov8n.pt \ epochs120 imgsz640 batch16 \ lr00.01 patience30 workers4 device0modelyolov8n.pt表示加载COCO预训练权重这是迁移学习的关键不要省略。2220张图属于中小规模数据集从n或s这种小模型起步更合适直接上yolov8l在小数据集上容易过拟合而且训练时间翻好几倍。imgsz640是通用选择电梯门缝和人体都是相对较大的目标640够用如果原始图是1080p且想抓细小的门缝特征可以提到800但显存占用会明显上升。几个关键参数的取舍参考参数建议值说明lr00.01迁移学习常见起点loss震荡降不下去就改0.005batch8~166G以下显存用8配合accumulate2凑足有效batchaccumulate2~4显存不足时用它替代增大batch实际batchbatch×accumulateepochs100~150看验证集mAP是否平台期patience会提前终止patience20~30连续N个epoch验证集无改善就停省时间imgsz640/800越大对小目标越友好但显存和速度代价高训练过程中如果显存报OOM优先降batch而不是降imgsz。降imgsz会把门缝这种细长目标缩得更小检测难度变大降到batch8再开accumulate4是比较均衡的解法。3.3 训练日志怎么读loss曲线与指标训练结束后结果目录下的results.csv记录了每个epoch的完整指标直接用pandas读import pandas as pd df pd.read_csv(runs/detect/train/results.csv, comment#) last df.iloc[-1] print(box_loss:, last[train/box_loss], -, last[val/box_loss]) print(mAP50:, last[metrics/mAP50(B)]) print(mAP50-95:, last[metrics/mAP50-95(B)])判读训练是否健康核心看三个信号train/box_loss和val/box_loss是否同步下降mAP50是否在验证集上稳步上升混淆矩阵里door_open和door_closed是否存在大面积互串。如果train/box_loss一路降但val/box_loss在某个epoch后掉头上升就是过拟合此时模型对训练集的细节记得太死而不是真正学会了门状态特征。我在这个数据集上一般以mAP50达到0.85以上作为第一步合格线mAP50-95达到0.6左右说明模型有一定的定位精度。如果mAP50连0.7都上不去大概率不是训练参数问题而是标注一致性出了问题回到第4章的清洗流程去处理。4. 训练避坑与排查标注一致性、BN崩溃与mAP为0的处置记录4.1 图片与标注数量对不上、XML与txt标签不一致现象启动训练后日志提示大量图片找不到标注文件或者验证集上GT框数量明显偏少。原因压缩包在搬运过程中丢文件或者labels和Annotations不是同一版本导出的。常见情况是有人先导出xml后来又重标了一版txt导致两张目录对不上。解决先跑一遍核对脚本把差集列出来再决定是删图还是补标from pathlib import Path jpg set(p.stem for p in Path(JPEGImages).glob(*.jpg)) xml set(p.stem for p in Path(Annotations).glob(*.xml)) txt set(p.stem for p in Path(labels).glob(*.txt)) print(缺xml:, jpg - xml - txt) print(缺txt:, jpg - txt) print(多出的标注:, (xml txt) - jpg)脚本逻辑很简单对三个目录分别取文件名集合用集合减法找差集。缺标注的图要么删除要么回补多出来的标注文件如果对应的图片不存在要检查是不是文件名编码问题比如jpg是1.jpg而标注是01.jpg。我一般先让脚本输出差集再用ls -la核对文件大小确认不是空文件。4.2 门半开状态被归到哪一类标注边界模糊导致loss不降现象训练后期val/box_loss在某个值附近来回震荡mAP50卡在0.7上不去混淆矩阵里door_open和door_closed互串明显。原因门扇半开时缝隙只有几个像素宽标注员A把它算成open标注员B算成closed标签噪声直接干扰模型收敛。电梯门是金属镜面反光也会让边界更模糊。解决定死判据——门扇之间有可见缝隙就一律算door_open完全闭合才算door_closed。然后重新过一遍标注或者至少把训练集里loss贡献大的难例筛出来复核。我自己的血泪经验是这种业务语义边界问题加loss权重和换模型结构都不如先把标签洗干净标注噪声对mAP的影响远大于模型本身的选择。4.3 BN崩溃与loss为nan现象训练到第20到40轮时train/box_loss突然跳到nan之后所有终端输出全是nan保存的权重推理时输出全为0。也有的情况是BN层的权重出现nan模型直接废掉。原因最常见是lr0设置偏大其次是batch太小导致BN的均值和方差统计量抖动。数据里混入纯黑、纯白或损坏的图片也会触发。在电梯场景中夜间断电时段抽出来的帧可能整张是黑的这类图会让前向传播产生异常值。解决先把学习率砍半重试同时用accumulate把有效batch提上去yolo detect train data./data.yaml modelyolov8n.pt \ epochs100 imgsz640 batch8 accumulate4 lr00.005如果还在nan那就检查图片质量用OpenCV遍历JPEGImages把灰度方差接近0的图剔除import cv2 from pathlib import Path for p in Path(JPEGImages).glob(*.jpg): img cv2.imread(str(p), cv2.IMREAD_GRAYSCALE) if img is None or img.var() 1: print(坏图:, p)注意img is None处理的是解码失败var() 1处理的是全黑或几乎纯色的帧。这部分问题通常在数据侧不要反复调参。4.4 mAP为0或极低但loss在降现象train/box_loss正常下降但每个epoch的mAP50都是0或者验证集结果远低于预期。原因最典型的两个——names顺序和标签ID错位以及val路径指向空目录。另外如果GT框落在图像边缘某些预处理会把超出边界的部分裁掉导致目标消失。解决先写脚本把归一化坐标还原成像素坐标检查是否都在图像范围内w, h 1920, 1080 for line in open(labels/样本.txt): c, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) print(c, x1, y1, x2, y2)如果发现大量框的宽或高小于3像素说明在imgsz640的缩放中被下采样吞掉了。电梯门缝在1080p原图里可能只有5像素宽缩到640后只剩不到3像素模型根本看不出来。这种情况把imgsz提到800或1024并使用s以上的模型才有意义。4.5 验证集混淆矩阵行和总是对不上GT数量现象验证集混淆矩阵里每行的数字加起来不等于该类别真实的GT数量。有人拿着这个结果质疑数据集标注不对以为漏标了。原因YOLO的混淆矩阵默认在置信度阈值下统计置信度低于阈值的预测框直接被丢弃NMS之后同一个GT目标只保留一个匹配框其他重叠预测试图归入background列。所以行和与GT总数对不上是正常的统计口径问题不是标注错误代码里也没有bug。解决看混淆矩阵时先确认文档里conf参数的取值口径判断类别质量要看每类recall是否都处于合理水平以及door_open和door_closed是否互相串扰而不是纠结行和。这个坑我换数据集时认真踩过一次当时花了一下午查标注文件和脚本最后才发现是统计口径白费力气。5. 把数据集改造成自己的任务VOC/YOLO互转、半开状态拆分与数据量边界5.1 VOC转YOLO的转换脚本与边界处理这个数据集本身带了txt但如果你拿到的是纯VOC版本或者想从xml重新导出一份自定义类别的txt转换脚本是刚需。下面这个脚本是我常用的模板import xml.etree.ElementTree as ET from pathlib import Path CLASS_MAP {door_open: 0, door_closed: 1, person_in: 2, person_out: 3} def voc_to_yolo(xml_path, out_dir): root ET.parse(xml_path).getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) if w 0 or h 0: print(fskip {xml_path}: zero size) return lines [] for obj in root.iter(object): name obj.find(name).text.strip() cls CLASS_MAP.get(name) if cls is None: raise ValueError(f{xml_path}: unknown class {name}) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) x1 max(0, min(x1, w)); x2 max(0, min(x2, w)) y1 max(0, min(y1, h)); y2 max(0, min(y2, h)) if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: out_file Path(out_dir) / (Path(xml_path).stem .txt) out_file.write_text(\n.join(lines) \n)逻辑说明先解析size拿到原图宽高再遍历每个object把类别名映射成数字像素坐标转成归一化中心点加宽高。这里有三处必须处理的边界图片宽高为零时直接跳过坐标越界时clip到图像范围内clip之后宽高为负或为零的框丢弃这类框通常是标注失误留下来训练只会产生垃圾loss。参数说明CLASS_MAP的顺序必须和后续yaml的names对齐你要把person_in放第一类就改成{person_in: 0, ...}。抛异常而不是静默跳过未知类别是为了第一时间发现xml里的脏标签静默跳过会让标注错误偷偷溜过去。5.2 给门状态再加一个“半开”类别怎么办有些电梯项目不满足于开/关二分类还想把门扇半开这种危险状态单独检出来。这个数据集本身是4类但你可以基于它二次加工出half_open。思路是先用训好的模型把door_open这类框全抽出来计算每个框的宽高比。电梯门是竖长条全开状态下bbox的宽高比接近固定值半开时宽度变窄比例明显变化。按这个特征做初筛import xml.etree.ElementTree as ET threshold_w_ratio 0.6 # 宽度与全开参考值之比 for xml_file in Annotations.glob(*.xml): root ET.parse(xml_file).getroot() for obj in root.iter(object): if obj.find(name).text ! door_open: continue bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) if w / h threshold_w_ratio: obj.find(name).text half_open注意这个方法只能做初筛阈值需要按你的实际机位角度调节而且必须人工复核一遍。我一般会把初筛结果和原始全开样本的宽高比分布画成直方图用分布上的断点确定划分阈值比拍脑袋定值靠谱。5.3 数据量边界2220张图能训到什么程度2220张图、4个类别每个类别摊下来几百个标注框属于小型专用数据集。这个规模下有两件事需要提前心理建设。第一必须依赖预训练权重做迁移学习。用yolov8n.pt从头微调可以达到可用的检测效果但如果用随机初始化的权重从零训练收敛速度会慢得多mAP也会低不少。这是数据量决定的不是模型参数调得不好。第二这类数据集的场景局限性很强。如果机位固定、电梯型号固定训练出的模型在同一个场景下表现良好换一个摄像头角度或换一款内饰不同的电梯mAP会有明显回落。正确做法是把它当作一个场景的baseline后续通过抽帧补充新场景数据再微调。数据划分上我习惯按80%训练、10%验证、10%测试切分。如果原始数据是从视频里抽帧的相邻帧高度相似切分时务必按时间段分桶不要让同一段视频的相邻帧同时出现在训练集和验证集中否则验证集上的mAP会虚高部署到真实环境立刻现原形。6. 调优技巧用混淆矩阵和置信度阈值扫描把误报压下来6.1 先读混淆矩阵找到误报的主要来源训练完成后第一步不是急着调参而是看runs/detect/train/confusion_matrix.png。重点关注两个点door_open和door_closed是否互相串person_in和person_out之间的混淆方向。电梯门是镜面金属摄像头角度一变门扇反光很容易让模型把关闭状态错判成open这类误报在业务上非常致命会触发联动开门或告警。6.2 置信度阈值扫描流程默认conf0.25不一定适合这个业务场景我用一段脚本遍历阈值找F1平衡点from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) for conf in [0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4]: res model.val(data./data.yaml, confconf, iou0.5, verboseFalse) print(fconf{conf:.2f} mAP50{res.box.map50:.3f} fprecision{res.box.p:.3f} recall{res.box.r:.3f})运行后观察数值conf越高precision越高但recall下降告警变少同时漏检变多conf越低recall上去了但误报刷屏。电梯场景我通常选F1最高的那个阈值如果误报比漏检更不可接受就在F1最高点的右侧再偏高一档。那次我在一个电梯项目里图省事直接用默认conf0.25上线结果轿厢门上的金属反光把door_open误报刷了整整一屏告警物业半夜打电话过来问怎么回事。从那以后每次换数据集、换摄像头角度我都会强制把阈值扫描跑一遍再谈部署这一步花不了十分钟但能省掉一整周的返工。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网