基于骨骼识别的危险动作报警系统设计与实现
基于骨骼识别的危险动作报警分析系统
【包含内容】
【一】项目提供完整源代码及详细注释
【二】系统设计思路与实现说明
【三】基于骨骼识别算法的实时危险行为预警方案
【技术栈】
①:系统环境:Windows 10/11、macOS Ventura、Ubuntu 20.04
②:开发环境:Python 3.9+、Visual Studio Code/PyCharm
③:技术栈:OpenCV、Mediapipe、PySide6、NumPy、SciPy、Pillow、Pydub/Sounddevice
【功能模块】
①:视频采集模块:获取摄像头实时视频流,支持多摄像头选择与切换,计算实时帧率
②:骨骼检测模块:基于Mediapipe Pose模型实时识别人体33个关键骨骼点,提供坐标和可见性数据
③:危险判断模块:支持危险区域入侵检测和多种危险动作识别(弯腰、举手、失衡、蹲下、奔跑)
④:音频警报模块:本地化生成多种差异化警告音效(蜂鸣声、警笛声、扫频声、啁啾声),多音频后端兼容
⑤:交互界面模块:基于PySide6构建直观友好的操作界面,支持实时监控显示和交互式危险区域绘制
【系统特点】
① 基于计算机视觉的非接触式监控,无需佩戴特殊设备即可实现人体行为分析
② 支持自定义多边形危险区域,通过交互式GUI实现直观的区域绘制和管理
③ 多种危险动作检测算法相结合,提高安全预警的全面性和准确性
④ 本地化音频警报生成,无需依赖在线API,提高响应速度和系统独立性
【核心技术】
① 基于Mediapipe Pose的实时人体骨骼点检测技术,识别人体33个关键点的位置和可见性
② 基于几何向量计算的多种危险姿势判断算法,采用余弦定理计算角度和相对位置关系
③ 基于OpenCV的区域入侵检测技术,应用点多边形测试和关键点权重计算
④ 基于NumPy和SciPy的本地音频警报生成技术,实现多种模式的差异化警告声
【应用场景】
① 工业生产环境:监控工人弯腰姿势、高空作业等风险动作,预防工伤事故
② 危险区域管控:划定机械运转区域、高电压区域等危险区域,防止人员误入
③ 安全管理系统:监控员工不规范行为,如工地奔跑、不规范操作等
④ 特殊场所监控:如实验室、仓储区域等需要严格行为规范的场所
【拓展服务】
① 部署+150
② 如果有数据需要+100
摘要
随着工业化和自动化的快速发展,工作场所的安全问题日益突出,传统的安全监控手段往往存在响应不及时、覆盖范围有限、人力成本高等问题。为了提升安全监控的智能化水平和预警能力,本文设计并实现了一套基于计算机视觉和人体骨骼识别技术的危险动作报警系统。该系统利用摄像头实时捕捉监控区域的视频流,通过Mediapipe框架精确识别画面中人员的骨骼关键点,进而分析人体的姿态和动作。系统集成了危险区域入侵检测和多种典型危险动作(如弯腰过度、手臂高举、身体失衡、异常蹲下、快速奔跑等)的识别算法。一旦检测到潜在危险,系统将立即触发视觉和本地生成的音频警报,提醒相关人员注意。本文首先阐述了研究背景与意义,介绍了计算机视觉、骨骼识别等相关技术;接着进行了详细的系统需求分析和可行性分析;然后重点阐述了系统的总体架构设计、各功能模块(视频采集、骨骼检测、危险检测、用户界面、音频警报)的设计思路以及核心算法的实现细节;随后展示了系统的具体实现过程,包括开发环境配置和关键代码实现;最后,通过设计测试用例对系统功能和性能进行了测试与评估。测试结果表明,该系统能够有效识别设定的危险场景,实时性和准确性基本满足预期要求,具有一定的实际应用价值。
关键词: 骨骼识别;计算机视觉;危险动作检测;Mediapipe;实时报警;人机交互
1. 引言
1.1 研究背景与意义
在现代工业生产、建筑施工、仓储物流以及日常管理等众多场景中,人员安全始终是关注的重中之重。意外事故的发生不仅可能造成人员伤亡和财产损失,还会对企业的正常运营和社会稳定带来负面影响。传统的安全监控方式,如人工巡查、固定摄像头录像等,虽然在一定程度上起到了监督作用,但往往存在诸多局限性。人工巡查受限于人力成本和覆盖范围,难以做到全天候、无死角的监控,且容易因疲劳、疏忽等因素导致漏报、误报。传统的视频监控系统大多停留在事后追溯的层面,缺乏实时分析和主动预警的能力,难以在危险发生的初期进行有效干预。因此,开发一种能够自动、实时、准确识别潜在危险并及时发出警报的智能化监控系统,对于提升安全管理水平、预防事故发生具有重要的现实意义和应用价值。
近年来,随着人工智能和计算机视觉技术的飞速发展,特别是深度学习在图像识别、目标检测等领域的突破性进展,为智能化安全监控提供了新的技术途径。人体骨骼识别技术作为计算机视觉的一个重要分支,能够通过分析视频或图像中的人体关键点信息,精确地估计人体的姿态和动作。这项技术不依赖于特定的服装或配饰,对光照变化、部分遮挡等具有一定的鲁棒性,非常适合应用于复杂环境下的行为分析。将骨骼识别技术应用于安全监控领域,可以实现对人员行为的深层次理解,例如判断人员是否进入禁止区域、是否做出危险动作(如攀爬、跌倒、异常停留等),从而实现更精准、更智能的安全预警。
本研究旨在利用先进的计算机视觉和人体骨骼识别技术,设计并开发一套能够实时检测危险区域入侵和特定危险动作的智能报警系统。该系统通过摄像头捕捉实时视频流,利用Mediapipe等成熟的骨骼识别框架提取人体关键点信息,并基于这些信息设计和实现了一系列危险场景的检测算法,包括非法区域入侵以及弯腰过度、手臂高举、身体失衡、蹲下、奔跑等典型危险动作的识别。当系统检测到危险情况时,会立即通过用户界面和本地生成的音频信号发出警报。本研究不仅有助于提高特定场景下的安全防护能力,也为计算机视觉技术在安全监控领域的应用提供了实践案例和参考。
1.2 国内外研究现状
基于计算机视觉的安全监控与行为识别一直是学术界和工业界的研究热点。早期的方法主要依赖于背景建模、光流法、轮廓分析等传统计算机视觉技术来检测运动目标或识别人体轮廓,但这些方法对环境变化敏感,鲁棒性较差。
随着深度学习的兴起,基于卷积神经网络(CNN)的目标检测算法(如YOLO, SSD, Faster R-CNN)和人体姿态估计算法(如OpenPose, AlphaPose, Mediapipe Pose)取得了显著进展。这些算法能够更准确地定位人体和提取骨骼关键点,为后续的行为分析奠定了坚实的基础。许多研究工作利用这些技术进行异常行为检测。例如,一些研究通过分析人体骨骼点的时空变化来识别跌倒动作;另一些研究则利用骨骼点信息结合机器学习或深度学习模型(如RNN, LSTM, GCN)来识别更复杂的行为,如打斗、偷窃等。在危险区域检测方面,常见的方法是结合目标检测和几何判断,判定目标(如人)的质心或边界框是否进入预定义的多边形区域。
尽管现有技术取得了一定的成果,但在实际应用中仍面临一些挑战:
- 实时性要求: 安全监控系统需要实时处理视频流并快速响应,这对算法的计算效率提出了很高要求。
- 准确性与鲁棒性: 复杂光照、遮挡、多人交互、视角变化等因素都会影响骨骼识别和行为分析的准确性。
- 动作多样性与定义模糊性: “危险动作”的定义往往与具体场景相关,且动作本身具有多样性,设计通用且准确的识别算法难度较大。
- 系统集成与易用性: 如何将复杂的视觉算法与用户友好的界面、可靠的报警机制相结合,构建一个完整、易用的系统也是一个重要的实践问题。
本系统旨在结合现有成熟的骨骼识别技术(Mediapipe Pose)和经典的几何计算方法,针对几种明确定义的危险场景(区域入侵、弯腰、举手、失衡、蹲下、奔跑)进行检测,并注重系统的实时性、易用性和报警机制的本地化实现,以期在特定应用场景下提供一个实用、可靠的解决方案。
1.3 本文主要工作
本文的主要工作在于设计和实现一个集成了危险区域入侵检测和多种危险动作识别功能的实时报警系统。具体工作内容包括:
- 技术选型与环境搭建: 选择了Python作为主要开发语言,利用OpenCV进行图像处理和视频流读取,采用Mediapipe Pose库进行实时人体骨骼关键点检测,使用PySide6构建图形用户界面(GUI),并结合Numpy、Scipy、Pillow和Pydub/Sounddevice等库进行数值计算、图像处理和本地音频警报的生成与播放。搭建了相应的开发和运行环境。
- 系统需求分析与设计: 对系统的功能需求(实时监控、区域定义、骨骼检测、危险判断、报警提示、UI交互)和非功能需求(实时性、准确性、易用性)进行了分析。设计了系统的整体架构,明确了视频采集、骨骼检测、危险检测、UI交互和音频警报等核心模块的功能和接口。
- 核心算法研究与实现:
- 实现了基于
cv2.pointPolygonTest
的危险区域入侵检测算法,并考虑了多点进入的判断逻辑。 - 研究并实现了多种危险动作的检测算法,包括:利用向量夹角(余弦定理)计算弯腰角度的算法;基于关键点相对位置判断手臂高举和身体失衡的算法;结合膝盖弯曲角度和髋部相对高度判断蹲下姿势的算法;综合腿部跨距、身体前倾和手臂摆动等特征判断奔跑姿势的算法。在算法实现中考虑了关键点可见性以提高可靠性。
- 实现了基于Numpy和Scipy的本地化音频警报生成模块,能够根据不同的危险类型生成不同模式(如蜂鸣、警笛、扫频、啁啾)的WAV格式提示音,并兼容多种音频播放后端(Sounddevice, Pydub, 系统命令),取代了对网络API的依赖。
- 实现了基于
- 系统集成与界面实现: 使用PySide6框架开发了图形用户界面,提供了摄像头选择、监控启停、危险区域绘制与清除、检测选项配置、实时视频显示(含骨骼和危险区域标注)、报警信息列表、状态显示等功能。通过信号与槽机制将后端处理逻辑与前端界面进行连接,实现了流畅的用户交互。应用了自定义QSS样式美化界面。解决了中文显示乱码和跨平台字体加载的问题。
- 系统测试与评估: 设计了测试用例,对系统的各项功能(区域检测、各种姿态检测、报警功能、UI交互)进行了测试,并对系统的实时性能(如FPS)和检测效果进行了初步评估。
1.4 论文结构安排
本文的结构安排如下:
- 第一章:引言。 主要介绍研究背景、意义、国内外研究现状以及本文的主要工作和结构安排。
- 第二章:相关技术介绍。 介绍本系统所涉及的关键技术,包括计算机视觉基础、OpenCV库、Mediapipe框架(特别是Pose模型)、PySide6 GUI框架、以及Numpy/Scipy等科学计算库。
- 第三章:系统分析。 对系统的需求进行详细分析,包括功能性需求和非功能性需求,并进行技术、操作和经济上的可行性分析。
- 第四章:系统设计。 阐述系统的总体架构设计,详细设计各个功能模块(视频采集、骨骼检测、危险检测、UI、音频警报)的实现方案,包括关键算法流程和界面布局。使用Mermaid绘制系统架构图和关键流程图。
- 第五章:系统实现。 介绍系统的开发环境和具体实现过程,展示核心模块的关键代码,并解释实现细节,包括骨骼点获取、危险判断逻辑、音频生成、UI交互等。展示核心公式。
- 第六章:系统测试。 描述测试环境、测试用例设计,展示主要功能的测试结果,并对结果进行分析,评估系统的性能和局限性。
- 第七章:结论。 总结全文工作,阐述系统的创新点和不足之处,并对未来的改进方向进行展望。
2. 相关技术介绍
本系统的开发涉及多个领域的关键技术,本章将对这些核心技术进行介绍,为后续的系统设计与实现奠定基础。
2.1 计算机视觉与OpenCV
计算机视觉(Computer Vision)是一门研究如何使机器“看”的科学,更进一步的说,就是指用摄影机和电脑代替人眼对目标进行识别、跟踪和测量等,并进一步做图像处理,用电脑处理成为更适合人眼观察或传送给仪器检测的图像。它涉及图像处理、模式识别、人工智能等多个领域。在本项目中,计算机视觉技术是实现环境感知和人体理解的基础。
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。它包含了超过2500种优化的算法,涵盖了计算机视觉的众多领域,如图像处理(滤波、形态学操作、色彩空间转换)、特征检测与描述(SIFT, SURF, ORB)、目标检测与跟踪(Haar级联、光流法)、摄像头标定、立体视觉以及机器学习工具等。OpenCV提供了Python、C++、Java等多种语言接口,具有强大的功能和良好的跨平台兼容性。在本系统中,OpenCV主要用于:
- 视频流读取与处理: 通过
cv2.VideoCapture
类访问和读取摄像头输入的实时视频流。 - 图像预处理: 如色彩空间转换(BGR到RGB),这是许多视觉分析算法(包括Mediapipe)的输入要求。
- 图像绘制与标注: 在视频帧上绘制检测到的骨骼点、连接线、危险区域边界、文字标签等信息,用于可视化展示,如使用
cv2.polylines
,cv2.fillPoly
,cv2.putText
,cv2.addWeighted
等函数。 - 几何计算: 利用OpenCV提供的几何计算功能,如
cv2.pointPolygonTest
函数来判断一个点是否在多边形(危险区域)内部。
OpenCV的高效性和丰富的函数库极大地简化了计算机视觉应用的开发流程,是本系统不可或缺的基础库。
2.2 Mediapipe与人体骨骼识别
Mediapipe是Google开发的一个开源框架,用于构建实时的、跨平台的感知管道(perception pipelines)。它提供了一系列预训练好的机器学习模型和工具,可以轻松实现人脸检测、手部跟踪、姿态估计、目标检测等多种复杂的视觉任务。Mediapipe的设计注重性能和效率,能够在移动设备、桌面端甚至Web端流畅运行。
本项目核心的人体骨骼识别功能就是基于Mediapipe的Pose解决方案实现的。Mediapipe Pose能够实时地从图像或视频中检测人体的33个关键骨骼点(landmarks),包括头部、躯干、四肢的主要关节点。其主要特点包括:
- 高精度检测: 基于先进的深度学习模型,能够比较准确地定位各种姿态下的人体关键点。
- 实时性能: 经过优化,可以在普通CPU或GPU上实现实时处理。
- 提供关键点坐标和可见性: 对于每个检测到的关键点,模型不仅输出其在图像中的(x, y)坐标(归一化),还提供一个可见性(visibility)评分,表示该点被模型认为可见的可信度。这对于后续判断姿态的可靠性非常有帮助。
- 跨平台支持: 可以在多种操作系统和硬件上运行。
在本系统中,src.core.skeleton_detector.SkeletonDetector
类封装了Mediapipe Pose的使用。其主要工作流程如下:
- 初始化: 创建
mp.solutions.pose.Pose
对象,可以配置模型复杂度、最小检测置信度、最小跟踪置信度等参数。 - 处理帧: 将OpenCV读取到的BGR图像帧转换为RGB格式,输入到
pose.process()
方法中进行处理。 - 获取结果: 处理结果
results
中包含了检测到的姿态关键点信息results.pose_landmarks
。 - 坐标提取:
get_landmarks_coordinates
方法遍历results.pose_landmarks.landmark
,将归一化的坐标(x, y)根据图像的宽高转换为像素坐标,并保存每个点的(x, y, visibility)信息到字典中,供危险检测模块使用。 - 可视化(可选): 利用
mp.solutions.drawing_utils.draw_landmarks
可以将检测到的骨骼点和连接线绘制到图像上。
Mediapipe Pose为本系统提供了稳定、高效、准确的人体骨骼关键点数据来源,是实现后续危险动作识别的基础。
2.3 PySide6与图形用户界面(GUI)
为了方便用户与系统交互,如图形化地设置危险区域、选择摄像头、查看实时监控画面和报警信息,本系统采用PySide6构建图形用户界面(GUI)。PySide6是Qt for Python项目的官方绑定,允许开发者使用Python语言来创建功能丰富、外观现代的跨平台桌面应用程序。Qt是一个成熟且广泛使用的C++ GUI框架。
PySide6的主要优势包括:
- 丰富的控件库: 提供了大量的标准UI控件,如按钮(QPushButton)、标签(QLabel)、下拉框(QComboBox)、复选框(QCheckBox)、列表(QListWidget)、组框(QGroupBox)等,可以构建复杂的界面布局。
- 信号与槽机制: 这是Qt的核心机制之一,用于对象间的通信。当某个事件发生时(如按钮被点击),一个对象可以发射(emit)一个信号,连接到该信号的槽函数(slot)会被自动调用。这种机制使得代码解耦,易于维护。本项目中大量使用了信号与槽,例如按钮点击连接到处理函数,定时器超时信号连接到帧更新函数,图像控件的鼠标事件信号连接到危险区域绘制函数等。
- 布局管理器: 提供了QVBoxLayout(垂直布局)、QHBoxLayout(水平布局)、QGridLayout(网格布局)等布局管理器,可以方便地组织和管理界面控件,使其能够自适应窗口大小变化。
- 强大的绘图系统: QPainter类提供了丰富的2D绘图功能,可以在控件上绘制图形、文本、图像等。本项目中用于在视频画面上叠加绘制危险区域的边界和顶点。
- 样式表(QSS): 支持使用类似CSS的语法(QSS)来定制应用程序的外观,可以轻松实现界面的美化和风格统一。本项目的
src.ui.style.py
文件定义了应用的QSS样式。 - 跨平台性: 基于Qt开发的应用可以在Windows, macOS, Linux等主流操作系统上运行,具有良好的可移植性。
在本系统中,src.ui.main_window.py
文件定义了主窗口MainWindow
类和图像显示控件ImageViewer
类。MainWindow
负责整体界面的布局和控件的初始化,而ImageViewer
则继承自QLabel
,重写了鼠标事件(mousePressEvent
, mouseMoveEvent
, mouseDoubleClickEvent
)来实现在视频画面上通过点击和双击来交互式绘制危险区域的功能,并通过自定义信号将绘制状态传递给主应用逻辑。
2.4 Numpy、Scipy与Pillow
Numpy (Numerical Python) 是Python中用于科学计算的核心库。它提供了一个强大的N维数组对象(ndarray
),以及用于处理这些数组的各种函数。Numpy数组操作通常比Python原生列表操作效率高得多,尤其是在处理大型数据集时。在本系统中,Numpy主要用于:
- 数值计算: 在危险动作检测算法中进行大量的向量和矩阵运算,如计算向量长度(
np.sqrt
)、点积、角度(np.degrees
,np.arccos
,np.arctan
)等。 - 数据表示: 危险区域的顶点坐标使用Numpy数组存储,方便后续的几何计算。
- 音频数据处理: 在生成本地音频警报时,使用Numpy数组来表示和操作音频波形数据(如生成正弦波、应用淡入淡出效果)。
Scipy (Scientific Python) 是建立在Numpy之上的另一个核心科学计算库,提供了更多用于科学和工程计算的模块,包括信号处理、优化、线性代数、统计等。在本系统中,Scipy主要用于:
- 音频文件读写: 使用
scipy.io.wavfile
模块来读取和写入WAV格式的音频文件,这是生成和保存本地警报音效的关键。 - 信号生成: 利用
scipy.signal
模块中的函数(如signal.chirp
)来生成更复杂的音频信号,例如啁啾声(chirp)。
Pillow 是PIL(Python Imaging Library)的一个活跃分支,是Python中最常用的图像处理库之一。它提供了广泛的文件格式支持、高效的内部表示以及相当强大的图像处理能力。在本系统中,Pillow主要用于解决OpenCV cv2.putText
无法直接绘制中文字符的问题:
- 图像格式转换: 将OpenCV的图像(通常是Numpy数组,BGR格式)转换为Pillow的Image对象(RGB格式),反之亦然。
- 加载字体: 使用
ImageFont.truetype
或ImageFont.load_default
加载系统字体文件。 - 绘制文本: 使用
ImageDraw.Draw
对象在PIL图像上绘制中文字符,可以指定字体、颜色、位置等。通过draw.textbbox
或draw.textsize
(兼容旧版)获取文本尺寸,以便进行居中和背景绘制。
这些库为系统提供了强大的数值计算、信号处理和图像处理能力,是实现核心算法和解决特定问题的关键工具。
2.5 音频处理库(Pydub, Sounddevice)
为了实现本地化的音频警报功能,系统需要能够生成和播放声音。除了使用Numpy/Scipy生成WAV数据外,还需要库来播放这些声音。
Pydub 是一个高级的音频处理库,旨在使音频操作变得简单直观。它封装了底层的音频处理库(如ffmpeg),提供了简洁的API来加载、处理、导出各种格式的音频文件,以及直接播放音频。在本系统中,Pydub用于:
- 播放WAV文件: 作为备选的音频播放后端,尝试使用
pydub.playback.play
函数播放生成的WAV警告音。 - 格式转换(潜在): 虽然当前代码主要生成WAV,但Pydub可以方便地进行MP3到WAV等的转换(如此前版本中使用gTTS时)。
Sounddevice 是一个提供Python绑定到PortAudio库的模块,PortAudio是一个跨平台的音频I/O库。Sounddevice允许Python程序录制和播放音频,支持底层的音频流操作。在本系统中,Sounddevice是首选的音频播放后端:
- 播放Numpy数组: 可以直接播放存储在Numpy数组中的音频波形数据,这与我们使用Numpy/Scipy生成音频的方式非常契合。通过
sounddevice.play()
播放,并使用sounddevice.wait()
等待播放完成。
系统设计了多级音频播放尝试机制:优先尝试Sounddevice,如果失败则尝试Pydub,最后尝试调用系统自带的播放命令(如macOS的afplay
,Windows的powershell
播放器,Linux的aplay
等),以最大程度地保证在不同环境下都能发出声音警报。
3. 系统分析
在进行系统设计和实现之前,必须对系统的需求进行全面分析,并评估其可行性。本章将详细阐述系统的需求分析和可行性分析。
3.1 需求分析
需求分析是系统开发的关键步骤,旨在明确系统需要实现的功能以及需要满足的性能、可靠性等非功能性要求。通过与潜在用户的沟通(在本研究中,基于项目目标和通用安全监控场景进行设定)和对应用场景的理解,我们将系统需求归纳如下。
3.1.1 功能性需求分析
功能性需求定义了系统应该具备的具体功能。
-
实时视频采集与显示:
- 系统应能连接并选择可用的摄像头设备。
- 系统应能实时捕获选定摄像头的视频流。
- 系统应在用户界面上清晰、流畅地显示实时视频画面。
- 应能在视频画面上显示当前的帧率(FPS)。
-
人体骨骼实时检测:
- 系统应能实时检测视频画面中的人体。
- 对于检测到的人体,系统应能实时识别其骨骼关键点(至少包括头、肩、肘、腕、髋、膝、踝等主要部位)。
- 应能在视频画面上叠加显示检测到的骨骼点和连接线,方便用户观察。
-
危险区域管理:
- 用户应能通过在视频画面上交互式操作(如单击添加点、双击完成)来定义一个或多个多边形危险区域。
- 系统应能在视频画面上清晰地绘制出用户定义的危险区域边界和填充效果。
- 用户应能清除所有已定义的危险区域。
- 系统应保存用户定义的危险区域信息,用于后续的入侵检测。
-
危险区域入侵检测:
- 系统应能实时判断检测到的人体是否有部分关键点进入了用户定义的危险区域。
- 判断逻辑应考虑人体有多个关键点进入区域的情况,设定合理的触发阈值(例如,至少有一定数量或特定部位的关键点进入)。
- 当检测到区域入侵时,应触发相应的危险状态。
-
危险动作识别:
- 系统应能实时分析检测到的骨骼点数据,识别特定的危险或异常动作。
- 弯腰过度检测: 当人体躯干(如头/肩中点与髋中点的连线)与垂直方向的夹角过大,且姿态符合前倾特征时,判定为弯腰过度。需要设定合理的角度阈值。
- 手臂高举检测: 当检测到人的手腕明显高于其头部位置时,判定为手臂高举。
- 身体失衡检测: 当左右肩膀的高度差超过一定阈值(如相对于身高的比例)时,判定为身体倾斜过度或失衡。
- 蹲下姿势检测: 当膝盖显著弯曲(角度小于阈值)且髋部位置相对于脚踝较低(低于一定身高比例)时,判定为蹲下姿势。
- 奔跑姿势检测: 当检测到人体步幅过大、身体前倾、或手臂有明显摆动特征时,判定为奔跑。
- 以上每种危险动作都需要设定合理的判断阈值和逻辑,并考虑关键点的可见性以提高准确性。
- 当检测到任何一种危险动作时,应触发相应的危险状态。
-
报警功能:
- 当系统检测到危险区域入侵或危险动作时,应立即触发报警机制。
- 视觉报警: 在用户界面的视频区域显示醒目的警告信息或覆盖层(例如,闪烁的红色边框或文字提示),明确指示危险类型。
- 音频报警: 播放本地生成的警告音效。不同的危险类型(区域入侵、弯腰、举手、失衡、蹲下、奔跑)应对应不同模式或音调的提示音,以便区分。
- 用户应能选择是否启用音频报警。
- 系统应有报警冷却机制,避免对同一事件在短时间内重复、密集地报警。
-
用户界面与交互:
- 提供一个集成化的图形用户界面,集中展示视频监控、控制选项和报警信息。
- 控制面板: 提供摄像头选择、开始/停止监控、绘制/清除危险区域、启用/禁用危险区域检测、启用/禁用危险姿势检测、启用/禁用音频报警、清除报警记录等控制功能。
- 信息显示: 实时显示监控视频(带标注)、FPS、系统运行状态、报警信息列表(带时间戳)。
- 界面布局应清晰、合理,操作应直观、便捷。
3.1.2 非功能性需求分析
非功能性需求定义了系统运行的质量属性和约束条件。
- 实时性: 系统必须能够实时处理视频流并进行检测和报警。从捕捉到画面到完成检测并发出报警的延迟应尽可能小(例如,控制在数百毫秒内),以确保预警的及时性。系统的处理帧率(FPS)应尽可能高,以保证画面的流畅性和检测的连续性。
- 准确性: 骨骼检测、区域入侵判断和危险动作识别的准确率应尽可能高,减少漏报(未检测到实际发生的危险)和误报(将正常情况误判为危险)。危险判断的阈值需要经过调试和优化,以在灵敏度和特异性之间取得平衡。
- 鲁棒性: 系统应能在一定程度上适应环境变化,如光照变化、轻微遮挡等。对不同体型、姿态的人应具有一定的识别能力。
- 易用性: 用户界面应简洁明了,操作逻辑符合用户习惯,危险区域的定义应方便快捷。系统状态和报警信息应清晰易懂。
- 兼容性: 系统应能在常见的操作系统(如Windows, macOS, Linux)上运行。应能兼容常见的USB摄像头。
- 资源消耗: 系统运行时对CPU、内存等资源的消耗应控制在合理范围内,避免过度占用导致其他应用卡顿或系统崩溃。
- 可维护性与可扩展性: 代码结构应清晰,模块化设计,方便后续的功能维护、调试和扩展(例如,增加新的危险动作识别类型)。
3.2 可行性分析
可行性分析旨在评估项目在现有条件下是否能够成功完成。我们从技术、操作和经济三个方面进行分析。
3.2.1 技术可行性
- 核心技术成熟度: 本项目依赖的关键技术,如计算机视觉、图像处理、人体骨骼识别等,已有数十年的发展历史,理论基础扎实,并有大量成熟的开源库和框架支持。OpenCV作为计算机视觉领域的事实标准库,功能强大且稳定。Mediapipe Pose是Google推出的先进且高效的姿态估计算法,已经在诸多应用中得到验证,能够提供满足需求的实时骨骼点数据。
- 开发工具与环境: Python作为一种语法简洁、生态丰富的胶水语言,非常适合快速开发此类应用。PySide6提供了现代化的GUI开发能力。Numpy, Scipy, Pillow等库为数值计算和图像/音频处理提供了强大支持。这些工具均为开源或免费提供,易于获取和使用。
- 算法实现复杂度: 危险区域入侵检测主要基于几何判断,算法相对简单。危险动作识别虽然涉及一些几何计算和逻辑判断,但相比于从零开始训练深度学习模型,基于Mediapipe提供的骨骼点进行二次分析的复杂度是可控的。本项目选取的几种危险动作(弯腰、举手、失衡、蹲下、奔跑)特征相对明确,可以通过设计合理的规则和阈值进行识别。本地音频生成使用成熟的信号处理库,技术难度适中。
- 性能要求: 实时性是关键挑战。Mediapipe本身经过优化,可以在普通硬件上达到不错的帧率。危险检测算法主要是几何计算,计算量相对可控。通过合理的算法设计和代码优化(如利用Numpy加速计算),结合多线程处理(如音频播放放在单独线程),可以预期在普通PC上达到可接受的实时处理性能。
结论: 从技术角度看,本项目所需的核心技术均有成熟的解决方案和工具支持,算法实现复杂度可控,性能要求可以通过优化达到,因此技术上是可行的。
3.2.2 操作可行性
- 用户界面: 系统设计了图形用户界面,将复杂的技术细节封装在后台,用户通过直观的按钮、复选框、下拉菜单以及视频窗口交互来操作。危险区域的定义采用鼠标点击方式,符合常规操作习惯。报警信息以列表形式展示,清晰明了。
- 系统部署与使用: 系统使用Python开发,依赖库可以通过
pip
方便地安装。最终可以打包成可执行文件(虽然本项目未明确要求,但技术上可行),方便在不同机器上部署。用户只需连接好摄像头,运行程序即可开始使用。 - 维护与调整: 对于危险动作的判断阈值,可能需要根据实际场景进行调整。代码中预留了打印调试信息,可以辅助进行参数优化。模块化的代码结构也便于后续维护。
结论: 系统提供了易于理解和操作的用户界面,部署和使用流程相对简单,操作上是可行的。
3.2.3 经济可行性
- 软件成本: 本项目所使用的核心软件库,包括Python解释器、OpenCV, Mediapipe, PySide6, Numpy, Scipy, Pillow, Pydub, Sounddevice等,均为开源或免费软件,无需支付额外的软件许可费用。
- 硬件成本: 系统运行需要一台普通性能的PC(具备运行Python和相关库的能力)和一个或多个USB摄像头。这些硬件设备成本相对较低,对于大多数企业或个人用户而言是可以接受的。
- 开发成本: 主要成本在于开发人员的时间投入。由于利用了大量现有库,可以显著缩短开发周期,降低开发成本。
- 运行成本: 系统运行主要消耗电力和少量计算资源,运行成本较低。本地化的音频报警避免了依赖可能收费的在线TTS API。
结论: 本项目主要依赖开源软件和通用硬件,开发和运行成本相对较低,经济上是可行的。
综合可行性分析结论: 通过对技术、操作和经济三个方面的分析,可以认为“基于骨骼识别的危险动作报警系统”的开发是完全可行的。系统能够在现有技术和资源条件下得以实现,并且具有良好的应用前景和实用价值。
4. 系统设计
在完成需求分析和可行性分析后,本章将对系统进行详细的设计,包括系统总体架构、各功能模块的设计、关键算法流程以及界面设计。
4.1 系统总体架构设计
为了实现系统的功能需求并保证其可维护性和可扩展性,我们采用模块化的设计思想,将系统划分为若干个相互协作的核心模块。系统的总体架构如下图所示:
架构说明:
-
用户界面 (UI) 模块: 负责与用户进行交互。
MainWindow
:作为主窗口,承载所有UI元素,管理布局,接收用户输入(按钮点击、选项更改等),并显示来自核心逻辑的信息。ImageViewer
:专门用于显示视频帧,并处理鼠标事件以实现危险区域的交互式绘制。- 控制面板:包含摄像头选择、启停按钮、区域绘制按钮、检测选项复选框等控件。
- 报警信息显示:使用
QListWidget
显示报警历史记录,并提供清除按钮和音频开关。
-
核心逻辑 (Core Logic) 模块: 负责协调各个子模块的工作流程。
Application
:作为应用程序的主控制类,初始化所有核心模块(CameraManager, SkeletonDetector, DangerDetector, AudioAlert, MainWindow),连接UI信号与处理槽函数,控制主事件循环(通过QTimer定时更新帧),调度数据流在各模块间传递。CameraManager
:负责管理摄像头的启动、停止和视频帧的读取,计算实时FPS。SkeletonDetector
:接收视频帧,调用Mediapipe Pose进行骨骼关键点检测,返回检测结果和标注后的图像帧。DangerDetector
:接收骨骼关键点坐标、图像尺寸和用户定义的危险区域信息,执行危险区域入侵检测和危险姿势识别算法,返回检测到的危险类型和对应的报警消息。AudioAlert
:接收报警消息,根据消息内容查找或生成对应的本地WAV音效,并在单独的线程中播放,提供报警冷却机制。
-
底层库/框架: 提供基础功能支持,如前文所述的OpenCV, Mediapipe, PySide6, Numpy, Scipy, Pillow, Pydub, Sounddevice等。
数据流说明:
- 用户通过UI进行操作(如点击“开始监控”)。
MainWindow
发出信号,Application
的槽函数接收到信号。Application
调用CameraManager
启动摄像头。- 定时器触发,
Application
调用CameraManager
读取视频帧。 Application
将视频帧传递给SkeletonDetector
。SkeletonDetector
返回骨骼数据和标注后的帧给Application
。Application
将骨骼数据、帧尺寸、危险区域信息传递给DangerDetector
。DangerDetector
进行危险判断,返回危险类型和消息给Application
。- 如果检测到危险且满足报警条件,
Application
将报警消息传递给AudioAlert
进行播放,并更新MainWindow
显示报警信息和视觉警告。 Application
调用DangerDetector
绘制危险区域(如果存在)。Application
将最终要显示的(可能带有骨骼和区域标注的)图像帧传递给MainWindow
,由ImageViewer
显示。- 同时,
Application
更新FPS和状态标签。 - 用户在
ImageViewer
上绘制危险区域时,ImageViewer
发出包含点坐标的信号,Application
接收后传递给DangerDetector
进行添加(坐标转换后)。
这种模块化的架构使得各部分职责清晰,便于开发、测试和维护。例如,如果未来需要更换骨骼检测算法,只需修改SkeletonDetector
模块,而对其他模块影响较小。
4.2 功能模块设计
4.2.1 视频采集模块 (CameraManager
)
- 主要功能: 初始化、启动、停止摄像头,读取视频帧,计算FPS。
- 设计:
- 使用
cv2.VideoCapture
打开指定ID的摄像头。 - 设置期望的视频分辨率(如640x480)。
- 提供
start()
和stop()
方法控制摄像头开关,并管理is_running
状态。 read_frame()
方法读取一帧,并包含错误处理(如读取失败时尝试重启)。- 通过计算相邻帧的时间差来估算实时FPS。
- 在析构函数
__del__
中确保摄像头资源被释放。
- 使用
4.2.2 骨骼检测模块 (SkeletonDetector
)
- 主要功能: 调用Mediapipe Pose检测骨骼关键点,返回结果和标注图像。
- 设计:
- 初始化
mp.solutions.pose.Pose
对象,配置参数(置信度阈值、模型复杂度)。 detect()
方法接收BGR帧,转换为RGB后送入pose.process()
,获取results
对象。- 如果
results.pose_landmarks
存在,调用mp_drawing.draw_landmarks
在帧副本上绘制骨骼。 - 返回原始
results
对象和标注后的帧。 get_landmarks_coordinates()
方法用于从results
中提取所有关键点的像素坐标(x, y)和可见性(visibility),并存入字典返回。
- 初始化
4.2.3 危险检测模块 (DangerDetector
)
- 主要功能: 管理危险区域,执行区域入侵和危险姿势检测。
- 设计:
danger_zones
: 列表,存储用户定义的危险区域(Numpy数组表示的多边形顶点)。add_danger_zone()
: 添加一个多边形区域。clear_danger_zones()
: 清空所有区域。draw_danger_zones()
: 在输入帧上绘制所有危险区域(包括边界、半透明填充、闪烁效果和中心文字标签)。使用Pillow解决中文标签绘制问题,并兼容不同Pillow版本。check_danger()
: 核心检测入口函数。接收骨骼坐标和帧尺寸,依次调用_check_zone_intrusion()
和_check_dangerous_posture()
。根据检测结果更新current_danger
状态和danger_message
。_check_zone_intrusion()
:- 遍历所有危险区域。
- 遍历人体所有关键点。
- 对每个可见性高于阈值(0.2)的关键点,使用
cv2.pointPolygonTest
判断是否在区域内。 - 统计落入每个区域内的点数,对核心关键点(头、肩、髋)赋予更高权重。
- 当某个区域内的点数(或加权分数)达到阈值(2分)时,判定为区域入侵,返回True。
- 包含详细的调试打印信息。
_check_dangerous_posture()
:- 首先检查所需关键点(头、肩、髋、肘、腕、膝、踝)是否足够且可见性高于阈值(0.7)。
- 弯腰检测: 计算肩中点到鼻子、肩中点到髋中点的向量,使用余弦定理计算夹角。结合
nose_y < shoulders_mid_y
判断是否前倾。当角度 > 60度且前倾且关键点可见时触发。 - 手臂高举检测: 判断左右手腕的y坐标是否显著低于鼻子y坐标(注意图像坐标系y轴向下)。
- 身体失衡检测: 计算左右肩y坐标差值,当差值超过帧高度的15%时触发。
- 蹲下检测: 计算左右膝关节角度(膝-髋向量与膝-踝向量夹角)。计算髋部中点y坐标与脚踝中点y坐标的距离,并计算其与估算身高(鼻到踝)的比例。当平均膝关节角度 < 120度且髋踝比例 < 0.4时触发。
- 奔跑检测: 计算脚踝水平距离(跨步)、膝盖水平距离。计算手腕水平距离与肩宽比例判断手臂摆动。计算身体躯干(肩中点到髋中点)与垂直方向夹角判断前倾。当跨步大(比例>0.3或绝对值>帧宽20%)且膝盖分开(>帧宽15%)且(手臂摆动或身体前倾)时触发。
- 每个检测成功后,设置对应的
danger_message
并返回True。如果所有检测都未触发,返回False。
危险姿势检测流程图示例 (Mermaid Activity Diagram):
4.2.4 音频警报模块 (AudioAlert
)
- 主要功能: 根据报警消息生成或加载对应的本地音效,并播放。
- 设计:
warning_sounds
: 字典,存储预先生成的警告消息与对应的WAV文件路径。_generate_all_warning_sounds()
: 在初始化时调用,在后台线程中为warnings_config
中定义的每种危险消息生成对应的音效文件。_generate_warning_sounds()
: 实际的音效生成函数,根据配置调用不同的生成方法。_generate_beep_sound()
,_generate_beep_sequence()
,_generate_siren_sound()
,_generate_sweep_sound()
,_generate_chirp_sound()
: 使用Numpy生成不同模式的波形数据(正弦波、序列、频率调制、线性扫频、对数扫频),应用淡入淡出效果,并使用scipy.io.wavfile.write
保存为WAV文件。文件名包含参数以避免冲突。play_alert()
: 接收报警消息。检查冷却时间(cooldown_period
)和是否与上次消息相同,避免频繁重复报警。如果允许播放,则在新的后台线程中调用_play_warning_sound()
。_play_warning_sound()
: 核心播放逻辑。使用线程锁alert_lock
确保同一时间只有一个警报在播放。查找warning_sounds
字典,如果找不到匹配的预生成音效,则生成一个默认的蜂鸣声。然后依次尝试使用Sounddevice、Pydub和系统命令播放找到的WAV文件,直到成功或所有方法失败。播放完成后释放锁。
4.2.5 用户界面模块 (MainWindow
, ImageViewer
)
- 主要功能: 提供用户交互界面,显示信息,接收用户输入。
- 设计:
MainWindow
:- 使用
QVBoxLayout
和QHBoxLayout
组织整体布局(标题、内容区、状态栏)。内容区分左右两部分。 - 左侧放置
video_group
(包含ImageViewer
和fps_label
)。 - 右侧垂直放置
camera_group
,zone_group
,detect_group
,alert_group
。 - 使用
QGroupBox
对功能区域进行分组。 - 使用
QLabel
,QComboBox
,QPushButton
,QCheckBox
,QListWidget
等标准控件。 - 为关键按钮(开始、停止、绘制、清除、清除警报)设置
objectName
,以便应用style.py
中定义的特定QSS样式(如蓝色主按钮、红色危险按钮)。 - 初始化
QTimer
用于定时触发update_frame
槽函数。 - 提供
update_frame()
,update_fps()
,update_status()
,add_alert()
,show_error()
,show_warning_overlay()
,clear_warning_overlay()
等方法供Application
调用以更新UI状态。 _init_status_bar()
: 创建底部状态栏,显示运行状态和版本号。show_warning_overlay()
: 在视频上方显示一个半透明、带文字的警告覆盖层,并启动warning_timer
实现闪烁效果。
- 使用
ImageViewer
:- 继承
QLabel
用于显示QPixmap
。 set_image()
: 接收OpenCV图像(Numpy数组),转换为QPixmap
,根据控件大小进行缩放(保持宽高比),并设置给QLabel
。调用_draw_points()
绘制用户当前绘制的多边形。- 重写
mousePressEvent
,mouseMoveEvent
,mouseDoubleClickEvent
: 实现交互式绘制逻辑。左键单击添加点到self.points
列表,移动时更新预览(如果需要),双击结束绘制。发出drawing_started
,drawing_progress
,drawing_finished
信号通知Application
绘制状态和最终点列表。 _draw_points()
: 在set_image
设置的QPixmap
副本上使用QPainter
绘制当前self.points
列表中的点和连线,以及完成后的半透明填充区域。第一个点用不同颜色标记。clear_points()
: 清空self.points
列表,重置绘制状态。
- 继承
4.3 数据库设计
本系统主要进行实时视频处理和分析,不涉及大量结构化数据的持久化存储和复杂查询。用户定义的危险区域信息、报警记录等都是在程序运行时内存中处理。因此,本系统不需要设计传统的关系型数据库或非关系型数据库。如果未来需要记录详细的报警日志或进行长期的行为数据分析,可以考虑引入简单的文件日志(如CSV或JSON格式)或轻量级数据库(如SQLite)。
4.4 关键算法描述
4.4.1 坐标转换(UI绘制点到实际图像点)
用户在UI的ImageViewer
上绘制危险区域的点坐标是相对于显示控件尺寸的。而危险检测需要作用于原始摄像头分辨率的图像。因此,在Application.add_danger_zone()
中需要进行坐标转换:
x
i
m
g
=
x
u
i
×
W
i
m
g
W
u
i
x_{img} = x_{ui} \times \frac{W_{img}}{W_{ui}}
ximg=xui×WuiWimg
y
i
m
g
=
y
u
i
×
H
i
m
g
H
u
i
y_{img} = y_{ui} \times \frac{H_{img}}{H_{ui}}
yimg=yui×HuiHimg
其中,
(
x
u
i
,
y
u
i
)
(x_{ui}, y_{ui})
(xui,yui)是UI上的点坐标,
(
x
i
m
g
,
y
i
m
g
)
(x_{img}, y_{img})
(ximg,yimg)是转换后的图像坐标,
W
u
i
,
H
u
i
W_{ui}, H_{ui}
Wui,Hui是ImageViewer
中显示的QPixmap
的尺寸,
W
i
m
g
,
H
i
m
g
W_{img}, H_{img}
Wimg,Himg是摄像头原始帧的宽度和高度。
4.4.2 角度计算(余弦定理)
在检测弯腰和蹲下姿势时,需要计算三个点构成的角度(例如肩中点-髋中点-膝中点构成的膝关节角)。给定三个点A, B, C,计算以B为顶点的角度 θ \theta θ:
- 计算向量 B A ⃗ = A − B \vec{BA} = A - B BA=A−B 和 B C ⃗ = C − B \vec{BC} = C - B BC=C−B。
- 计算两个向量的点积: B A ⃗ ⋅ B C ⃗ = B A x × B C x + B A y × B C y \vec{BA} \cdot \vec{BC} = BA_x \times BC_x + BA_y \times BC_y BA⋅BC=BAx×BCx+BAy×BCy。
- 计算两个向量的模(长度): ∣ B A ⃗ ∣ = B A x 2 + B A y 2 |\vec{BA}| = \sqrt{BA_x^2 + BA_y^2} ∣BA∣=BAx2+BAy2, ∣ B C ⃗ ∣ = B C x 2 + B C y 2 |\vec{BC}| = \sqrt{BC_x^2 + BC_y^2} ∣BC∣=BCx2+BCy2。
- 计算夹角的余弦值: cos ( θ ) = B A ⃗ ⋅ B C ⃗ ∣ B A ⃗ ∣ × ∣ B C ⃗ ∣ \cos(\theta) = \frac{\vec{BA} \cdot \vec{BC}}{|\vec{BA}| \times |\vec{BC}|} cos(θ)=∣BA∣×∣BC∣BA⋅BC
- 计算角度(弧度): θ r a d = arccos ( cos ( θ ) ) \theta_{rad} = \arccos(\cos(\theta)) θrad=arccos(cos(θ))。注意处理 cos ( θ ) \cos(\theta) cos(θ) 超出 [-1, 1] 范围的边界情况。
- 转换为角度(度数): θ d e g = θ r a d × 180 π \theta_{deg} = \theta_{rad} \times \frac{180}{\pi} θdeg=θrad×π180。
4.4.3 点在多边形内判断
使用OpenCV的cv2.pointPolygonTest(polygon, point, measureDist)
函数。
polygon
: 危险区域的顶点列表(Numpy数组)。point
: 要测试的人体关键点坐标(x, y)
。measureDist
: 设置为False
。
函数返回值:> 0
: 点在多边形内部。= 0
: 点在多边形边界上。< 0
: 点在多边形外部。
在_check_zone_intrusion
中,当返回值>= 0
时,认为该点落入危险区域。
5. 系统实现
本章将详细介绍系统的具体实现过程,包括开发环境的配置、核心模块的关键代码实现以及代码中涉及的重要算法和逻辑细节。
5.1 开发环境与依赖
- 操作系统: Windows 10/11, macOS Ventura, Ubuntu 20.04 (设计时考虑跨平台兼容性)
- 编程语言: Python 3.9+
- 主要依赖库及其版本 (参考
requirements.txt
):PySide6
: 6.5.2+ (用于GUI)opencv-python
: 4.8.0+ (图像处理和视频读取)mediapipe
: 0.10.3+ (骨骼识别)numpy
: 1.24.3+ (数值计算)scipy
: 1.11.2+ (科学计算,音频文件处理,信号生成)Pillow
: 10.1.0+ (图像处理,字体绘制)sounddevice
: 0.4.6+ (音频播放后端)pydub
: 0.25.1+ (音频播放后端,需要ffmpeg支持)setuptools
: (用于pkg_resources
检测Pillow版本)
- 开发工具: Visual Studio Code 或 PyCharm Professional
- 版本控制: Git
依赖库通过pip install -r requirements.txt
命令进行安装。对于pydub
的完整功能(特别是播放非WAV格式),可能需要单独安装ffmpeg
并将其添加到系统路径。
5.2 核心模块实现
5.2.1 骨骼检测模块 (src/core/skeleton_detector.py
)
该模块封装了Mediapipe Pose的使用,实现了骨骼点的检测和坐标提取。
关键代码片段:
import cv2
import mediapipe as mp
import numpy as np
import logging
class SkeletonDetector:
def __init__(self, min_detection_confidence=0.5, min_tracking_confidence=0.5):
# ... (初始化 Mediapipe Pose 对象 self.pose) ...
self.mp_pose = mp.solutions.pose
self.mp_drawing = mp.solutions.drawing_utils
self.mp_drawing_styles = mp.solutions.drawing_styles
self.pose = self.mp_pose.Pose(
static_image_mode=False, # 处理视频流
model_complexity=1, # 模型复杂度,0, 1, 2可选,越高越准但越慢
enable_segmentation=False,# 不启用分割
min_detection_confidence=min_detection_confidence,
min_tracking_confidence=min_tracking_confidence
)
def detect(self, frame):
# 将BGR转换为RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 提高性能,将图像标记为不可写
frame_rgb.flags.writeable = False
# 进行骨骼检测
results = self.pose.process(frame_rgb)
# 恢复图像为可写
frame_rgb.flags.writeable = True
# 绘制骨骼点和连接线 (在原始 BGR 帧的副本上绘制)
annotated_frame = frame.copy()
if results.pose_landmarks:
self.mp_drawing.draw_landmarks(
annotated_frame,
results.pose_landmarks,
self.mp_pose.POSE_CONNECTIONS,
landmark_drawing_spec=self.mp_drawing_styles.get_default_pose_landmarks_style()
)
return results, annotated_frame
def get_landmarks_coordinates(self, results, frame_shape):
landmarks_coords = {}
if results.pose_landmarks:
height, width, _ = frame_shape
for idx, landmark in enumerate(results.pose_landmarks.landmark):
# 将相对坐标转换为像素坐标,并存储可见性
x = int(landmark.x * width)
y = int(landmark.y * height)
# 限制坐标在图像范围内
x = max(0, min(x, width - 1))
y = max(0, min(y, height - 1))
landmarks_coords[idx] = (x, y, landmark.visibility)
return landmarks_coords
实现说明:
- 初始化时创建
mp.solutions.pose.Pose
实例,并配置相关参数。 detect
方法是核心处理函数,接收OpenCV读取的BGR帧,先转换为RGB格式(Mediapipe需要),然后调用self.pose.process()
进行检测。为了效率,处理前将图像标记为不可写。检测后,在原始帧的副本上绘制骨骼。get_landmarks_coordinates
方法遍历检测结果中的每个landmark
,将其归一化的(x, y)坐标乘以图像的宽高,得到像素坐标,并连同visibility
一起存储在字典中返回。增加了边界检查确保坐标不越界。
5.2.2 危险检测模块 (src/core/danger_detector.py
)
该模块负责实现危险区域入侵和各种危险姿势的判断逻辑。
关键代码片段 - 弯腰检测:
# ... 在 _check_dangerous_posture 方法内 ...
# 检查弯腰过度
nose_x, nose_y, nose_vis = landmarks_coords.get(0, (0, 0, 0))
ls_x, ls_y, ls_vis = landmarks_coords.get(11, (0, 0, 0)) # 左肩
rs_x, rs_y, rs_vis = landmarks_coords.get(12, (0, 0, 0)) # 右肩
lh_x, lh_y, lh_vis = landmarks_coords.get(23, (0, 0, 0)) # 左髋
rh_x, rh_y, rh_vis = landmarks_coords.get(24, (0, 0, 0)) # 右髋
# 检查所需点是否存在且可见
bend_points_visible = all(vis > visibility_threshold for vis in [nose_vis, ls_vis, rs_vis, lh_vis, rh_vis])
if bend_points_visible:
shoulders_mid_x = (ls_x + rs_x) / 2
shoulders_mid_y = (ls_y + rs_y) / 2
hips_mid_x = (lh_x + rh_x) / 2
hips_mid_y = (lh_y + rh_y) / 2
# 向量1: 肩中点 -> 鼻子
vector1 = np.array([nose_x - shoulders_mid_x, nose_y - shoulders_mid_y])
# 向量2: 肩中点 -> 髋中点
vector2 = np.array([hips_mid_x - shoulders_mid_x, hips_mid_y - shoulders_mid_y])
vector1_length = np.linalg.norm(vector1)
vector2_length = np.linalg.norm(vector2)
if vector1_length > 1 and vector2_length > 1: # 避免长度过小导致计算误差
dot_product = np.dot(vector1, vector2)
cos_angle = dot_product / (vector1_length * vector2_length)
cos_angle = np.clip(cos_angle, -1.0, 1.0) # 限制范围
bend_angle = np.degrees(np.arccos(cos_angle))
# 检查是否前倾 (鼻子y坐标小于肩部y坐标)
is_bending_forward = nose_y < shoulders_mid_y
print(f"弯腰角度: {bend_angle:.1f}度, 是否前倾: {is_bending_forward}")
# 阈值判断
if bend_angle > 60 and is_bending_forward:
self.danger_message = "警告:检测到弯腰过度!"
return True
关键代码片段 - 蹲下检测:
# ... 在 _check_dangerous_posture 方法内 ...
# 检查蹲下姿势
required_squat_points = [0, 23, 24, 25, 26, 27, 28] # 鼻子, 髋, 膝, 踝
squat_points_visible = all(landmarks_coords.get(idx, (0,0,0))[2] > visibility_threshold for idx in required_squat_points)
if squat_points_visible:
# ... (获取左右髋、膝、踝坐标) ...
hips_y = (landmarks_coords[23][1] + landmarks_coords[24][1]) / 2
ankles_y = (landmarks_coords[27][1] + landmarks_coords[28][1]) / 2
nose_y = landmarks_coords[0][1]
# 估算身高 (简化为鼻子到脚踝垂直距离)
estimated_height = abs(ankles_y - nose_y)
if estimated_height < 50: # 身高过小,可能检测不准
return False # 或 continue
# 计算左右膝盖角度 (调用计算角度的辅助函数,或直接实现余弦定理)
left_knee_angle = self._calculate_angle(landmarks_coords[23], landmarks_coords[25], landmarks_coords[27])
right_knee_angle = self._calculate_angle(landmarks_coords[24], landmarks_coords[26], landmarks_coords[28])
avg_knee_angle = (left_knee_angle + right_knee_angle) / 2
# 计算髋部相对高度比例
hip_ankle_dist = abs(hips_y - ankles_y)
hip_height_ratio = hip_ankle_dist / estimated_height
print(f"平均膝盖角度: {avg_knee_angle:.1f}度, 髋踝比例: {hip_height_ratio:.2f}")
# 阈值判断
if avg_knee_angle < 120 and hip_height_ratio < 0.4:
self.danger_message = "警告:检测到蹲下姿势,可能存在危险!"
return True
(注:_calculate_angle
辅助函数需要实现,其逻辑即前述的余弦定理计算)
关键代码片段 - 奔跑检测:
# ... 在 _check_dangerous_posture 方法内 ...
# 检测奔跑姿势
required_run_points = [11, 12, 15, 16, 25, 26, 27, 28] # 肩, 腕, 膝, 踝
run_points_visible = all(landmarks_coords.get(idx, (0,0,0))[2] > visibility_threshold for idx in required_run_points)
key_points_visible = all(landmarks_coords.get(idx, (0,0,0))[2] > visibility_threshold for idx in [0, 11, 12, 23, 24]) # 用于前倾判断
if run_points_visible:
# ... (获取肩、腕、膝、踝坐标) ...
left_ankle_x, left_ankle_y, _ = landmarks_coords[27]
right_ankle_x, right_ankle_y, _ = landmarks_coords[28]
ankle_x_distance = abs(left_ankle_x - right_ankle_x)
ankles_y = (left_ankle_y + right_ankle_y) / 2
nose_y = landmarks_coords.get(0, (0, ankles_y, 0))[1] # 获取鼻子y,若无则用脚踝y
estimated_height = abs(ankles_y - nose_y)
if estimated_height < 1: estimated_height = frame_shape[0] # 防止除零
left_knee_x, _, _ = landmarks_coords[25]
right_knee_x, _, _ = landmarks_coords[26]
knee_x_distance = abs(left_knee_x - right_knee_x)
# 手臂摆动判断
left_wrist_x, _, lw_vis = landmarks_coords[15]
right_wrist_x, _, rw_vis = landmarks_coords[16]
arm_swing = False
if lw_vis > visibility_threshold and rw_vis > visibility_threshold:
shoulder_width = abs(landmarks_coords[11][0] - landmarks_coords[12][0])
wrist_x_distance = abs(left_wrist_x - right_wrist_x)
if shoulder_width > 1:
arm_swing = (wrist_x_distance / shoulder_width) > 1.5
# 身体前倾判断
forward_tilt = False
if key_points_visible:
# ... (获取肩中点、髋中点坐标 shoulders_mid_x/y, hips_mid_x/y) ...
shoulder_to_hip_x = hips_mid_x - shoulders_mid_x
shoulder_to_hip_y = hips_mid_y - shoulders_mid_y
if abs(shoulder_to_hip_y) > 1:
tilt_angle = abs(np.degrees(np.arctan(shoulder_to_hip_x / shoulder_to_hip_y)))
forward_tilt = tilt_angle > 15 # 前倾超过15度
# 跨步比例
stride_height_ratio = ankle_x_distance / estimated_height
print(f"脚踝间距: {ankle_x_distance}, 相对身高比: {stride_height_ratio:.2f}, 前倾: {forward_tilt}, 手臂摆动: {arm_swing}")
# 阈值判断
if (stride_height_ratio > 0.3 or ankle_x_distance > frame_shape[1] * 0.2) and \
knee_x_distance > frame_shape[1] * 0.15 and \
(forward_tilt or arm_swing):
self.danger_message = "警告:检测到奔跑姿势,请减速慢行!"
return True
实现说明:
- 每个姿势检测前都检查所需的关键点是否在
landmarks_coords
字典中存在且其visibility
高于设定的阈值(如0.7)。 - 大量使用向量计算(Numpy)和几何关系判断。
- 阈值(如弯腰角度60度,蹲下膝角120度/高度比0.4,奔跑跨步比0.3/前倾15度等)是根据经验设定,可能需要根据实际应用场景进行调整优化。
- 包含了
print
语句输出中间计算结果,便于调试。 - 危险区域绘制部分使用了Pillow库来绘制中文标签,并做了版本兼容处理(
textbbox
vstextsize
)和字体加载处理。
5.2.3 音频警报模块 (src/utils/audio_alert.py
)
该模块负责生成和播放本地化的警告音效。
关键代码片段 - 音效生成与播放逻辑:
import os
import tempfile
import threading
import time
import subprocess
import platform
import wave
import numpy as np
from scipy.io import wavfile
from scipy import signal
# ... (导入 pydub, sounddevice) ...
class AudioAlert:
def __init__(self):
# ... (初始化路径, 锁, 采样率等) ...
self.sample_rate = 44100
self.warning_sounds = {} # 存储预生成音效路径
self.cooldown_period = 3 # 冷却时间3秒
self.last_message = ""
self.last_alert_time = 0
self.is_playing = False
self.alert_lock = threading.Lock()
self._generate_all_warning_sounds() # 后台生成
def _generate_all_warning_sounds(self):
warnings_config = { # 配置不同警告和对应的声音参数
"警告:有人进入危险区域!": {"type": "siren", "duration": 1.5, "pitch": 0.8},
"警告:检测到弯腰过度!": {"type": "beep_sequence", "duration": 1.2, "count": 3, "pitch": 1.2},
# ... (其他警告配置,包括蹲下和奔跑) ...
"警告:检测到蹲下姿势,可能存在危险!": {"type": "beep_sequence", "duration": 1.0, "count": 2, "pitch": 0.9, "frequency": 700},
"警告:检测到奔跑姿势,请减速慢行!": {"type": "siren", "duration": 1.2, "pitch": 1.2, "intensity": 0.9, "cycles": 5}
}
# 启动后台线程生成
threading.Thread(target=self._generate_warning_sounds, args=(warnings_config,), daemon=True).start()
def _generate_warning_sounds(self, warnings_config):
for message, config in warnings_config.items():
# ... (根据config调用不同的 _generate_xxx_sound 方法生成WAV) ...
# 例如: sound_file = self._generate_siren_sound(...)
if sound_file and os.path.exists(sound_file):
self.warning_sounds[message] = sound_file
print(f"成功生成警告音效: {message}")
# --- 音效生成函数示例 ---
def _generate_siren_sound(self, duration=1.5, low_freq=400, high_freq=1000, cycles=3, intensity=0.7):
t = np.linspace(0, duration, int(self.sample_rate * duration), False)
# 正弦调制频率
freq_mod = (high_freq - low_freq) / 2 * np.sin(2 * np.pi * cycles * t / duration) + (high_freq + low_freq) / 2
phase = 2 * np.pi * np.cumsum(freq_mod) / self.sample_rate
siren = np.sin(phase) * intensity
# ... (应用淡入淡出, 裁剪, 转换为int16, 保存WAV) ...
siren = np.clip(siren, -1, 1)
siren = (siren * 32767).astype(np.int16)
# ... (添加淡入淡出代码) ...
siren_file = os.path.join(self.temp_dir, f"alert_siren_{low_freq}_{high_freq}.wav")
wavfile.write(siren_file, self.sample_rate, siren)
return siren_file
# --- 播放逻辑 ---
def play_alert(self, message):
if not message: return
current_time = time.time()
# 检查冷却时间
if (current_time - self.last_alert_time < self.cooldown_period and message == self.last_message):
return
self.last_message = message
self.last_alert_time = current_time
# 启动后台线程播放
alert_thread = threading.Thread(target=self._play_warning_sound, args=(message,), daemon=True)
alert_thread.start()
def _play_warning_sound(self, message):
with self.alert_lock: # 确保同一时间只有一个声音播放
if self.is_playing: time.sleep(0.1) # 等待上一个结束 (或者直接返回)
self.is_playing = True
try:
sound_file = self.warning_sounds.get(message)
if not sound_file or not os.path.exists(sound_file):
print(f"未找到对应警告音效 '{message}',使用默认提示音")
# 生成默认音效
sound_file = self._generate_beep_sequence(count=2, frequency=900)
if not sound_file or not os.path.exists(sound_file):
print("无法生成默认音效")
self.is_playing = False
return
played = False
# 尝试播放方法1: sounddevice
if HAVE_SOUNDDEVICE and not played:
try:
sample_rate, data = wavfile.read(sound_file)
if len(data.shape) > 1: data = data.mean(axis=1) # 转单声道
sd.play(data, sample_rate)
sd.wait()
played = True
except Exception as e: print(f"sounddevice播放失败: {e}")
# 尝试播放方法2: pydub
if HAVE_PYDUB and not played:
try:
sound = AudioSegment.from_wav(sound_file)
play(sound)
played = True
except Exception as e: print(f"pydub播放失败: {e}")
# 尝试播放方法3: 系统命令
if not played:
try:
if self._play_with_system_tools(sound_file):
played = True
except Exception as e: print(f"系统命令播放失败: {e}")
if not played:
print("警告: 所有音频播放方法都失败了")
except Exception as e:
print(f"播放警告音效时出错: {e}")
finally:
self.is_playing = False # 释放锁
实现说明:
- 初始化时,通过
_generate_all_warning_sounds
在后台线程异步生成所有定义好的警告音效,避免阻塞主程序启动。 - 提供了多种音效生成函数(
_generate_beep_sound
,_generate_beep_sequence
,_generate_siren_sound
等),利用Numpy生成波形数据,Scipy写入WAV文件。音效类型和参数在warnings_config
中配置。 play_alert
方法实现了冷却机制,防止短时间内对同一事件重复报警。_play_warning_sound
方法负责实际的播放。它使用了线程锁alert_lock
来防止多个警报声同时播放造成混乱。它实现了播放后端的多级降级策略:优先尝试sounddevice
,失败则尝试pydub
,再失败则尝试调用系统命令(如afplay
,powershell
,aplay
),以提高跨平台的兼容性和鲁棒性。
5.2.4 主应用逻辑 (src/main.py
)
该模块作为应用的核心控制器,连接UI和后端处理模块。
关键代码片段 - 主循环与信号连接:
import sys
import cv2
import numpy as np
import traceback
from PySide6.QtWidgets import QApplication, QMessageBox
from PySide6.QtCore import Qt, QTimer
# ... (导入 MainWindow, CameraManager, SkeletonDetector, DangerDetector, AudioAlert) ...
class Application:
def __init__(self):
self.app = QApplication(sys.argv)
# ... (设置高DPI等属性) ...
self.main_window = MainWindow()
# ... (初始化 camera, skeleton_detector, danger_detector, audio_alert,包含异常处理) ...
self.is_drawing_zone = False
self._connect_signals()
def _connect_signals(self):
# --- UI控件信号连接到槽函数 ---
# 相机控制
self.main_window.start_button.clicked.connect(self.start_camera)
self.main_window.stop_button.clicked.connect(self.stop_camera)
# 危险区域控制
self.main_window.draw_zone_button.clicked.connect(self.toggle_draw_zone)
self.main_window.clear_zone_button.clicked.connect(self.clear_danger_zone)
# 图像查看器信号 (绘制完成)
self.main_window.image_viewer.drawing_finished.connect(self.add_danger_zone)
# 定时器信号 (用于帧更新)
self.main_window.timer.timeout.connect(self.update_frame)
# 清除警报按钮
self.main_window.clear_alerts_button.clicked.connect(self.main_window.alert_list.clear)
def run(self):
self.main_window.show()
return self.app.exec()
def start_camera(self):
try:
camera_id = self.main_window.camera_combo.currentData()
self.camera = CameraManager(camera_id=camera_id)
self.camera.start()
# 更新UI状态
self.main_window.start_button.setEnabled(False)
# ... (其他UI更新) ...
self.main_window.update_status("运行中")
# 启动定时器
self.main_window.timer.start(33) # 约30 FPS
except Exception as e:
self.main_window.show_error("相机错误", f"无法启动相机: {str(e)}")
self.camera = None
def stop_camera(self):
self.main_window.timer.stop()
if self.camera:
self.camera.stop()
self.camera = None
# 更新UI状态
self.main_window.start_button.setEnabled(True)
# ... (其他UI更新) ...
self.main_window.update_status("已停止")
def update_frame(self): # 定时器触发的核心处理流程
if not self.camera: return
try:
frame, success = self.camera.read_frame()
if not success or frame is None: return
fps = self.camera.get_fps()
self.main_window.update_fps(fps)
# 如果正在绘制区域,只显示原始帧
if self.is_drawing_zone:
self.main_window.update_frame(frame)
return
# 检查核心模块是否初始化成功
if not self.skeleton_detector or not self.danger_detector:
self.main_window.update_frame(frame)
return
try:
# 1. 骨骼检测
results, annotated_frame = self.skeleton_detector.detect(frame)
landmarks_coords = self.skeleton_detector.get_landmarks_coordinates(results, frame.shape)
# 2. 危险检测
previous_danger = self.danger_detector.current_danger # 记录上次状态
danger_type = DangerType.NO_DANGER
message = ""
# 根据UI选项决定是否执行检测
perform_zone_check = self.main_window.zone_check.isChecked()
perform_posture_check = self.main_window.posture_check.isChecked()
if perform_zone_check or perform_posture_check:
# 调用危险检测器 (内部会区分区域和姿势)
danger_type, message = self.danger_detector.check_danger(
landmarks_coords, frame.shape,
check_zone=perform_zone_check, # 传递选项 (注: 当前check_danger未接收此参数,需修改)
check_posture=perform_posture_check # 传递选项 (注: 同上)
)
# 3. 处理检测结果 & 报警
if danger_type != DangerType.NO_DANGER and message:
self.main_window.add_alert(message) # 添加到UI列表
# 状态变化时触发视觉和音频警报
if previous_danger != danger_type:
self.main_window.show_warning_overlay(danger_type, message)
if self.main_window.audio_check.isChecked() and self.audio_alert:
try:
self.audio_alert.play_alert(message)
except Exception as e: print(f"播放语音警报时出错: {e}")
# 对于区域入侵,即使状态未变也可能需要持续报警(取决于策略,当前仅状态变化时报警)
# 4. 清除警告 (如果当前无危险)
elif not landmarks_coords or danger_type == DangerType.NO_DANGER:
self.main_window.clear_warning_overlay()
# 5. 绘制危险区域 (如果存在)
display_frame = annotated_frame # 默认使用带骨骼的帧
if self.danger_detector.danger_zones:
# 在带骨骼的帧上绘制区域
display_frame = self.danger_detector.draw_danger_zones(annotated_frame)
# 6. 更新UI显示
self.main_window.update_frame(display_frame)
except Exception as e:
print(f"处理帧时发生错误: {e}")
traceback.print_exc()
self.main_window.update_frame(frame) # 出错时显示原始帧
except Exception as e:
print(f"读取或更新帧时发生错误: {e}")
traceback.print_exc()
def toggle_draw_zone(self):
self.is_drawing_zone = not self.is_drawing_zone
# ... (更新按钮文本, 状态标签, 清除ImageViewer点) ...
def add_danger_zone(self, points): # 接收来自ImageViewer的信号
if not self.danger_detector or not self.camera: return
if len(points) >= 3:
try:
# --- 坐标转换 ---
camera_width = self.camera.width
camera_height = self.camera.height
pixmap_width = self.main_window.image_viewer.current_pixmap.width()
pixmap_height = self.main_window.image_viewer.current_pixmap.height()
if pixmap_width == 0 or pixmap_height == 0: return # 防止除零
ratio_x = camera_width / pixmap_width
ratio_y = camera_height / pixmap_height
scaled_points = []
for p in points:
scaled_x = int(p[0] * ratio_x)
scaled_y = int(p[1] * ratio_y)
# 限制在图像范围内
scaled_x = max(0, min(scaled_x, camera_width - 1))
scaled_y = max(0, min(scaled_y, camera_height - 1))
scaled_points.append((scaled_x, scaled_y))
if len(scaled_points) >= 3:
self.danger_detector.add_danger_zone(scaled_points)
print(f"已添加危险区域,共 {len(self.danger_detector.danger_zones)} 个区域")
self.main_window.clear_zone_button.setEnabled(True) # 使能清除按钮
else:
print("错误: 有效点数不足")
except Exception as e: print(f"添加危险区域时出错: {e}")
# ... (重置绘制状态) ...
self.is_drawing_zone = False
self.main_window.draw_zone_button.setText("绘制危险区域")
self.main_window.update_status("运行中" if self.camera and self.camera.is_running else "已停止")
def clear_danger_zone(self):
if self.danger_detector:
self.danger_detector.clear_danger_zones()
self.main_window.image_viewer.clear_points() # 清除UI上的绘制
self.is_drawing_zone = False
self.main_window.draw_zone_button.setText("绘制危险区域")
self.main_window.clear_zone_button.setEnabled(False) # 禁用清除按钮
实现说明:
__init__
:初始化UI和所有核心业务逻辑模块,并调用_connect_signals
连接信号槽。_connect_signals
:将UI控件(如按钮点击)的信号连接到Application
类中对应的处理方法(槽)。将ImageViewer
的drawing_finished
信号连接到add_danger_zone
。将QTimer
的timeout
信号连接到update_frame
。start_camera
/stop_camera
:控制CameraManager
的启停,更新UI按钮状态和状态标签,启动/停止定时器。update_frame
:这是由定时器周期性调用的核心函数,实现了完整的数据处理流水线:读取帧 -> 骨骼检测 -> 危险检测 -> 处理结果(报警、更新UI) -> 显示帧。包含了对模块是否初始化和处理过程中异常的捕获。(注:代码中补充了将UI检测选项传递给check_danger
的设想,实际代码需要修改DangerDetector.check_danger
方法签名来接收并使用这些参数)。toggle_draw_zone
:切换危险区域绘制模式的状态。add_danger_zone
:响应ImageViewer
绘制完成的信号,获取UI点坐标,执行坐标转换(UI坐标 -> 图像坐标),然后调用DangerDetector.add_danger_zone
添加区域。clear_danger_zone
:调用DangerDetector.clear_danger_zones
清除后端区域数据,并调用ImageViewer.clear_points
清除UI绘制。
5.2.5 用户界面模块 (src/ui/main_window.py
, src/ui/style.py
, src/utils/icons.py
)
这部分代码主要负责界面的布局、控件的创建和样式的应用。
main_window.py
: 如4.2.5节设计所述,使用PySide6控件和布局管理器构建界面。通过setObjectName
为控件设置对象名,以便QSS选择器应用特定样式。ImageViewer
类通过重写鼠标事件和使用QPainter
实现了交互式区域绘制。style.py
: 定义了全局QSS样式表MAIN_STYLE
,包含了对QWidget
,QMainWindow
,QPushButton
(包括#primary_button
,#stop_button
,#danger_button
),QGroupBox
,QComboBox
,QCheckBox
,QListWidget
,QLabel
等控件的样式设置(背景色、前景色、边框、圆角、字体、内外边距等)。icons.py
: 解决了QSS中直接引用本地图片文件可能存在的路径问题和部署问题。apply_icons_to_style
函数将style.py
中对checkmark.png
和down_arrow.png
的引用替换为内联的SVG数据URL。这样图标就直接嵌入到样式表中,无需单独分发图片文件。
关键代码片段 - QSS样式应用与按钮ObjectName设置:
# src/ui/main_window.py
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.setObjectName("mainWindow") # 为主窗口设置ObjectName
style = apply_icons_to_style(MAIN_STYLE) # 获取处理图标后的样式
self.setStyleSheet(style) # 应用样式表
# ...
def _init_control_area(self):
# ...
self.start_button = QPushButton("开始监控")
self.start_button.setObjectName("primary_button") # 设置ObjectName
self.stop_button = QPushButton("停止监控")
self.stop_button.setObjectName("stop_button") # 设置ObjectName
# ...
self.draw_zone_button = QPushButton("绘制危险区域")
self.draw_zone_button.setObjectName("primary_button") # 设置ObjectName
self.clear_zone_button = QPushButton("清除危险区域")
self.clear_zone_button.setObjectName("danger_button") # 设置ObjectName
# ...
def _init_alert_area(self):
# ...
self.clear_alerts_button = QPushButton("清除所有警报")
self.clear_alerts_button.setObjectName("danger_button") # 设置ObjectName
# ...
# src/ui/style.py
MAIN_STYLE = """
/* ... 其他样式 ... */
/* 主要按钮(蓝色) */
QPushButton#primary_button {
background-color: #4a86e8;
color: white;
}
QPushButton#primary_button:hover { background-color: #3a76d8; }
QPushButton#primary_button:pressed { background-color: #2a66c8; }
/* 停止按钮(灰色) */
QPushButton#stop_button {
background-color: #707070;
color: white;
}
QPushButton#stop_button:hover { background-color: #606060; }
QPushButton#stop_button:pressed { background-color: #505050; }
/* 危险按钮(红色) */
QPushButton#danger_button {
background-color: #e8546c;
color: white; /* 确保文字也是白色 */
}
QPushButton#danger_button:hover { background-color: #d8445c; }
QPushButton#danger_button:pressed { background-color: #c8344c; }
/* ... 其他样式 ... */
"""
实现说明: 通过为控件设置objectName
并配合QSS中的ID选择器 (#objectName
),可以为特定类型的按钮应用不同的背景色,使得UI更加清晰地区分不同功能的按钮。apply_icons_to_style
的使用提高了样式的可移植性。
6. 系统测试
系统测试是确保软件质量、验证系统是否满足需求的关键环节。本章将描述测试环境、设计测试用例、执行测试并分析结果,以评估本系统的功能、性能和稳定性。
6.1 测试环境
- 硬件环境:
- CPU: Intel Core i7-10700 @ 2.90GHz / Apple M1 Pro
- 内存: 16 GB RAM
- 显卡: NVIDIA GeForce RTX 3060 / Apple M1 Pro integrated GPU
- 摄像头: 标准USB摄像头 (Logitech C920, 720p/1080p)
- 软件环境:
- 操作系统: Windows 11 / macOS Sonoma
- Python版本: 3.10.4
- 依赖库版本: 见
requirements.txt
(如 PySide6 6.5.2, OpenCV 4.8.0, Mediapipe 0.10.3, Numpy 1.24.3等)
6.2 测试用例设计
我们针对系统的主要功能模块设计了以下测试用例。测试的目标是验证功能的正确性、界面的响应以及基本性能。
用例ID | 测试模块 | 测试项 | 测试步骤 | 预期结果 |
---|---|---|---|---|
TC_CAM_01 | 视频采集与显示 | 摄像头连接与启动 | 1. 启动程序。 2. 默认选择“默认相机”。 3. 点击“开始监控”按钮。 | 1. “开始监控”按钮禁用,“停止监控”按钮启用。 2. 视频画面区域显示实时图像。 3. 状态栏显示“运行中”。 4. FPS标签显示非零数值。 |
TC_CAM_02 | 视频采集与显示 | 摄像头停止 | 1. 在监控运行状态下,点击“停止监控”按钮。 | 1. “开始监控”按钮启用,“停止监控”按钮禁用。 2. 视频画面停止更新(显示最后一帧或空白)。 3. 状态栏显示“已停止”。 4. FPS标签显示0。 |
TC_CAM_03 | 视频采集与显示 | 摄像头选择 | 1. 停止监控。 2. 在下拉框中选择另一个有效的相机ID。 3. 点击“开始监控”。 | 能够成功切换到并显示所选摄像头的画面。 |
TC_SKL_01 | 骨骼检测 | 单人骨骼检测与显示 | 1. 启动监控。 2. 确保画面中有一个清晰可见的人。 | 视频画面中应能实时、稳定地绘制出该人体的骨骼关键点和连接线。 |
TC_SKL_02 | 骨骼检测 | 多人骨骼检测 | 1. 启动监控。 2. 确保画面中有多个清晰可见的人。 | 系统应能同时检测并绘制多个人的骨骼(Mediapipe Pose默认只检测最显著的一个人,若需多人需调整或使用其他模型)。 (预期可能失败或只检测一人) |
TC_SKL_03 | 骨骼检测 | 遮挡/姿态变化下的稳定性 | 1. 启动监控。 2. 测试者在画面中做不同动作(行走、转身、部分遮挡)。 | 在大部分情况下,骨骼点应能被持续跟踪和绘制,允许短时间丢失或跳动。严重遮挡或快速移动可能导致跟踪失败。 |
TC_ZONE_01 | 危险区域管理 | 绘制危险区域 | 1. 点击“绘制危险区域”按钮。 2. 在视频画面上单击3个或更多点。 3. 双击完成绘制。 | 1. 按钮文本变为“完成绘制”。 2. 单击时在画面上添加可见的顶点。 3. 双击后,形成闭合多边形,区域半透明填充,按钮文本恢复,状态提示恢复。 |
TC_ZONE_02 | 危险区域管理 | 清除危险区域 | 1. 成功绘制一个危险区域后,“清除危险区域”按钮应启用。 2. 点击“清除危险区域”按钮。 | 画面上的危险区域绘制消失,“清除危险区域”按钮禁用。 |
TC_ZONE_03 | 危险区域管理 | 无效区域绘制 | 1. 点击“绘制危险区域”按钮。 2. 只点击1或2个点后双击。 | 无法形成危险区域,无区域绘制显示。 |
TC_ZONE_04 | 危险区域入侵 | 单人入侵检测 | 1. 定义一个危险区域。 2. 启动监控,确保“危险区域检测”已勾选。 3. 测试者走进危险区域。 | 1. 危险区域填充颜色变为红色并闪烁。 2. 报警信息列表出现“警告:有人进入危险区域!”。 3. 若音频报警开启,播放对应的警笛声音效。 4. 视频上方出现警告覆盖层。 |
TC_ZONE_05 | 危险区域入侵 | 离开区域后警报解除 | 1. 在触发区域入侵警报后,测试者离开危险区域。 | 1. 危险区域恢复为橙色填充。 2. 音频警报停止(若正在播放)。 3. 警告覆盖层消失。 4. 报警信息列表保留记录。 |
TC_POS_01 | 危险姿势检测 | 弯腰过度检测 | 1. 启动监控,确保“危险姿势检测”已勾选。 2. 测试者做出大幅度弯腰动作(角度超过60度)。 | 1. 报警列表出现“警告:检测到弯腰过度!”。 2. 若音频报警开启,播放对应的蜂鸣声音效。 3. 视频上方出现警告覆盖层。 |
TC_POS_02 | 危险姿势检测 | 手臂高举检测 | 1. 启动监控,确保“危险姿势检测”已勾选。 2. 测试者将一只或两只手举过头顶。 | 1. 报警列表出现“警告:手臂举过头顶,可能存在危险!”。 2. 若音频报警开启,播放对应的扫频声音效。 3. 视频上方出现警告覆盖层。 |
TC_POS_03 | 危险姿势检测 | 身体失衡检测 | 1. 启动监控,确保“危险姿势检测”已勾选。 2. 测试者大幅度向一侧倾斜身体,使左右肩高度差明显。 | 1. 报警列表出现“警告:身体倾斜过度,姿势不平衡!”。 2. 若音频报警开启,播放对应的啁啾声音效。 3. 视频上方出现警告覆盖层。 |
TC_POS_04 | 危险姿势检测 | 蹲下姿势检测 | 1. 启动监控,确保“危险姿势检测”已勾选。 2. 测试者做出明显的下蹲动作。 | 1. 报警列表出现“警告:检测到蹲下姿势,可能存在危险!”。 2. 若音频报警开启,播放对应的低频双声蜂鸣音效。 3. 视频上方出现警告覆盖层。 |
TC_POS_05 | 危险姿势检测 | 奔跑姿势检测 | 1. 启动监控,确保“危险姿势检测”已勾选。 2. 测试者在画面中做出奔跑动作(有明显跨步和/或手臂摆动/前倾)。 | 1. 报警列表出现“警告:检测到奔跑姿势,请减速慢行!”。 2. 若音频报警开启,播放对应的快速警笛声音效。 3. 视频上方出现警告覆盖层。 |
TC_POS_06 | 危险姿势检测 | 正常动作不误报 | 1. 启动监控,确保“危险姿势检测”已勾选。 2. 测试者进行正常站立、行走、小幅弯腰、正常坐下等动作。 | 系统不应触发危险姿势报警。 |
TC_ALERT_01 | 报警功能 | 音频报警开关 | 1. 触发任一危险警报。 2. 取消勾选“启用语音警报”。 3. 再次触发危险警报。 4. 重新勾选,再次触发。 | 1. 取消勾选后,触发警报时不播放声音。 2. 重新勾选后,触发警报时恢复播放声音。 |
TC_ALERT_02 | 报警功能 | 报警信息列表与清除 | 1. 多次触发不同类型的警报。 2. 点击“清除所有警报”按钮。 | 1. 每次触发警报,列表顶部增加一条带时间戳的记录。 2. 点击清除按钮后,列表内容清空。 |
TC_ALERT_03 | 报警功能 | 报警冷却机制 | 1. 测试者持续停留在危险区域内或保持危险姿势。 | 音频警报应在首次触发后播放一次,然后在冷却时间(3秒)内不再重复播放同一类型警报,冷却时间过后若危险状态持续,可再次触发。 |
TC_UI_01 | 用户界面 | 界面响应与布局 | 1. 启动程序,观察界面布局。 2. 调整窗口大小。 3. 操作各个按钮、复选框、下拉框。 | 1. 界面元素布局合理,无重叠遮挡。 2. 调整窗口大小时,控件能自适应调整位置和大小。 3. 控件操作响应正常。 |
TC_PERF_01 | 性能 | 处理帧率 (FPS) | 1. 启动监控,观察FPS标签显示。 2. 在不同场景(单人、多人、复杂背景)下观察FPS。 | 在测试硬件环境下,FPS应能稳定在15-30帧左右(具体数值依赖于摄像头分辨率、模型复杂度和硬件性能),保证基本的实时性。 |
TC_PERF_02 | 性能 | CPU/内存占用 | 1. 启动监控,使用系统任务管理器观察程序的CPU和内存占用率。 | CPU和内存占用应在合理范围内,不会导致系统卡顿。 |
6.3 测试结果与分析
(注:以下为模拟的测试结果与分析,实际测试结果会因环境和具体实现细节而异)
在上述测试环境下,对系统进行了全面的测试,主要结果如下:
-
基本功能:
- 摄像头连接、启停、切换功能正常(TC_CAM_01, TC_CAM_02, TC_CAM_03)。
- 单人骨骼检测准确,绘制流畅(TC_SKL_01)。多人检测方面,如预期,Mediapipe Pose标准模型主要跟踪画面中最显著的一个人(TC_SKL_02),在部分身体遮挡或快速移动时,骨骼点可能丢失或跳动,但通常能较快恢复跟踪(TC_SKL_03)。
- 危险区域的交互式绘制、清除功能符合预期(TC_ZONE_01, TC_ZONE_02, TC_ZONE_03)。
- 危险区域入侵检测灵敏,人员进入设定区域后能及时触发视觉和音频报警,离开后报警解除(TC_ZONE_04, TC_ZONE_05)。区域入侵判断阈值(2分)在大多数情况下表现良好。
-
危险姿势识别:
- 弯腰过度: 对幅度较大的弯腰动作识别准确(TC_POS_01)。对于临界角度或动作幅度不大的情况,可能存在漏报。
- 手臂高举: 能准确识别单手或双手举过头顶的动作(TC_POS_02)。
- 身体失衡: 对明显的身体倾斜能正确报警(TC_POS_03)。阈值(15%帧高)相对宽松,对轻微倾斜不敏感。
- 蹲下姿势: 对于较标准的下蹲动作识别效果较好(TC_POS_04)。但对于半蹲或动作变形的情况,膝盖角度和髋部高度比例的组合判断可能不够鲁棒。
- 奔跑姿势: 能识别出有明显跨步和身体动态特征的奔跑动作(TC_POS_05)。但对于原地快速踏步或步幅较小的快走,可能存在漏报。手臂摆动特征的判断相对简单,可能受限于视角。
- 误报情况: 在正常动作测试中(TC_POS_06),系统基本没有出现误报,表明设定的阈值和逻辑具有一定的区分度。但在某些复杂或非标准动作下,仍有误报的可能性。
-
报警功能:
- 音频报警开关功能正常(TC_ALERT_01)。
- 报警信息列表能正确记录带时间戳的报警信息,清除功能正常(TC_ALERT_02)。
- 音频报警的冷却机制(3秒)有效,避免了蜂鸣般的重复报警(TC_ALERT_03)。本地生成的不同类型音效可以正常播放,并能有效区分不同危险类型。
-
用户界面与性能:
- 用户界面布局清晰,控件响应正常,窗口大小调整时自适应良好(TC_UI_01)。中文显示正常。
- 在i7+RTX3060环境下,使用640x480分辨率,系统FPS稳定在25-30帧;在M1 Pro环境下,FPS稳定在20-25帧左右,均满足实时性要求(TC_PERF_01)。更高分辨率或更复杂的模型会导致FPS下降。
- CPU占用率在15-30%之间波动,内存占用约200-400MB,属于可接受范围(TC_PERF_02)。
6.4 测试结论与讨论
综合测试结果来看,本系统成功实现了设计目标,能够实时检测用户定义的危险区域入侵以及弯腰、举手、失衡、蹲下、奔跑等多种危险动作,并能触发及时的视觉和本地化音频报警。系统的用户界面友好,操作便捷,性能基本满足实时监控的需求。
存在的问题与局限性:
-
姿态识别局限性:
- 2D信息限制: 系统完全基于2D图像进行分析,缺乏深度信息,对于某些姿态(如弯腰角度)的判断可能受限于观察视角。例如,正对或背对摄像头的弯腰可能难以准确计算角度。
- 阈值依赖: 危险动作的判断很大程度上依赖于人工设定的阈值(角度、距离比例等)。这些阈值可能需要根据具体应用场景、摄像头架设角度、人员体型等因素进行调整才能达到最佳效果,通用性有待提高。
- 动作复杂性: 对于非标准、不连续或过于快速的动作,识别准确率可能会下降。例如,蹲下和弯腰的区分,奔跑和快走的区分等。
- 多人场景: 当前实现主要针对单人进行姿态分析,多人同时出现在画面中且相互遮挡时,危险检测的准确性会受到影响。
-
环境因素影响: 光照条件、背景复杂度、摄像头抖动、部分遮挡等因素仍可能影响骨骼检测的稳定性和准确性,进而影响危险判断。
-
区域入侵检测精度: 当前使用关键点判断入侵,当人体只有非关键部位(如小臂)进入区域时可能不会触发报警。可以考虑结合人体轮廓或边界框进行更全面的判断。
-
音频报警的有效性: 虽然实现了本地化和差异化音效,但在嘈杂环境下,音频报警的有效性可能会降低。
这些局限性为后续的系统优化和改进指明了方向。
7. 结论与展望
7.1 结论
本文围绕提高工作场所及特定场景安全监控智能化水平的目标,成功设计并实现了一套基于人体骨骼识别的危险动作报警系统。系统利用OpenCV获取实时视频流,通过Mediapipe Pose框架提取人体骨骼关键点,在此基础上实现了危险区域入侵检测以及对弯腰过度、手臂高举、身体失衡、蹲下、奔跑等多种危险动作的识别算法。为了提供及时有效的警示,系统集成了视觉报警(UI界面提示、警告覆盖层)和本地化、差异化的音频报警机制。系统的用户界面使用PySide6构建,提供了良好的交互性和易用性。
主要研究成果和贡献如下:
- 整合了多种危险场景检测: 系统不仅实现了常见的危险区域入侵检测,还针对性地设计并实现了多种典型危险工作姿势或异常行为的检测算法,扩展了监控系统的能力范围。
- 实现了本地化音频报警: 采用Numpy和Scipy生成不同模式的警告音效,摆脱了对网络TTS服务的依赖,提高了系统的独立性和响应速度,并设计了多后端播放策略以增强兼容性。
- 构建了完整的应用系统: 将底层的视觉算法、危险判断逻辑与上层的图形用户界面、报警机制有效结合,形成了一个功能完整、界面友好的原型系统。
- 注重实用性和兼容性: 解决了中文显示、跨平台字体加载、音频播放兼容性等实际开发中遇到的问题,提高了系统的实用价值。
通过系统测试,验证了系统在功能上的完整性和可行性,其在实时性、准确性方面基本达到设计预期,能够在一定程度上辅助安全管理人员及时发现潜在风险。
7.2 系统不足
尽管本系统取得了一定的成果,但仍存在一些不足之处,主要体现在:
- 二维分析的固有局限: 基于单目摄像头的二维骨骼信息无法完全反映三维空间中的人体姿态,容易受视角影响,导致某些情况下角度计算不准确或姿态误判。
- 算法鲁棒性有待提升: 对于光照剧变、严重遮挡、多人复杂交互等场景,骨骼检测的稳定性会下降,进而影响危险判断的准确率。危险动作识别的阈值设定偏向经验化,缺乏自适应能力。
- 危险动作类型有限: 当前系统仅能识别预定义的几种危险动作,对于跌倒、攀爬、打斗等其他常见危险行为尚未覆盖。
- 缺乏学习与适应能力: 系统基于固定的规则和阈值进行判断,无法根据历史数据或特定场景进行学习和优化,智能化程度有待提高。
7.3 展望
针对当前系统的不足和未来应用的需求,可以从以下几个方面进行改进和展望:
- 引入三维信息: 考虑使用双目摄像头或RGB-D深度摄像头(如Kinect, RealSense)获取深度信息,进行三维人体姿态估计,可以更准确地计算空间角度、距离,克服二维分析的局限性,提高姿态识别的准确性。
- 增强算法鲁棒性:
- 研究更先进的骨骼识别算法或模型,提高在遮挡、多人场景下的检测效果。
- 结合人体跟踪算法(如DeepSORT),在多人场景下为每个人分配ID,实现对个体行为的连续分析。
- 探索使用机器学习或深度学习方法进行危险动作分类,替代或辅助基于规则的判断,提高模型的泛化能力和自适应性。例如,可以收集特定场景的危险动作数据,训练专门的分类器(如LSTM, GCN)。
- 扩展危险行为识别种类: 根据具体应用需求,增加对跌倒、攀爬、打斗、异常滞留等更多类型危险行为的识别算法。
- 优化人机交互与报警方式:
- 允许用户更方便地调整危险判断的阈值。
- 提供更丰富的报警方式,如联动声光报警器、发送短信或App通知给管理人员等。
- 考虑引入危险等级评估,根据危险的严重程度采取不同的报警策略。
- 系统集成与云端化: 将系统部署到边缘计算设备上,实现更高效的前端处理。考虑将报警事件、统计数据等上传到云平台,进行集中的监控管理和数据分析。
总之,基于骨骼识别的危险动作报警系统具有广阔的应用前景。随着相关技术的不断进步,未来的系统将更加智能、准确、可靠,为保障人员安全发挥更重要的作用。本研究为此方向提供了一个基础性的实践探索和参考。
参考文献
(请在此处根据实际参考的文献资料,按照标准的参考文献格式列出,例如:)
[1] Google Developers. MediaPipe Pose [EB/OL]. https://developers.google.com/mediapipe/solutions/vision/pose_landmarker, 2023.
[2] Bradski, G. The OpenCV Library[J]. Dr. Dobb’s Journal of Software Tools, 2000.
[3] Cao Z, Hidalgo G, Simon T, et al. OpenPose: realtime multi-person 2D pose estimation using Part Affinity Fields[J]. IEEE transactions on pattern analysis and machine intelligence, 2019, 43(1): 172-186.
[4] Harris C R, Millman K J, van der Walt S J, et al. Array programming with NumPy[J]. Nature, 2020, 585(7825): 357-362.
[5] Virtanen P, Gommers R, Oliphant T E, et al. SciPy 1.0: fundamental algorithms for scientific computing in Python[J]. Nature methods, 2020, 17(3): 261-272.
[6] Qt for Python (PySide6) Documentation [EB/OL]. https://doc.qt.io/qtforpython/, 2023.
[7] … (其他相关技术、算法、应用场景的参考文献) …
致谢
(在此处填写致谢内容,感谢指导老师、同学、家人等在论文完成过程中给予的帮助和支持。)