简介:面向目标检测与行为识别开发者,一套专为“打电话/玩手机”场景设计的数据集,支持YOLOV11格式标注,可覆盖手持打电话、非接触式打电话、玩手机自拍等细分行为,适合用于安防监控、驾驶舱监管或课堂状态分析等场景。资源包共2000个文件,其中包含724张jpg原始图像、1275个txt标注文件及1个yaml配置文件,总大小51.36MB,标注格式与YOLO系列模型直接兼容,便于快速投入训练与验证。从内容预览来看,图片多来自视频抽帧与实拍画面,涵盖多样角度与光照条件,标注信息完整,可有效提升模型对不同姿态和远近目标的泛化能力,用户只需按YOLOV11要求调整路径即可使用。当前已有851人学习下载,数据集经过预处理与格式整理,省去自行采集标注的繁琐环节,适合需要快速建立行为识别模型的初学者与实战项目开发者。
1. 打电话玩手机识别:别被“手持检测”骗了,真正的难点是这三类目标
很多刚接触这个领域的人,接到“打电话玩手机识别”的需求后,第一反应都是直接训练一个手机目标检测器。但实际落地时你会发现,光有手机框根本判不了行为:非接触式打电话时手机可能在支架上、在肩膀上甚至拿在手里但没贴耳朵,而玩手机自拍和手持打电话的视觉表现又高度重叠。这份支持 YOLOv11 格式标注的数据资源,把目标拆成了手持打电话、非接触式打电话、玩手机自拍三类,省掉了我最头疼的标注整理环节。拿到手之后,我花了不少精力做格式校验、调训练参数、跑推理验证,中间踩了不少坑。这篇文章就把从数据检查到最终可部署推理的完整过程写下来,给正要在这类行为检测场景里用 YOLOv11 的工程同学做个参考。
2. YOLOv11 格式的数据集:先检查标签,再谈训练
2.1 目录结构和标签格式:txt、类别ID到底怎么对应
YOLOv11 的标签格式和 YOLOv5/v8 完全一致,一个图片对应一个同名.txt文件,文件里每一行代表一个目标框。标准的写法是:
class_id center_x center_y width height这里center_x、center_y、width、height都是归一化到 0~1 的值,而不是像素坐标。如果你想直接用这份数据集训练,第一步就要确认目录结构是不是符合 Ultralytics 引擎的默认约定。一般来说,下载下来的数据包会是这样:
phone_play_dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ └── val/ │ ├── img_1001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_0001.txt │ │ └── ... │ └── val/ │ ├── img_1001.txt │ └── ... └── data.yamldata.yaml里最重要的就是nc和names。对于这个项目,通常会用三个类别,我建议在训练前先明确:
| 类别 ID | 名称 | 行为定义 |
|---|---|---|
| 0 | hold_phone_call | 手持打电话,手机贴在耳边或保持在耳旁 |
| 1 | handsfree_phone_call | 非接触式打电话,手机支撑在固定位置,人体未手持 |
| 2 | phone_play_selfie | 玩手机/自拍,手机拿在手里操作或举高自拍 |
这套定义相对清晰,但真正用的时候会发现一个麻烦:类别 0 和类别 2 在物理形态上都是“手里有手机”,如果你把标注框打在手机上,模型根本分不出“打电话”和“玩手机”。所以这类行为识别数据集,最常见、也最容易训练成功的做法是:把标注框打在“人”身上,用人的动作和手机姿态联合语义来区分类别。也就是说,标签里center_x这类坐标对应的往往是人的包围框,而不是手机的包围框。
我一般拿到数据集之后,不会急着建data.yaml,而是先做一个简单的标签可视化:把标注框和类别名直接画回图片上。肉眼判断匹配工作区背景中的真实场景,避免后续训练的时间被浪费。
2.2 用一段脚本检查标注是否可用,避免白练一场
标签文件本身是纯文本,格式错了不会报错,只会在训练时莫名丢失目标。我见过不少现象:训练完 val 的 mAP 有数字,但推理结果却和原始图片对不上。这种大多是标签坐标写错、类别 ID 越界,或者存在空文件。
每次拿到数据,我都会先跑一个校验脚本。下面的代码可以检查出大部分常见问题:
import os from pathlib import Path def yolo_label_validate(labels_dir, images_dir): """ 遍历 YOLO 格式标签文件, 检查格式、类别 ID 和坐标范围。 labels_dir: 标签目录 images_dir: 对应图片目录 """ label_files = sorted(Path(labels_dir).glob("*.txt")) class_names = [ "hold_phone_call", "handsfree_phone_call", "phone_play_selfie", ] empty_files = [] bad_lines = [] for txt_path in label_files: # 标签文件命名为 tmp.jpg -> tmp.txt img_path = Path(images_dir) / (txt_path.stem + ".jpg") if not img_path.exists(): bad_lines.append(f"{txt_path}: 对应图片不存在 {img_path.name}") lines = txt_path.read_text(encoding="utf-8").strip().splitlines() if not lines: empty_files.append(str(txt_path)) continue for line_no, line in enumerate(lines, 1): parts = line.split() if len(parts) != 5: bad_lines.append(f"{txt_path}:{line_no} 列数不是 5") continue cls_id = int(parts[0]) if cls_id < 0 or cls_id >= len(class_names): bad_lines.append(f"{txt_path}:{line_no} 类别越界: {cls_id}") continue cx, cy, bw, bh = map(float, parts[1:]) if bw <= 0 or bh <= 0: bad_lines.append(f"{txt_path}:{line_no} 宽高非法: {bw} {bh}") # 归一化后的中心点越界,说明坐标没有换算干净 if not (0.0 <= cx <= 1.0 and 0.0 <= cy <= 1.0): bad_lines.append(f"{txt_path}:{line_no} 中心点越界: {cx} {cy}") print(f"标签文件总数: {len(label_files)}") print(f"空标签文件: {len(empty_files)}") print(f"可疑行数: {len(bad_lines)}") if empty_files: print("空标签文件示例:") for f in empty_files[:10]: print(" ", f) if bad_lines: print("可疑行示例:") for b in bad_lines[:20]: print(" ", b) if __name__ == "__main__": validate_yolo_labels("phone_play_dataset/labels/val", "phone_play_dataset/images/val")这段脚本的逻辑很简单,却有效。第一,校验每个 txt 是否都对应一张图片,避免训练时因为图片没读进来,导致某个类别的样本量失真。第二,检查列数,YOLO 格式如果写成class_id x1 y1 x2 y2,坐标是像素值,在官方训练器里会被当成归一化坐标直接算,很多时候不会报错,但模型就废了。第三,检查归一化坐标是否落在 0~1 区间,像中心点出现负数或大于 1,基本都是转换脚本没写好。
脚本执行后如果空文件比你预想的多,别急着删除。空标签意味着这张图片没有目标,通常来自背景帧或难样本。在训练集里保留一部分空文件,能让模型学会“没有目标时不乱框”,减少误检。
2.3 数据分布和类别策略:手持、非接触、玩手机怎么划分
这个数据包叫“超高识别率”,但识别率的前提是数据分布接近你的真实场景。三类目标里,非接触式打电话是最容易翻车的类别。因为“非接触”意味着手机不在人手里,那么人形姿态可能完全正常,只有桌面上某个小物品是手机。如果标注框打在人身上,模型相当于要去记忆“这个人正在打电话”的上下文,这类样本必须足够多。
玩手机和自拍看上去容易,但实际很难和手持打电话区分。以我见过的标注标准,两者互斥的原则通常是:手机在耳边附近,且人处于通话姿势,归为手持打电话;手机在视线下方、胸前或举过头顶进行滑动、浏览、拍摄,归为玩手机自拍。如果你的现场需求里还有“吸烟”“揉眼睛”等动作,混淆的概率会更高。
当你发现数据分布偏得厉害时,我的做法是先画一张类别样本数量图,用条形图统计每个类别在不同角度、不同光照下的数量。如果一个类别的样本只有另一个类别的五分之一,哪怕整体识别率虚高,部署时这个类别的召回也会很差。实在没有更多数据,就先合并类别,把手持打电话和玩手机自拍合为“使用手机”,等数据充足后再拆分,边界线会清晰很多。
3. 跑通 YOLOv11 训练:从 data.yaml 到一条命令出权重
3.1 Ultralytics 环境配置:版本和依赖的边界
YOLOv11 在 Ultralytics 框架里跑起来非常省事,但环境上一个常见的坑就是版本冲突。官方仓库更新很快,如果你的环境里装了旧版torch或opencv-python,训练时可能直接编译失败,也可能在推理时莫名其妙地报CUDA内存错误。
一般我会按下面的流程配置一个干净环境:
python -m venv yolo11_env source yolo11_env/bin/activate pip install --upgrade pip pip install ultralytics yolo version这里yolo version是为了确认安装成功,也方便后面定位问题。如果你的机器是 Windows,把source yolo11_env/bin/activate换成yolo11_env\Scripts\activate即可。GPU 环境里的 PyTorch 建议单独装,尤其是 CUDA 版本不一致的时候,直接用pip install ultralytics拉到的 PyTorch 可能不是本地驱动对应的版本。我的经验是:先把torch、torchvision按官网命令装好,再装ultralytics,能省掉很多“训练前 10 分钟才发现显卡不可用”的无用操作。
3.2 训练参数怎么定:imgsz、epochs、batch 的选择逻辑
拿到一份外来的数据包,我的第一个习惯不是直接开训,而是先在data.yaml里把路径写对。路径里的相对路径是相对于data.yaml所在目录还是相对于工作目录,很多人会搞混。为了避免这种问题,建议直接用绝对路径,或者把整个数据包放到工程目录下,用相对路径./phone_play_dataset:
# phone_play_dataset/data.yaml path: ./phone_play_dataset train: images/train val: images/val test: images/test nc: 3 names: 0: hold_phone_call 1: handsfree_phone_call 2: phone_play_selfie然后就可以开训了。我的起步命令长这样:
yolo detect train \ data=phone_play_dataset/data.yaml \ model=yolo11s.pt \ imgsz=640 \ epochs=200 \ batch=16 \ patience=50 \ device=0参数并不需要全部调整,但有几个关键点需要理解,特别是对新手:
| 参数 | 说明 | 我的建议 |
|---|---|---|
model | 预训练模型文件 | 先yolo11n.pt试跑通,稳定后换成yolo11s.pt或yolo11m.pt |
imgsz | 输入图片尺寸 | 场景里人的像素高度小于 50 时,建议 1280,否则 640 就够 |
epochs | 训练轮数 | 数据集几千张时,200 轮左右能收敛;数据量小则配合早停 |
batch | 每批图片数 | 显存不够就减半,能接受训练时间变长 |
patience | 早停耐心轮数 | 验证集指标连续多少轮不涨就停,我一般设 50 |
device | 训练设备 | 0代表第一张 GPU,纯 CPU 环境填cpu |
imgsz的选择对行为识别影响很大。你的现场摄像头如果是 300 万像素的高空视角,一个人可能只有 30 像素高,这时候你用 640 训练,下采样后人的细节基本全没了。我更倾向于把imgsz调到 1280,第一次训练可以先跑 50 轮看速度,确认单卡能接受,再跑完整训练。另一个被忽略的点是batch:batch 太小,BN 层的统计量不稳定,模型在小数据集上会来回震荡;batch 太大,显存不够时会自动降级为梯度累积,速度反而变慢。所以不要盲目往上加。
3.3 训练后第一时间看什么指标
训练结束不是看最后的loss有多低就行。YOLOv11 在训练过程中会在runs/detect/train/下生成results.csv和一堆图,我最关注的是混淆矩阵和每个类别的 PR 曲线。对于这个数据包,手持打电话类别的 AP 通常最高,玩手机自拍次之,非接触式打电话最低。如果你发现非接触式的 AP 低于 0.5,别急着调模型,先去看这个类别的标注框是否把手机支架上的手机也框进去了。如果标签框位置主要落在身体上,而测试数据中的“非接触式打电话”人形和训练集中差异很大,AP 自然上不去。
验证阶段还需要单独跑一波,不要只看训练时内嵌的 val 结果:
yolo detect val \ model=runs/detect/train/weights/best.pt \ data=phone_play_dataset/data.yaml \ imgsz=640跑完后查看confusion_matrix.png。如果“非接触式打电话”大量被预测成“玩手机自拍”,说明两个类别的姿态样本区分度不够。这时候不是改网络,而是要回到数据本身,增加两个类别在同一机位下的独立样本,并且要保证标注标准严格互斥。很多情况下,同一个动作被两个标注员标成了不同类别,这种“标注语义漂移”在行为检测里比模型结构问题更隐蔽。
4. 推理脚本:把模型变成“打电话/玩手机”事件输出
4.1 基础检测推理
训练完成之后,最好的验证方式是直接跑一段视频流,而不是只对几张图片看效果。YOLOv11 的 Python API 接口很简洁,下面的代码是把训练好的权重跑在一段测试视频上:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="test_video.mp4", imgsz=640, conf=0.35, iou=0.5, device=0, save=True, save_txt=True, )代码里conf=0.35是核心参数。它对三类目标的影响不同:手持打电话的检测置信度往往在 0.5 以上,非接触式打电话因为姿态不明显,很多真实框只能跑到 0.25~0.4 的置信度。如果你为了保召回把conf调到 0.2,那么误检的杯子、显示器、反光贴纸也会跟着出来。我的建议是先分别在conf=0.2、0.3、0.4下跑同一段视频,观看预测结果,再选择一个对现场需求最友好的阈值。
4.2 后处理:按类别统计违规事件
在大多数管理场景里,用户不只是要“看到框”,而是想要“谁在违规”。所以推理脚本需要把连续帧里的检测结果转换成一次事件。最简单的方法是在帧计数上做统计:目标连续出现 N 帧,才算一个有效事件。
import cv2 from ultralytics import YOLO from collections import defaultdict model = YOLO("runs/detect/train/weights/best.pt") cap = cv2.VideoCapture("camera_02.mp4") fps = max(int(cap.get(cv2.CAP_PROP_FPS)), 1) # 只统计目标出现过的帧数 counters = defaultdict(int) # 已经触发的事件,避免同一人重复计数 event_flags = defaultdict(bool) while True: ret, frame = cap.read() if not ret: break results = model(frame, conf=0.35, iou=0.5, verbose=False)[0] current_frame_events = set() for box in results.boxes: cls_id = int(box.cls) class_name = model.names[cls_id] current_frame_events.add(class_name) for cls_name in current_frame_events: counters[cls_name] += 1 # 连续出现超过 0.5 秒,认为一次真实事件 for cls_name, count in list(counters.items()): if count > int(fps * 0.5) and not event_flags[cls_name]: print(f"{cls_name} 事件触发,持续帧数 {count}") event_flags[cls_name] = True elif count == 0: event_flags[cls_name] = False cap.release()这段逻辑的价值在于过滤单帧误检。拿一个水杯的误检来说,它可能在某两帧里置信度很高,但不会连续出现半秒钟。通过时间维度的过滤,能把大量单帧噪声消掉。需要留意的是,counters这个字典没有区分画面里的多个人。如果现场会有多个人同时出现在视野里,你需要先做目标跟踪或按框的中心点位置分配 ID,否则统计会混在一起。基础版先用classes名称做事件判断,对单人场景完全够用。
4.3 延时与性能:非接触式打电话的难点
行为检测的实时性,很多人会踩到同一个坑:用 1080p 视频流喂给 YOLOv11 做全图推理,GPU 负载很高,延迟也比较大。事实上,监控场景里人的尺寸通常只占画面的很小一部分,更推荐的做法是通过一个轻量级的行人检测器先圈出人的区域,再把每个区域裁剪后送给 YOLOv11 做行为分类。这样做有两个好处:第一,输入图片变小,推理速度显著提升;第二,行为识别模型看到的是“放大的完整人像”,而不是远处的一个小目标。
非接触式打电话在这种流程里仍然是最难的。因为如果手机没在手上,裁剪出来的人像里手机占比很小,模型可能要依赖“手机放在桌面上 + 人头部微侧”这种弱特征,这类特征很容易被姿势变化干扰。实际部署时,最好给非接触式打电话保留一个独立的摄像头角度,比如正对办公桌或驾驶室仪表盘方向的机位,会比一个斜上方全局视角可靠得多。
5. 避坑和排查:模型为什么总把水杯当手机
5.1 现象:训练完误检多,水杯、耳机、键盘都被框成玩手机
现象:跑测试视频时,桌面的保温杯、耳机盒、甚至键盘反光区域都被标成了phone_play_selfie,置信度还在 0.4 左右。
原因:训练集中缺乏负样本,也就是“画面里没有打电话/玩手机行为”的帧。模型被动学习了大量“手在桌面上拿着东西”的背景特征,把类似的小型物体都激活了。
解决:从现场监控视频里抽几段空白场景,截取几百张没有人或人没有使用手机的帧,放到训练集的images/train里,并保留为空的.txt标签文件。这样让模型接触更多真实负样本,误检会明显下降。同时可以把推理conf从 0.2 提到 0.35,过滤掉低置信度噪声。
5.2 现象:夜间红外场景下,手机和手部轮廓全部糊在一起
现象:白天测试 mAP 能到 0.8,晚上切到红外摄像头后召回率下降到 0.3,手持打电话根本框不出来。
原因:训练数据大概率是白天自然光图片,红外图像丢失了颜色信息,且手部和手机的温度差异在画面上表现成灰色纹理接近的区域。YOLO 系列模型默认的 HSV 增强只针对颜色空间的小变换,对红外这种全图灰度化场景效果有限。
解决:在训练时增加灰度化增强,把hsv_v提到 0.5,或者直接对训练集做灰度预处理,生成一部分单通道复制件。更有效的是采集现场红外样本做半天的增量训练。可以用少量红外图片加上原来数据联合训练,注意红外图要先用标注脚本转成同样的 YOLO 格式,不要漏掉标注框。
5.3 现象:类别不平衡,模型总把玩手机识别成手持打电话
现象:验证集显示hold_phone_call的 AP 很高,但实际画面里一个人在低头刷手机,模型却给了“hold_phone_call”的框。
原因:手持打电话和玩手机自拍在姿态上本来就是连续过渡的,如果数据集中手持打电话样本多,模型会把所有“手举着手机”的相似特征都归到这个多数类里。另外,很多标注在“手机接触脸颊”和“手机举在胸前”之间的边界不一致。
解决:先做一次标签统一,明确拿手机到耳边才算手持打电话,否则一律归玩手机。如果已经统一过,仍然失衡,就在训练时给玩手机类别的图片在upsample层面简单复制两倍,而不是改变损失函数权重。因为改动 YOLOv11 的 loss 权重需要动框架源码,对工程上线不划算,不如直接调整数据比例。
5.4 现象:换一个摄像头角度,精度立刻崩掉
现象:A 摄像头俯视角训练出来的模型,换到 B 摄像头平视视角,检测结果几乎全漏。
原因:这是过拟合到了训练集特定的拍摄角度和人物尺度。特别是行为识别任务,同一个“非接触式打电话”在俯视视角和水平视角下的视觉特征完全不同,模型没有学到通用的“打电话”概念。
解决:随机增强参数里打开翻转、旋转和透视变化。Ultralytics 默认的degrees=0.0,也就是不做旋转。建议设置degrees=10,translate=0.2,scale=0.5,fliplr=0.5。另外在验证集里必须隔离开不同摄像头的数据,不能把同一个摄像头的帧同时放进训练集和验证集,否则看到的指标全是假的。
5.5 现象:训练时 loss 还在下降,但 mAP 到了某个点就不再涨
现象:训练到 120 轮时 loss 还在持续下降,但验证集 mAP 一直停在 0.73 左右,无论加多少轮都上不去。
原因:模型对小样本类别的特征表达已经饱和。YOLOv11n 的容量有限,在非接触式打电话这类弱特征类别上尤其明显。也可能是imgsz=640且检测框很小,网络下采样后目标特征丢失。
解决:先换更大的预训练模型,比如yolo11m.pt,同时把输入尺寸提升到imgsz=1280。如果显存不够,则改用迁移学习,只冻结骨干层前几个 stage,释放后面的学习率。另一个隐藏原因是验证集里标注框本身存在错误,低质量的标签会把 mAP 压在一个虚高水平。建议抽 200 个验证样本重新核对一遍,把明显错标的数据剔除。
6. 更近一步:用强增强和置信度融合提高现场泛化能力
6.1 增强参数调整:不要用默认值直接跑
默认训练参数在公开数据集上表现不错,但换到行为识别现场场景,还是偏保守。我比较常用的做法是在命令里显式增加增强项:
yolo detect train \ data=phone_play_dataset/data.yaml \ model=yolo11s.pt \ imgsz=1280 \ epochs=150 \ batch=8 \ degrees=10 \ translate=0.2 \ scale=0.5 \ fliplr=0.5 \ hsv_v=0.5其中hsv_v=0.5是为了模拟不同亮度和红外灰度场景,处理后的图在灰度层面变化更大,对夜间监控更友好。degrees=10虽然会增加训练难度,但能明显提升不同安装角度下的稳定性。
6.2 对检测结果按时间段做事件判定
单独一帧的置信度不如连续帧的“多数投票”可靠。我在最终交付时习惯用一个长度为 15 帧的滑动窗口:窗口内超过 10 帧出现同一个类别,才输出一次事件。这样既不会漏掉短暂抬头看手机的瞬间,也能过滤掉喝水、玩笔等误检。实现方式简单,用collections.deque(maxlen=15)可以轻松完成。
6.3 ONNX 导出与现场验证
训练结束后,把权重导出为 ONNX 格式,方便接入现有的 C++/Java 推理服务:
yolo export model=runs/detect/train/weights/best.pt format=onnx dynamic=True imgsz=640导出后需要重新验证一次,因为 ONNX 的预处理和后处理和 PyTorch 原生推理存在细微差异。从那以后,我每次换场景都会固定走一遍“数据校验 → 短训练 → 全量训练 → 多阈值推理 → ONNX 复验”的流程,不再依赖训练日志里那个 mAP 数字。希望你拿到这份资源后,也能在你的现场场景里少走这些弯路,希望帮到你。
本文还有配套的精品资源,点击获取