yolov13手机使用检测:数据集、模型训练与PyQt5实现

发布时间:2026/10/2 8:35:30
yolov13手机使用检测:数据集、模型训练与PyQt5实现
简介面向手机使用检测与行为分析研究的YOLOv13-PyQt5完整方案包适合目标检测初学者、行为研究相关开发人员以及需要可视化演示的学生使用。包内包含标注好的目标检测数据集提供yolo格式txt标签与voc格式xml标签已按train、val、test划分并附data.yaml可直接用于yolov5/v8/v9/v10/v11/v12等主流YOLO系列模型训练同时打包训练好的模型和PyQt5可视化界面便于直接观察检测结果、分析手机使用习惯。资源共2000个文件以1980个txt标注文件为主辅以18个md说明文档、1个yaml配置和1个pdf资料压缩包大小约345.16MB。已有35人学习适合结合附带教程快速上手完成从数据准备、模型训练到界面展示的完整流程。1. yolov13手机使用检测数据集、训练好的模型、PyQt5可视化界面到底是什么楼上楼下两个人同时开会楼上在用手机刷短视频楼下以为对方在认真看报表——这种误判就是手机使用检测要解决的问题。标题里这个带数据集、训练好的模型、PyQt5可视化界面的打包项目本质是一套完整的解决方案用目标检测模型识别画面里的人体、手机、手部再靠界面程序把谁在什么时间玩了多久手机记录成可分析的行为报告。它既不是论文里的算法demo也不是单纯的界面皮肤而是把标注、训练、推理、可视化四件事串成了一条流水线。适合谁用也很明确做行为分析相关毕设的学生、做工作室或自习室监督软件的人、想给家里老人或孩子做手机使用提醒的开发者。你把模型和界面拆出来换个场景就能迁移到吸烟检测、离岗检测、宠物看护这些同构任务上。后面所有内容都围绕这个打包项目的三块资产展开数据集怎么构建、模型怎么训练到能用、PyQt5界面怎么把结果变成人看得懂的使用习惯研究数据。2. 检测模型与数据集不是随便找个模型就能检测手机先解决检测什么的问题2.1 为什么手机使用检测选yolo系方案而不是传统检测网络做手机使用检测第一步不是写界面而是选定检测方案。传统做法里Faster R-CNN这类两阶段模型精度出色但在实时视频流上帧率很难撑住SSD虽然快小目标召回率又不够。手机在监控画面里往往只占几十个像素而且是斜着握在手里的角度多变、容易被遮挡。yolo系模型从yolov5训练自己的数据集开始到v8、v9、v11再到标题里的yolov13都是同一套one-stage思路在速度和精度之间给了更好的平衡。具体到yolov13这个叫法大概率是社区fork的后续迭代版本或者某个集成包里的自定义命名。不用纠结版本号本身因为训练和部署的入口都是ultralytics接口加载权重、推理、导出onnx的代码几乎一致。这套方案检测三类目标就够了手机、人手、人脸。人手和人脸的框不是最终目的它们是辅助逻辑——判断手机是否被正在使用需要结合手与手机的相对位置以及人脸是否正对屏幕。提示如果只是在室内固定摄像头场景做检测不用追求大模型。yolo系n/s级别的权重在CPU上也能跑到15-25帧足够行为分析使用。2.2 从零攒手机检测数据集labelme标注、类别定义与json转txt脚本数据集是这套方案里最花时间的部分。常见做法是先自己录视频抽帧或者把公开数据集里的手机、人手、人脸类别抽出来合并。标注工具选labelme或labelImg都行但注意——网上很多教程一上来就装labelImg它在某些Python版本下会触发pyqt5缺失或版本冲突这也是热词里labelme 无法安装 pyqt5经常被搜的原因。其实labelme对pyqt5的依赖是显式声明的装不上通常是pip源里pyqt5版本和Python版本不匹配。标注时类别名我建议用固定小写英文phone、hand、face。不要用中文标签后面转YOLO格式和训练时utf-8编码问题会非常折磨人。每张图上手机可能很小标注时要把框贴着手机边缘宁多框一圈别少框因为模型对边界框的回归本身就有误差。标注完的labelme文件是json格式polygon或rectangle标记。YOLO训练需要的是txt格式归一化坐标所以需要一个转换脚本。这里给一份我常用的转换代码import json import os from tqdm import tqdm # 类别映射顺序固定之后data.yaml里按这个顺序写 CLASS_MAP {phone: 0, hand: 1, face: 2} def convert_labelme_json(json_path, out_txt_path, img_w, img_h): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue points shape[points] # labelme矩形是两点对角坐标 x1, y1 points[0] x2, y2 points[1] # 归一化到0~1 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h w abs(x2 - x1) / img_w h abs(y2 - y1) / img_h # 防止边界溢出YOLO要求值在(0,1)范围内 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) w min(w, 1.0) h min(h, 1.0) lines.append(f{CLASS_MAP[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 批量转换假定jpg和json同名 img_dir dataset/images_annotated out_dir dataset/labels os.makedirs(out_dir, exist_okTrue) for json_name in tqdm(os.listdir(img_dir)): if not json_name.endswith(.json): continue img_name json_name.replace(.json, .jpg) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue # 读取图片尺寸 from PIL import Image img Image.open(img_path) w, h img.size convert_labelme_json( os.path.join(img_dir, json_name), os.path.join(out_dir, json_name.replace(.json, .txt)), w, h ) img.close()脚本逻辑分四步读json、按CLASS_MAP过滤类别、把两点对角坐标换成归一化的中心点宽高、写txt。参数上最需要注意的是img_w和img_h它必须来自原图实际尺寸如果图片被resize过但json里坐标没同步改转换结果会全偏。我在第一次转coco2017数据集结构改过来的数据时就吃过这个亏表格里看起来坐标都正常训练loss死活不降。2.3 数据集划分与类别不平衡不是所有帧都算有效数据标注完成后还要解决三个常见问题。第一是划分。按照train/val 9:1或8:2的比例随机打乱注意同一段视频抽出来的帧要尽量归到同一个集合里否则训练集和验证集画面太像验证指标虚高。做法是抽帧时按视频名分组以组为单位划分不要单帧随机分配。第二是类别不平衡。手机使用检测场景里face出现的频率远高于phone因为人脸始终可见而手机可能只在低头时才入画。如果训练后发现phone这一类精度明显低优先做两件事给phone类多补样本或者在loss里给类别加权重。yolo的cls_loss里可以按instance数量逆向加权这个参数在ultralytics里通过class_weights传入虽然默认不开启但值得一试。第三是无效样本清理。画面里手插兜但没拿手机、人脸转头但没看屏幕这些样本如果全标成hand、face模型会把存在手和存在人学得很强却学不到手机正在被使用这个关键状态。手机使用检测真正要的是行为不是静态存在。所以标注时我建议额外加一个规则手在身体两侧垂着、或者手机在桌面上这两个case要么不标phone要么同时加一个context标签让模型学会区分。3. 用yolo训练自己的数据集目录结构、训练命令与模型验证3.1 训练前的目录结构images和labels必须严格对齐标题里的zip包如果直接解压训练你第一眼要检查的就是目录结构。yolo系训练不管yolov13还是更早版本读取数据的方式固定一个data.yaml指定训练集、验证集路径和类别名然后训练脚本按images和labels两个平行目录去找对应文件。结构一般是dataset/ images/ train/ 0001.jpg 0002.jpg val/ 0003.jpg labels/ train/ 0001.txt 0002.txt val/ 0003.txtdata.yaml写法如下# data.yaml path: dataset train: images/train val: images/val nc: 3 names: [phone, hand, face]几个容易踩坑的点train和val写的是相对path的子路径不要写绝对路径否则换机器训练要改一堆配置。nc必须和CLASS_MAP的长度一致names顺序必须和转换脚本里的CLASS_MAP键顺序一致。很多训练出来loss正常但预测类别全错的案件最后查出来是names顺序和标注txt里的索引对不上。images和labels的对齐还有一层含义每张图片必须有同名txt哪怕txt是空文件。如果某张图没有任何目标但少了一个txt训练时会报警告甚至把这张图跳过数量多了会明显影响epoch的收敛判断。我习惯在转换脚本最后扫一遍images目录给缺失的txt写一个空文件。3.2 训练命令与关键参数从yolov5到yolov13共用的调参经验训练本身不需要写复杂脚本ultralytics库一行命令的事。标题里的yolov13既然走的是ultralytics接口命令基本长这样yolo train modelyolo11n.pt datadata.yaml \ epochs100 imgsz640 batch16 device0 \ projectruns/namephone_use参数说明model预训练权重路径。yolo11n.pt是官方COCO预训练权重迁移到手机检测上收敛速度快很多不要从随机初始化开始训6000张数据也能训但前期loss下降会慢到让人怀疑人生。imgsz输入尺寸。640是默认值手机是中小目标不建议降到320降到320会丢小手机框。batch看显存定。16G显存跑yolo11s可以开到32跑l版本就只能16。如果显存溢出优先降batch而不是降imgsz因为imgsz影响检测精度更明显。device0是GPUCPU训练能跑但非常煎熬不建议。project和name产物输出路径runs/phone_useweights就在这下面。这里顺带回答一个热门问题——yolov13训练和yolov5训练自己的数据集在流程上有区别吗答案是没有本质区别yolov5时代还要手动改yaml文件、py文件ultralytics统一后只需一个配置文件和一个命令。所以网上搜到的老教程里那套数据集放哪、怎么改nc的流程放到现在的v13上完全适用只是命令更精简了。训练过程中的监控点有两个一个是训练集loss是否持续下降另一个是验证集mAP50和mAP50-95的曲线。如果mAP50已经到0.9但mAP50-95只有0.5说明框的定位精度不够这时候不要急着加数据先检查标注框边缘是否贴合、有没有大量框比目标大半圈的情况。手机检测对IoU要求高因为后面界面里要根据手和手机框的IoU判断是否手持框偏了这层逻辑就全错。3.3 训练产物怎么用best.pt、混淆矩阵与导出onnx训练跑完产物都在runs/phone_use/weights/下面。优先使用best.pt而不是last.ptlast.pt是最后一个epoch的权重可能过拟合best.pt是验证集指标最优的保存点。压缩包里说的训练好的模型实际就是指这个best.pt。拿到best.pt先别急部署先看两样东西混淆矩阵和验证集的预测效果图。混淆矩阵在runs/phone_use/confusion_matrix.png重点看phone这一行的对角值。如果phone容易被预测成hand说明两类特征太像——画面里手机经常被手挡住一半模型只能看到手。此时可以考虑合并成一个大类phone_in_hand或者增加遮挡样本而不是继续调参。界面程序要跑实时检测pt格式在CPU上效率一般建议导出成onnx再上生产yolo export modelruns/phone_use/weights/best.pt formatonnx imgsz640导出后得到best.onnxPyQt5界面里用onnxruntime加载它做推理比torch直接用省内存、启动快。导出命令里imgsz要和训练时一致否则输入尺寸变了输出张量解析会出问题。注意onnx导出后一定要检查输出维度yolo系输出是1xNx(5nc)的二维矩阵N是预测框数量不是三维。常见翻车是用老代码按三维解析onnx输出结果NMS出来全是空框。4. PyQt5可视化界面检测线程、绘制框与使用习惯统计4.1 架构先定QThread跑检测主线程管界面别让画面冻结PyQt5可视化界面的核心不是有什么控件而是检测放在哪个线程。新手最容易犯的错误是在界面主循环里一帧帧调模型推理结果是一边拖动窗口一边卡死。PyQt5的事件循环和模型推理的for循环互相抢时间界面会假死。正确架构是三段式QThread子线程读视频帧并推理、通过Signal把检测结果emit到主线程、主线程里QLabel更新画面并绘制框。下面给一个最小可跑的骨架以手机检测为例# detector_thread.py import cv2 import numpy as np import onnxruntime as ort from PyQt5.QtCore import QThread, pyqtSignal class DetectThread(QThread): # 信号原始帧、框坐标、类别、置信度 frame_ready pyqtSignal(np.ndarray, list, list, list) def __init__(self, onnx_path, source0): super().__init__() self.session ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.cap cv2.VideoCapture(source) # source可以是摄像头或视频文件路径 self.running True self.input_w 640 self.input_h 640 def preprocess(self, frame): # 等比例缩放填充灰色边避免拉伸变形 img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w img.shape[:2] scale min(self.input_w / w, self.input_h / h) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(img, (new_w, new_h)) canvas np.full((self.input_h, self.input_w, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] resized # 归一化并转NCHW x canvas.astype(np.float32) / 255.0 x np.transpose(x, (2, 0, 1))[None, ...] return x, scale, new_w, new_h def run(self): while self.running: ret, frame self.cap.read() if not ret: break x, scale, nw, nh self.preprocess(frame) outputs self.session.run(None, {self.session.get_inputs()[0].name: x}) # outputs[0] shape: (1, 5nc, 8400)需要转置用 boxes, scores, class_ids self.postprocess(outputs[0], scale) self.frame_ready.emit(frame.copy(), boxes, scores, class_ids) def stop(self): self.running False self.wait()后处理NMS那段代码长度较大生产代码里我一般复用ultralytics的ops模块这里不展开但逻辑说明两点一是onnx输出的中心点坐标要按letterbox的scale回映射到原图尺寸二是置信度阈值先设0.4、NMS的IoU阈值设0.5这两参数直接影响画框数量放低了满屏框放高了漏检。这份骨架在PyQt5界面设计里属于标准写法。pycharm里调试时如果遇到numpy数组跨线程emit崩溃多半是环境变量没设置在app启动前加上os.environ[QT_QPA_PLATFORM] xcb或根据系统改对应值Linux下这种问题最常见。4.2 主界面代码骨架QLabel画框、表格记录、状态联动主窗口不需要花哨。我常用的控件组合是一个QLabel显示视频、一个QTableWidget记录每次拿起手机的起止时间、一个QTextBrowser显示统计报告。以下是主线程接收检测信号并画框的部分# main_window.py from PyQt5.QtWidgets import QMainWindow, QLabel, QTableWidget from PyQt5.QtGui import QImage, QPainter, QPen, QColor from detector_thread import DetectThread class MainWindow(QMainWindow): def __init__(self): super().__init__() self.video_label QLabel(self) self.table QTableWidget(self) self.thread DetectThread(best.onnx, source0) self.thread.frame_ready.connect(self.update_frame) def update_frame(self, frame, boxes, scores, class_ids): # 在副本上画框避免改到原始帧 disp frame.copy() for box, score, cls in zip(boxes, scores, class_ids): x1, y1, x2, y2 [int(v) for v in box] color (0, 255, 0) if cls 0 else (0, 0, 255) # phone绿其他红 cv2.rectangle(disp, (x1, y1), (x2, y2), color, 2) cv2.putText(disp, f{score:.2f}, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) # 转成QImage显示 rgb cv2.cvtColor(disp, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch*w, QImage.Format_RGB888).copy() self.video_label.setPixmap(QPixmap.fromImage(qimg))这里有个关键细节frame传到主线程后必须copy否则QImage引用的是子线程内存地址主线程重绘时子线程可能已经写了新数据画面会出现撕裂或绿条。这个血泪经验是当初在PyQt5显示html和视频流同时刷新时踩到的。另一个参数是QImage的bytesPerLine必须等于ch*w很多教程写成w三通道时显示颜色错乱排查半天。行为记录逻辑这样接当phone框出现且与hand框的IoU大于0.3判定为正在使用手机在表格里记一条开始时间连续N帧比如10帧都没检测到该状态就记结束时间。这样表格里就有了使用时段的数据后续统计全靠它。4.3 从检测框到使用习惯研究时长、频次、时段分布标题里行为分析和使用习惯研究这个落点靠的是把检测结果变成指标而不是停留在画框。三个最基础的指标一是使用总时长。每次phonehandface三个框同时存在且面积比例合理算一次有效使用累加所有session时长。二是使用频次。一天内触发拿起手机事件的次数这个数比时长更敏感因为很多人是几分钟看一眼手机单次时长短但频次高。三是时段分布。按小时统计使用时长画出柱状图能直接看出晚上20点到23点是高峰。这些指标在PyQt5里用QChart或matplotlib都能画。我建议导出HTML报告而不是截图——用QTextBrowser加载一个自生成的HTML文件里面嵌图表和表格叫日报或周报。这样用户能保存、能打印也更像一套研究工具而不是demo。5. 手机检测与PyQt5可视化避坑安装失败、线程崩溃、坐标错乱排查记录5.1 现象labelme或labelImg装不上提示pyqt5缺失或版本冲突原因labelme在Python3.10以上版本容易与pyqt5的wheels不匹配pycharm环境里更容易复现因为pycharm可能已经装过不同版本的Qt绑定库出现PyQt5和PySide6共存冲突。解决先统一环境。建一个干净的conda虚拟环境指定Python3.9然后按顺序装pip install pyqt55.15.9再装labelme或者直接用pip install labelme[pyqt5]一条命令带出依赖。装完后在终端跑labelme能弹窗说明pyqt5正常。若提示找不到Qt platform插件多半是PyQt5安装不完整重装一次比手动改环境变量省心。如果项目里同时有PySide2卸载掉一个两套Qt绑定在同一进程里必冲突。5.2 现象界面运行几秒后卡死窗口拖动时白屏原因典型的检测推理写在主线程。onnxruntime的session.run在CPU上单帧要50-100毫秒主线程被占住后Qt事件循环无法处理重绘消息表现为画面卡在最后一帧、窗口无响应。解决检测挪到QThread主线程只负责收信号和重绘。注意QThread里不要直接操作任何QWidget连self.video_label.setText都不行必须通过信号把数据传回主线程。另外QThread的stop不要用terminate要设置running标志位并wait超时强制杀掉线程会导致onnxruntime的session句柄泄漏第二次启动检测时直接崩溃。5.3 现象训练和推理都正常但画出来的框偏半个身位原因letterbox预处理与原图坐标没有正确反算。检测模型输入是640x640的填充图输出坐标是填充图坐标系直接映射到原始视频帧就会整体偏移。填充的灰色区域越大偏移越明显在竖屏手机画面上尤其明显。解决记录预处理时的scale和pad偏移量后处理时先减pad再除scale。上面4.1代码里用scale和new_w、new_h就是为了这个。一个校验方法拿一张标注过的原图把检测结果画上去如果框中心和标注中心误差超过5%重点查letterbox的pad方向OpenCV的resize配合copyMakeBorder时左上右下填充量要分别记录。5.4 现象推理速度很快但识别结果全是hand手机框一个都没有原因训练集类别不平衡的典型表现。标注时手部特征太明显手机特征被手部遮挡淹没或者验证集里大量画面是手拿手机靠近镜头训练时模型学会了用手代替手机。解决先看混淆矩阵确认。确认hand确实吃掉phone后针对phone类重新采集样本手机平放桌面、手机在裤兜、手机在通话耳边等边缘case都要补。如果没时间补数据退而求其次在业务逻辑上绕开——界面判定使用手机时要求hand和phone同时出现这样即使phone漏检也不会把单纯抬手误判成用手机。这类问题属于数据问题不是模型结构问题调超参数没有用。5.5 现象程序启动时报cuda error: out of memory或首次推理极慢原因onnxruntime的CPU provider加载时默认优化级别较高首次推理要做图优化耗时可能几十秒cuda out of memory则是因为同时加载了torch的权重和onnx两个模型。解决界面程序里只保留一种推理引擎能用onnx就不加载torch。onnxruntime设置optimization_levelORT_ENABLE_ALL并让session的线程数等于CPU物理核心数首次推理会显著加快。带GPU的机器上优先用CUDAExecutionProvider但要注意显存小的显卡上CUDA和CPU两个provider串行加载也可能OOM指定单provider即可。启动时加一段print日志输出实际用的provider排查为什么我的程序比别人的慢一倍这类问题最快。6. 进阶技巧把检测结果做成时段热力图并用真实使用日志验证精度检测模型跑通、界面能画框之后这个项目就从监控工具进化成研究工具了。一个很实用的进阶做法是生成全天使用热力图——横轴是24小时纵轴是星期几格子里填充颜色深浅代表该时段手机使用时长。热力图比柱状图直观的地方在于它能一眼看出周末和周三的晚高峰差异这恰好是使用习惯研究里最有价值的信息。生成方式不复杂把4.3里记录的session数据存成csv列名用start_time、end_time、duration_sec。用pandas按小时分桶累加得到二维矩阵再用matplotlib的imshow画出来最后嵌入到QTextBrowser加载的HTML报告里。PyQt5显示html这件事本身不难把html字符串直接塞给QTextBrowser的setHtml即可注意带!DOCTYPE html完整结构只传片段会导致样式丢失。验证精度这件事我建议做成人工日志对比法让受试者在当天手写记录每次拿起手机的时刻晚上拿记录和系统统计结果对拍。核心指标看两个——漏检率和误检占比而不是mAP。mAP是模型层面的指标用户不关心用户关心的是我说我只玩了三次手机你统计出六次这种体验。这个方法能帮你暴露一个模型指标看不出来的问题检测框在抖动导致同一次使用被切成了多个短session。解决办法是在行为判定里加最小session时长阈值比如单次使用低于10秒的不计入使用频次防止页面滑动时的瞬时手影干扰。最后说一个我自己的习惯做这类行为分析项目一定要在界面第一屏加一个系统时间校准按钮。检测到的使用时长依赖视频帧时间戳如果摄像头和系统时间不一致所有时段分布图都是错的。我做过一次连续三天的记录热力图每天的高峰时段漂移一小时排查半天发现是摄像头内置时钟快了55分钟。从那以后我把设备时钟同步作为启动检测的第一步。希望这些从数据到界面的经验帮你在做yolov13手机使用检测项目时少走几步弯路。本文还有配套的精品资源点击获取