简介:本资源是一套完整的YOLOv5打电话行为检测实战项目,面向计算机视觉初学者与安防、交通监管等场景开发者,解决日常监控中对手机使用行为的自动化识别需求。压缩包共165个文件,含34个核心Python脚本(含训练/推理/界面逻辑)、27个配置yaml文件(模型结构与超参)、26张标注图像及8张界面截图,另有4个预训练.pt权重、4个PyQt设计.ui文件、2个实测视频(mp4)及CSV结果统计文件,整体大小为433.91MB。已有600人学习下载,项目已通过CSDN博文公开验证效果。用户可直接运行PyQt图形界面,一键完成图片、视频及摄像头实时检测;配套提供txt与xml双格式标注数据集,支持快速迁移训练;内容预览显示包含TensorBoard日志、Docker部署文件及完整实验记录(results.csv),兼顾算法复现、工程部署与结果分析全流程。
1. 项目缘起:从“打电话检测”需求到技术选型
最近在做一个智慧安防相关的项目,其中有一个很具体的需求:在特定区域(比如工厂车间、实验室、考场)自动检测人员是否在违规使用手机打电话。这个需求听起来简单,但真做起来,你会发现它横跨了目标检测、模型训练、界面开发甚至部署优化等多个环节。市面上现成的解决方案要么太“重”,集成了一堆用不上的功能;要么太“轻”,只给个模型,前后端都得自己从头搭。
经过一番调研和折腾,我最终选择用YOLOv5作为核心检测模型,并配套完成了从数据集准备、模型训练、到PyQt桌面应用封装的全流程。今天就把这个完整的项目——“YOLOv5打电话行为检测系统”的构建过程、踩过的坑以及最终的成果分享出来。这不仅仅是一个训练好的模型和一堆代码,更是一套可以复现的、包含图形界面的端到端解决方案。无论你是想直接应用这个“打电话检测”功能,还是想学习如何将一个AI想法落地成一个可交互的桌面软件,这篇文章都能给你提供一条清晰的路径。
2. 核心问题拆解:什么是“打电话行为检测”?
在开始动手之前,我们得先明确我们要检测的“打电话”到底是什么。这可不是简单的“检测到手机”就完事了。
2.1 行为定义与视觉特征
打电话是一个典型的“人-物交互”行为。在视觉上,它通常表现为:
- 头部姿态:头部向一侧倾斜,耳朵贴近某物体。
- 手部姿态:一只手(或双手)举起,靠近头部。
- 目标物体:手中或耳边持有一个小型矩形物体(手机)。
因此,我们的检测任务可以分解为两个层次的目标检测:
- 一级目标:人(Person)。这是行为的主体。
- 二级目标:手机(Cell Phone)。这是行为交互的关键物体。
单纯的“手机”检测不足以判定为“打电话”,因为手机可能被拿在手里、放在口袋或桌上。而“打电话”这个行为,本质上是**“人”与“手机”在空间上的一种特定组合关系**。最直接的判定逻辑是:检测到“人”和“手机”两个目标框(Bounding Box),并且手机框的位置处于人脸侧方区域(大致在耳朵附近)。
2.2 技术实现路径选择
明确了目标后,我们有几种技术路径可选:
- 姿态估计 + 目标检测:先用姿态估计模型(如YOLO-Pose, MMPose)识别人体的关键点(如左手腕、右手腕、左耳、右耳),同时用目标检测模型识别手机,然后通过关键点与手机框的空间关系(如距离)来判定。这种方法最精准,但模型复杂,计算量大,对数据集要求高(需要带关键点标注)。
- 纯目标检测(多标签):只使用目标检测模型,但定义一个新的类别,例如“person_calling”。我们需要收集大量人正在打电话的图片,并统一标注为这个类别。模型直接学习这种复合特征。好处是推理速度快,一步到位;缺点是模型可能难以区分“打电话”和“手托腮思考”等相似姿势,且需要专门的数据集。
- 目标检测 + 空间关系后处理(本项目采用方案):分别检测“人”和“手机”两个独立类别。在推理后,添加一个后处理逻辑:对于每一个检测到的“人”框,寻找其附近(如IoU交并比或中心点距离在一定阈值内)的“手机”框。如果找到,且手机框的中心点落在人脸侧方的某个预设区域内,则判定此人正在“打电话”。这个方案平衡了精度和复杂度。我们利用YOLOv5强大的通用目标检测能力,后处理逻辑简单清晰,且“人”和“手机”的公开数据集非常丰富,便于我们训练一个高精度的基础模型。
基于快速落地和效果可控的考虑,我选择了第三种方案。它的 pipeline 非常清晰:YOLOv5检测 → 目标关联 → 行为判定 → 结果可视化/告警。
3. 数据集准备:寻找与制造“打电话”的图片
任何AI项目,数据都是基石。对于我们的方案,我们需要的是同时包含“人”和“手机”的图片,并且最好有相当一部分是“人正在打电话”的图片。
3.1 公开数据集的利用与局限
我首先搜索了主流的公开数据集:
- COCO (Common Objects in Context):包含“person”和“cell phone”类别,且样本量巨大。这是训练基础检测模型的最佳选择。我们可以直接用YOLOv5在COCO预训练模型上微调,快速获得一个能较好检测人和手机的模型。
- 其他专用数据集:如“人头、人肩、人身识别数据集”可能有助于提升对人的检测精度,但缺乏手机标签。“BDD100K”等自动驾驶数据集含“人”但“手机”样本极少。
然而,公开数据集中的“手机”图片,大部分是手机单独放在桌上、手里握着但没打电话的场景。直接训练出的模型,对于“手机贴在耳边”这个特定姿态的识别能力可能不足。因此,补充“打电话”的特写场景数据至关重要。
3.2 自制数据集的采集与标注
为了弥补公开数据集的不足,我通过以下方式自制了数据集:
- 场景模拟拍摄:在办公室、会议室、走廊等不同光照背景下,请同事模拟接打电话的姿势,从正面、侧面、背面等多个角度进行拍摄。注意涵盖不同距离(全身、半身、特写)、不同衣着、不同手机型号。
- 网络图片爬取(注意版权):在遵守版权和隐私规定的前提下,可以从一些免费图库或特定场景的公开视频中截取含有打电话行为的图片。
- 数据标注:使用LabelImg或CVAT工具进行标注。这里的关键是标注策略:
- 对于“人”,标注整个人体边界框。
- 对于“手机”,无论是否在打电话,只要可见就标注。对于贴在耳边的手机,框要尽可能紧贴手机,即使它被手或头发部分遮挡。
- 不需要标注“打电话”这个行为类别,我们只标“person”和“cell_phone”。
最终,我的数据集混合了约70%的COCO相关图片和30%的自制“打电话”场景图片,总计约5000张图片。按照YOLO格式整理,目录结构如下:
dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 └── labels/ ├── train/ # 对应的YOLO格式标签文件 (.txt) └── val/每个标签文件(.txt)内容示例:
0 0.5 0.6 0.1 0.2 # 类别0(person) 中心点x,y 宽w 高h (归一化坐标) 67 0.52 0.55 0.05 0.08 # 类别67(cell_phone) COCO中手机类别ID是67注意:如果你的自制数据集中“手机”类别ID与COCO预训练模型不一致,需要在模型配置文件(
data/coco.yaml)中正确修改nc(类别数)和names列表。为了简化,我建议在自制数据集中,将“人”和“手机”的类别ID分别映射为0和1,并相应地调整模型配置。
4. YOLOv5模型训练:从零开始与迁移学习
有了数据,就可以开始训练模型了。YOLOv5的工程化做得非常好,让训练变得相对简单。
4.1 环境搭建与YOLOv5下载
首先,准备好Python环境(建议3.8+),安装PyTorch(>=1.7)。然后克隆YOLOv5官方仓库:
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这个过程可能会遇到一些包版本冲突,特别是opencv-python和torchvision。一个稳妥的做法是先安装PyTorch,再安装requirements.txt中除torch和torchvision以外的包。
4.2 数据配置文件准备
在yolov5/data/目录下,创建我们的数据集配置文件call_phone.yaml:
# 数据集路径 path: /path/to/your/dataset # 根目录 train: images/train # 相对于path的路径 val: images/val # 类别数 nc: 2 # 类别名称 names: ['person', 'cell_phone']4.3 模型选择与超参数调整
YOLOv5提供了从轻量到高精度的多个模型(yolov5s.pt,yolov5m.pt,yolov5l.pt,yolov5x.pt)。考虑到最终要集成到桌面应用,我选择了在精度和速度上平衡较好的yolov5m.pt作为预训练模型。
训练命令如下:
python train.py --img 640 --batch 16 --epochs 100 --data data/call_phone.yaml --cfg models/yolov5m.yaml --weights yolov5m.pt --name phone_detection关键参数解析:
--img 640: 输入图像尺寸。更大的尺寸通常带来更好的精度,但会显著增加显存消耗和推理时间。640是一个常用的平衡点。--batch 16: 批大小。根据你的GPU显存调整。如果出现CUDA out of memory错误,就减小这个值或--img尺寸。--epochs 100: 训练轮数。可以通过观察验证集损失曲线来提前停止。--weights yolov5m.pt: 加载COCO预训练权重。这是迁移学习的关键,能极大加速收敛并提升最终性能。--name phone_detection: 本次训练运行的名称,所有输出(模型、日志、图表)会保存在runs/train/phone_detection/下。
4.4 训练过程监控与调优
训练开始后,可以通过TensorBoard查看各项指标:
tensorboard --logdir runs/train需要重点关注的指标有:
train/box_loss,train/obj_loss,train/cls_loss: 训练损失,应稳步下降。metrics/precision,metrics/recall: 验证集上的精确率和召回率。我们希望两者都高且平衡。val/box_loss: 验证集损失,是判断模型是否过拟合的关键。如果训练损失持续下降而验证损失开始上升,说明可能过拟合了。
如果发现验证集精度不高怎么办?
- 检查数据质量:这是最常见的问题。用YOLOv5提供的
utils/plots.py脚本可视化你的训练数据,看看标注框是否准确、是否覆盖了所有目标。 - 增加数据增强:在
data/hyps/hyp.scratch-low.yaml中可以调整增强参数,如mosaic,mixup的概率,以及色彩、旋转、缩放等。适度的增强能提升模型鲁棒性。 - 调整类别权重:如果“手机”的样本远少于“人”,可能会导致手机检测不佳。可以在损失函数中为“手机”类别设置更高的权重,但这需要修改YOLOv5的源码,相对复杂。更简单的办法是过采样含有手机的图片。
训练完成后,最佳模型会保存在runs/train/phone_detection/weights/best.pt。我们可以用这个模型进行推理测试。
5. 行为判定逻辑开发:从检测框到“打电话”事件
拿到一个能精准检测“人”和“手机”的模型后,下一步就是编写后处理逻辑,将两个独立的检测框关联起来,判定打电话行为。
5.1 核心关联算法
推理后,YOLOv5会输出一个检测结果列表,每个结果包含(x1, y1, x2, y2, confidence, class),分别是框的左上角、右下角坐标、置信度和类别ID。
关联算法的伪代码如下:
def detect_calling(persons, phones, iou_thresh=0.3, ear_region_ratio=0.25): """ persons: list of person detections [x1,y1,x2,y2,conf,cls] phones: list of phone detections [x1,y1,x2,y2,conf,cls] iou_thresh: 人框和手机框IOU的阈值,用于初步筛选“可能持有”的手机 ear_region_ratio: 定义人脸侧方区域宽度占人脸框宽度的比例 """ calling_persons = [] for p in persons: px1, py1, px2, py2, p_conf, _ = p person_bbox = [px1, py1, px2, py2] # 1. 找出所有与此人框IOU大于阈值的手机(可能被手拿着的手机) nearby_phones = [] for ph in phones: phone_bbox = [ph[0], ph[1], ph[2], ph[3]] if calculate_iou(person_bbox, phone_bbox) > iou_thresh: nearby_phones.append(ph) # 2. 如果没有附近手机,跳过 if not nearby_phones: continue # 3. 计算人脸的粗略位置(假设人脸在上半部分) face_height = (py2 - py1) * 0.4 # 假设人脸占头部区域的40%高度 face_y1 = py1 face_y2 = py1 + face_height face_width = px2 - px1 # 定义左右耳区域:人脸两侧的矩形区域 ear_region_width = face_width * ear_region_ratio left_ear_region = [px1 - ear_region_width, face_y1, px1, face_y2] right_ear_region = [px2, face_y1, px2 + ear_region_width, face_y2] # 4. 检查附近手机的中心点是否落在耳侧区域 for ph in nearby_phones: phone_center_x = (ph[0] + ph[2]) / 2 phone_center_y = (ph[1] + ph[3]) / 2 # 判断点是否在矩形内 if point_in_bbox(phone_center_x, phone_center_y, left_ear_region) or \ point_in_bbox(phone_center_x, phone_center_y, right_ear_region): calling_persons.append((p, ph)) # 记录此人和对应的手机 break # 找到一个即判定为打电话,跳出循环 return calling_persons这个逻辑的关键在于两个阈值:iou_thresh和ear_region_ratio。它们需要根据你的实际场景进行调整。例如,在远景中,人和手机可能没有重叠(IoU为0),此时可以改用中心点距离(手机中心点到人头中心的距离小于人头框宽度的一半)作为关联条件。
5.2 优化与误判处理
在实际测试中,我发现了一些误判情况并做了优化:
- 情况一:手机在手中,但未贴近耳朵(如看手机)。此时手机中心点通常落在人脸正前方或下方区域,不会被耳侧区域捕获,从而被过滤掉。
- 情况二:多人密集,手机误关联。A在打电话,B站在旁边,B的手机检测框可能与A的人框有IoU。解决方法:在关联时,除了IoU,还考虑手机框中心点距离人框中心点的距离,选择距离最近且符合耳侧区域的手机。
- 情况三:侧脸或背面。此时“耳侧区域”的假设可能失效。一个更鲁棒的方法是使用一个简单的头部方向估计(例如,基于人脸关键点或人头宽高比),动态调整“耳侧区域”的位置。
我的经验是:在室内固定摄像头场景下,简单的“IoU+耳侧区域”规则已经能达到90%以上的准确率。对于复杂场景,可以引入一个轻量级的姿态估计模型来定位手腕和耳朵关键点,计算距离,但这会增加系统复杂度。
6. PyQt界面开发:将AI模型封装成桌面应用
模型和逻辑都准备好了,接下来就是打造一个用户友好的界面。PyQt是一个功能强大的GUI框架,非常适合用来包装我们的检测系统。
6.1 界面设计与功能规划
我设计的界面主要包含以下几个区域:
- 视频/图像显示区:主画面,用于实时显示摄像头视频流或图片的检测结果。
- 控制面板:
- 模型加载按钮
- 摄像头选择/开关按钮
- 图片/视频文件选择按钮
- 检测开始/停止按钮
- 置信度阈值、IoU阈值调节滑块
- 结果显示区:
- 实时显示检测到的“人”、“手机”数量。
- 列表显示当前“打电话”的人员(可以用框ID或位置标识)。
- 告警日志(如“检测到打电话行为!”并记录时间)。
- 参数配置区:可以调整行为判定的参数(如耳侧区域比例、关联距离阈值等)。
6.2 多线程架构:防止界面卡死
这是PyQt开发AI应用最关键的技巧。YOLOv5的推理(尤其是加载模型和运行检测)是计算密集型任务,如果在主UI线程中执行,会导致界面完全卡住,无法响应。
解决方案是使用QThread:
from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectionThread(QThread): # 自定义信号,用于将检测结果(带标注的图片、统计信息)传递回主线程 result_ready = pyqtSignal(np.ndarray, dict) def __init__(self): super().__init__() self.model = None self.cap = None self.running = False self.conf_thres = 0.25 self.iou_thres = 0.45 def run(self): while self.running and self.cap.isOpened(): ret, frame = self.cap.read() if not ret: break # 进行YOLOv5推理 results = self.model(frame, size=640) # 提取检测框,运行我们的行为判定逻辑 detections = parse_results(results) calling_events = detect_calling(detections['persons'], detections['phones']) # 在原图上绘制框和标注 annotated_frame = draw_results(frame, detections, calling_events) # 准备返回的数据 info = {'person_count': len(detections['persons']), 'calling_count': len(calling_events)} # 通过信号发送结果,主线程的槽函数接收并更新UI self.result_ready.emit(annotated_frame, info) # 控制一下帧率,避免过度消耗CPU self.msleep(30) def stop(self): self.running = False self.wait()在主窗口类中,我们实例化这个工作线程,将按钮点击信号连接到线程的启动/停止方法,并将线程的result_ready信号连接到更新UI的槽函数。这样,界面就能保持流畅,同时实时显示检测画面。
6.3 模型加载与推理集成
在PyQt应用中加载YOLOv5模型:
from models.experimental import attempt_load import torch class MainWindow(QMainWindow): def __init__(self): super().__init__() # ... 初始化UI ... self.detection_thread = DetectionThread() def load_model(self): model_path, _ = QFileDialog.getOpenFileName(self, "选择模型文件", "", "PyTorch Model (*.pt)") if model_path: # 加载模型,设置设备 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.detection_thread.model = attempt_load(model_path, device=device) self.detection_thread.model.to(device).eval() # 将模型的其他参数(如stride、names)也传给线程 self.detection_thread.model_names = self.detection_thread.model.module.names if hasattr(self.detection_thread.model, 'module') else self.detection_thread.model.names这里使用attempt_load函数可以兼容不同版本的YOLOv5模型。加载后,将模型设置为评估模式(eval())并转移到GPU(如果可用)。
7. 项目集成与打包:从代码到可执行文件
当所有功能都在PyQt界面上跑通后,最后一步就是打包,生成一个可以分发给没有Python环境的用户使用的.exe文件。
7.1 使用PyInstaller打包
PyInstaller是最常用的工具。首先安装它:pip install pyinstaller。
为我们的项目创建一个简单的打包脚本build.spec,或者直接使用命令行。由于项目依赖了PyTorch、OpenCV、PyQt5等大型库,直接打包会非常慢且容易出错。关键是要正确地收集这些库的依赖项。
一个相对可靠的命令行如下(在项目根目录下执行):
pyinstaller --name="PhoneCallDetector" ^ --windowed ^ --onefile ^ --add-data="path/to/yolov5;yolov5" ^ --hidden-import=torchvision.models ^ --hidden-import=PyQt5.sip ^ --collect-all=ultralytics.yolov5 ^ --paths="your/python/site-packages" ^ main.py参数解释:
--windowed: 不显示控制台窗口。--onefile: 打包成单个exe文件。--add-data: 将YOLOv5的源代码目录(包含models/,utils/等)打包进去,因为我们的代码运行时需要导入这些模块。--hidden-import: 显式告诉PyInstaller一些它可能找不到的隐式导入。--collect-all: 对于像ultralytics.yolov5这样的包(如果你是用pip安装的yolov5),需要全部收集。--paths: 添加Python的site-packages路径,确保能找到所有安装的包。
踩坑实录:直接打包十有八九会失败,最常见的错误是“Failed to execute script”或运行时找不到模块。我的经验是:
- 在一个干净的虚拟环境中,严格按照
requirements.txt安装依赖。 - 先尝试打包一个最简单的、只导入必要库(torch, cv2, PyQt5)的脚本,确保基础环境没问题。
- 逐步增加功能模块,每加一个就打包测试一次,定位是哪个模块引入的问题。
- 对于YOLOv5,可能需要手动在生成的exe同级目录下创建
yolov5文件夹,并把源码放进去,然后在代码中使用sys._MEIPASS来定位这个路径(PyInstaller打包后,临时解压目录的路径)。
7.2 打包后的路径问题与资源管理
在开发时,我们通常使用相对路径(如./models/best.pt)来加载模型。但打包成单文件exe后,这些路径就失效了。PyInstaller会将资源文件解压到一个临时目录,路径存储在sys._MEIPASS中。
因此,在代码中加载模型或配置文件时,需要做路径判断:
import sys import os def resource_path(relative_path): """ 获取资源的绝对路径。用于PyInstaller打包后定位资源。""" if hasattr(sys, '_MEIPASS'): # PyInstaller创建的临时文件夹 base_path = sys._MEIPASS else: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) # 使用方式 model_path = resource_path("models/best.pt")同时,在打包命令中,也要用--add-data把模型文件、图标等资源加进去:--add-data="models/best.pt;models"。
8. 模型优化与部署思考
虽然我们已经有了一个可用的桌面应用,但在实际部署中,还会面临性能、精度和稳定性的挑战。
8.1 模型轻量化与加速
yolov5m.pt模型在RTX 3060上跑1080p视频可能能达到30fps以上,但在CPU或低算力设备上就会很慢。可以考虑以下优化:
- 模型剪枝与量化:使用PyTorch的量化工具(如
torch.quantization)将FP32模型转换为INT8模型,可以大幅减少模型体积和提升CPU推理速度,精度损失通常很小。 - 更换更小模型:直接使用
yolov5s.pt甚至yolov5n.pt进行训练和部署。对于固定场景、目标尺度变化不大的情况,小模型往往也够用。 - 使用TensorRT或ONNX Runtime:将PyTorch模型导出为ONNX格式,然后利用TensorRT(NVIDIA GPU)或ONNX Runtime进行推理优化,能获得显著的性能提升。YOLOv5官方提供了
export.py脚本支持导出ONNX。
8.2 针对嵌入式设备的部署(如RK3568, RV1106)
标题热词中提到了RV1106、RK3568等嵌入式芯片。将YOLOv5部署到这类设备是另一个热门方向,通常流程是:
- 模型转换:将PyTorch模型通过ONNX,转换为设备厂商提供的推理框架支持的格式,如RKNN(瑞芯微)、NNIE(海思)或Tengine。
- C++/Python SDK集成:在嵌入式设备上,使用厂商提供的SDK编写推理代码。这通常需要交叉编译环境。
- 性能调优:调整输入分辨率、使用量化模型、利用硬件AI加速单元(NPU)是提升嵌入式端性能的关键。例如,对于RV1106,可能需要将输入分辨率从640x640降到320x320甚至更低。
这个过程比桌面端复杂得多,涉及到芯片特定的工具链和大量的底层优化。如果你的最终目标是嵌入式部署,那么在模型训练阶段就要考虑输入尺寸和算力约束。
8.3 持续改进:数据迭代与主动学习
模型上线后,总会遇到新的误检和漏检。建立一个简单的数据回流机制非常重要。可以在PyQt界面中添加一个“保存误检样本”的按钮,将出错的图片连同当前的预测结果保存下来。定期整理这些“困难样本”,重新标注后加入训练集进行迭代训练,模型的鲁棒性会越来越强。
这个“YOLOv5打电话行为检测+PyQt界面”的项目,从需求分析到最终打包,涵盖了AI项目落地的多个核心环节。它不仅仅是一个工具,更是一个如何将算法、工程和用户体验结合起来的范例。代码和训练好的模型我已经整理好,希望这个详细的构建过程能帮你少走弯路。在实际使用中,最重要的还是根据你的具体场景去调整数据、模型参数和后处理逻辑。
本文还有配套的精品资源,点击获取