十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv8与PyQt5的密集人群人体检测识别计数系统设计

基于YOLOv8与PyQt5的密集人群人体检测识别计数系统设计 基于深度学习YOLOv8PyQt5的密集人群人体检测识别计数系统设计如果你正在做一个“摄像头画面里到底有多少人”的桌面应用你会发现一个尴尬的现实目标检测模型跑通了但离“能交给别人用”还差得很远。模型输出的只是坐标框和置信度用户想要的是“当前画面人数”“密度预警”“自动统计结果”。这个从算法到产品的中间地带正是 PyQt5 这类桌面 GUI 框架的价值所在。本文要解决的问题很具体如何把 YOLOv8 人体检测模型封装成一个带界面的密集人群计数系统支持图片、视频和摄像头实时画面输入并且在界面上直接看到检测框、人数信息和告警结果。文章涉及模型选型、数据集标注、训练配置、PyQt5 界面设计、推理线程优化、计数逻辑实现以及常见报错排查读完可以直接对照落地。项目不会要求你从零训练一个神经网络。YOLOv8 提供了足够好的预训练权重COCO 数据集里也有 person 这个类别。你要做的核心工作是把这个开源模型用对、用稳再用 PyQt5 把它包装成非技术用户也能操作的软件。换句话说你写的不只是一个算法 Demo而是一个可交付的桌面工具。1. 这篇文章真正要解决的问题人群计数听起来是个小众需求但在实际工程里出现频率比想象中高很多。商场需要统计各楼层客流景区需要监控游客密度校园需要管理食堂和图书馆的人流安防项目需要判断某个区域是否过度拥挤。这些场景的共同特点是不能只靠人眼看监控需要软件自动给出一个可量化的数字并在人数超过阈值时发出提示。传统的解决方案大致有两条路。一条是红外对射或闸机方案能计数但无法感知密度分布也不知道人群在画面中的具体位置。另一条是纯手工统计准确率全凭责任心完全不具备实时性。深度学习目标检测模型出现后这个问题有了更自然的解法直接检测画面里的每一个人画框、计数、定位一次完成。但算法只是第一步。很多初学者拿到 YOLOv8 之后跑通了命令行推理便以为项目已经结束。真正接手实际项目时会发现模型推理和产品交付之间还隔着好几层图片路径是写死的没有交互界面算法参数改起来要动代码视频流处理时界面卡死人数统计结果没有导出非技术用户根本不会用命令行。这些问题恰恰是 PyQt5 能解决的。所以本文的技术路线是用 YOLOv8 做人体的目标检测用 PyQt5 构建桌面交互界面通过 OpenCV 处理图像与视频流把“检测框叠加展示”和“实时人数统计”串成完整链路。读完这篇文章你应该能够搭建一个这样的人体检测识别计数系统理解每个环节为什么这样设计并且能针对自己的业务场景做替换和修改。2. YOLOv8 与 PyQt5 的核心概念2.1 YOLOv8 的检测机制YOLOv8 是 Ultralytics 团队在 2023 年初推出的目标检测框架沿用了 YOLO 系列“单阶段、端到端”的设计思路。所谓单阶段指的是它在一次前向推理中直接回归出目标的类别和位置而不像传统两阶段检测器那样先产生候选区域再逐个分类。这种做法换来了极快的推理速度配合 GPU 可以做到实时分析视频流。YOLOv8 的网络结构大体上可以拆成三个部分Backbone 负责提取图像特征Neck 负责融合多尺度特征Head 负责输出检测结果。Backbone 沿用了 C2f 结构比 YOLOv5 的 C3 模块拥有更丰富的梯度流对小目标的特征表达更好Head 部分采用了 Anchor-Free 的 Decoupled Head分类和回归分支各自独立训练收敛更稳定。通俗理解就是“眼睛”看得更细“大脑”分工更清晰。对于人体检测这个具体任务YOLOv8 的 COCO 预训练模型已经包含了 person 类别。你不需要从随机初始化开始训练而是可以在预训练权重的基础上做迁移学习让模型更快地适配自己的数据分布。这里也正好回答一个很多人关心的问题YOLOv8 跑检测需要 GPU 吗答案是看场景用 CPU 也能跑但实时视频流建议用 GPU。GTX 1660Ti 这类显卡已经能满足 640 分辨率下的实时推理需求部署时按自己的显存和精度需求选择合适的模型规格就好。2.2 PyQt5 在视觉系统里的角色PyQt5 是 Qt5 应用框架的 Python 绑定常被用来开发带有图形界面的桌面工具。在视觉检测项目里它承担的职责不只是“画几个按钮和窗口”还包括显示视频画面、更新检测框、接收用户参数输入、触发文件选择、管理算法的启停状态。把模型推理和 GUI 放在同一个进程时最容易出现的问题是画面卡死。原因在于 PyQt5 的主线程负责事件循环和界面刷新如果在主线程里直接执行耗时的视频推理循环界面就失去了响应。解决办法是把推理任务放到 QThread 子线程里推理完成后通过信号把结果传回主线程更新界面。这是视觉 GUI 开发中最值得重视的架构点后文会有完整代码演示。2.3 人体检测和计数的逻辑边界关于“检测”和“计数”需要先做一个概念区分。人体检测的目标是找出画面中每个人的包围框输出形式是 [x1, y1, x2, y2, conf, class_id]。计数则是把检测结果按业务规则汇总成一个数字例如“当前画面人数”“某个区域内人数”或“一段时间内累计通过人数”。如果只看静态图片或单帧视频计数就是统计当前帧检测框的数量。如果要做跨帧累计计数比如统计一个时间段内经过某个区域的人数就要引入目标跟踪比如 YOLOv8 内置的 ByteTrack 或 BoT-SORT。本文先以单帧计数为主因为这是“识别计数系统”中最基础也最常用的能力跨帧跟踪会在工程建议中补充说明。3. 环境准备与前置条件3.1 操作系统与硬件选择从项目可行性出发推荐在 Windows 10/11 或 Ubuntu 20.04 以上系统完成开发两者在依赖安装上没有本质区别。硬件方面如果只是验证算法流程CPU 即可运行但推理速度会明显偏慢如果后续要实时处理摄像头画面建议准备一张 NVIDIA 显卡并安装与 CUDA 匹配的 PyTorch 版本。关于具体版本建议以实际安装时 PyTorch 官网给出的 CUDA 对应关系为准。3.2 创建虚拟环境新建一个独立的 Python 虚拟环境是必要的。这里推荐 Python 3.8 到 3.10 的版本区间因为 YOLOv8 和 PyQt5 在这个区间兼容性最成熟。如果系统已经安装了 Anaconda可以用 conda 创建conda create -n crowd_count python3.9 conda activate crowd_count如果不使用 Anaconda也可以用 venvpython -m venv crowd_count_env crowd_count_env\Scripts\activate3.3 安装依赖库激活虚拟环境后依次安装 PyQt5、OpenCV、Ultralytics、NumPy 以及其他辅助库pip install pyqt5 pip install opencv-python pip install ultralytics pip install numpy pip install pillow这里说明一下各库的用途。PyQt5 用于构建桌面界面OpenCV 负责读取图片、视频和摄像头画面也负责在图像上画检测框Ultralytics 提供 YOLOv8 模型的加载和推理接口NumPy 是 OpenCV 和 PyTorch 之间图像数据转换的基础Pillow 则方便在 Qt 界面中转换图像格式。安装完成后建议先跑一个最简单的版本确认 YOLOv8 可用python -c from ultralytics import YOLO; model YOLO(yolov8n.pt); print(model.names)首次执行会自动下载 yolov8n.pt 预训练权重。打印出类别字典后环境就说明已经通了一半。4. 整体系统架构与核心流程拆解一个完整的密集人群检测计数系统在逻辑上可以拆成三层输入层、算法层、展示层。输入层解决“图像从哪里来”的问题支持图片文件、视频文件和摄像头实时画面算法层负责加载 YOLOv8 模型对每一帧图像执行推理筛选出 person 类别并统计人数展示层通过 PyQt5 窗口显示原始画面、检测结果叠加画面以及人数信息。这套架构的推荐调用流程如下用户在 PyQt5 界面中选择输入源图片、视频或摄像头。系统读取一帧图像转换为 YOLOv8 推理所需的格式。模型执行前向推理返回检测框、置信度和类别 ID。程序过滤出类别 ID 等于 personCOCO 中为 0的目标框。在图像上绘制检测框和人数标签更新界面显示。如果人数超过设定阈值界面给出告警提示。用户可随时停止检测或切换输入源。这里特别要强调“类别过滤”这一步。YOLOv8 的 COCO 预训练模型能检测 80 类目标如果不加过滤画面里的椅子、背包、手机都会出现在检测结果里。项目里要做人体识别就必须通过类别 ID 只保留 person 类别。4.1 为什么使用 QThread 而不是在主线程推理前面已经提到PyQt5 主线程负责事件循环如果在主线程里执行 while True 的视频推理循环窗口会失去响应。哪怕用户只是想把窗口拖一下位置操作系统也会提示“程序未响应”。正确的做法是把视频处理放到继承自 QThread 的工作线程中。工作线程每处理完一帧就通过 Qt 信号把格式转换好的 QImage 和人数结果传递回主线程。主线程只负责接收信号并更新 Label 控件不执行任何耗时操作。这个模式在 PyQt 图像项目中是最基础也最实用的架构。5. 完整示例与代码实现5.1 模型推理工具类在实际工程中推荐把模型加载和推理封装成一个独立的模块方便界面和命令行共用。以下代码定义了一个 PersonDetector 类# 文件路径person_detector.py from ultralytics import YOLO class PersonDetector: def __init__(self, weights_pathyolov8n.pt, conf_thres0.5): self.model YOLO(weights_path) self.conf_thres conf_thres self.person_class_id 0 # COCO 数据集中 person 的类别 ID 为 0 def detect(self, frame): results self.model.predict(frame, confself.conf_thres, verboseFalse)[0] person_boxes [] for box in results.boxes: cls_id int(box.cls[0].item()) if cls_id ! self.person_class_id: continue x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0].item()) person_boxes.append({ bbox: [int(x1), int(y1), int(x2), int(y2)], conf: round(conf, 3) }) return person_boxes这段代码的要点有两个。第一通过cls_id ! self.person_class_id过滤非人体目标第二把每个检测结果整理成字典列表后续界面绘制和人数统计都基于这个列表展开。5.2 使用 OpenCV 画检测框与人数标签拿到检测框之后下一步是在原始图像上画出矩形框和文字。建议单独封装函数让界面代码保持精简# 文件路径draw_utils.py import cv2 def draw_detections(frame, person_boxes): result_frame frame.copy() for box in person_boxes: x1, y1, x2, y2 box[bbox] conf box[conf] cv2.rectangle(result_frame, (x1, y1), (x2, y2), (0, 255, 0), 2) label fperson {conf:.2f} cv2.putText(result_frame, label, (x1, max(y1 - 10, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) total_count len(person_boxes) cv2.putText(result_frame, fPerson Count: {total_count}, (15, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2) return result_frame如果业务上只需要一个总数统计len(person_boxes)就够了。如果要求分区计数可以在绘制函数里增加区域判断逻辑后续工程建议部分会再展开。5.3 PyQt5 主窗口设计主窗口的核心元素包括显示图像的 QLabel、输入源选择按钮、人数选择阈值、启动停止按钮以及一个文本区域显示当前人数和状态。以下是一个可运行的主界面代码结构# 文件路径main_window.py import sys import cv2 from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QSpinBox, QTextEdit, QMessageBox) from PyQt5.QtCore import QTimer, Qt from PyQt5.QtGui import QImage, QPixmap from person_detector import PersonDetector from draw_utils import draw_detections class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(人群检测识别计数系统 - YOLOv8 PyQt5) self.setMinimumSize(960, 640) self.detector PersonDetector(weights_pathyolov8n.pt, conf_thres0.5) self.cap None self.timer QTimer() self.timer.timeout.connect(self.process_frame) self.video_label QLabel(请选择图片、视频或摄像头开始检测) self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setStyleSheet(border: 1px solid #ccc; background: #000;) self.btn_image QPushButton(选择图片) self.btn_video QPushButton(选择视频) self.btn_camera QPushButton(打开摄像头) self.btn_stop QPushButton(停止检测) self.spin_conf QSpinBox() self.spin_conf.setRange(10, 95) self.spin_conf.setValue(50) self.spin_conf.setSuffix( %) self.text_log QTextEdit() self.text_log.setReadOnly(True) self.text_log.setMaximumHeight(120) self._init_ui() self._connect_events() def _init_ui(self): central QWidget() layout QVBoxLayout(central) btn_layout QHBoxLayout() btn_layout.addWidget(self.btn_image) btn_layout.addWidget(self.btn_video) btn_layout.addWidget(self.btn_camera) btn_layout.addWidget(self.btn_stop) btn_layout.addWidget(QLabel(置信度阈值:)) btn_layout.addWidget(self.spin_conf) btn_layout.addStretch() layout.addWidget(self.video_label) layout.addLayout(btn_layout) layout.addWidget(self.text_log) self.setCentralWidget(central) def _connect_events(self): self.btn_image.clicked.connect(self.open_image) self.btn_video.clicked.connect(self.open_video) self.btn_camera.clicked.connect(self.open_camera) self.btn_stop.clicked.connect(self.stop_detection) def update_log(self, message): self.text_log.append(message) def open_image(self): file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , Image Files (*.jpg *.png *.jpeg *.bmp)) if not file_path: return self.stop_detection() frame cv2.imread(file_path) if frame is None: QMessageBox.warning(self, 错误, 图片读取失败) return self.detector.conf_thres self.spin_conf.value() / 100.0 person_boxes self.detector.detect(frame) result_frame draw_detections(frame, person_boxes) self.show_frame(result_frame) self.update_log(f图片检测完成画面人数: {len(person_boxes)}) def open_video(self): file_path, _ QFileDialog.getOpenFileName( self, 选择视频, , Video Files (*.mp4 *.avi *.mov *.mkv)) if not file_path: return self.stop_detection() self.cap cv2.VideoCapture(file_path) if not self.cap.isOpened(): QMessageBox.warning(self, 错误, 视频打开失败) return self.timer.start(30) self.update_log(f开始处理视频: {file_path}) def open_camera(self): self.stop_detection() self.cap cv2.VideoCapture(0) if not self.cap.isOpened(): QMessageBox.warning(self, 错误, 摄像头打开失败请检查设备) return self.timer.start(30) self.update_log(摄像头已打开) def process_frame(self): ret, frame self.cap.read() if not ret: self.stop_detection() self.update_log(视频读取完毕或读取失败) return self.detector.conf_thres self.spin_conf.value() / 100.0 person_boxes self.detector.detect(frame) result_frame draw_detections(frame, person_boxes) self.show_frame(result_frame) self.update_log(f当前画面人数: {len(person_boxes)}) def show_frame(self, frame): rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_frame.shape bytes_per_line ch * w qimg QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap QPixmap.fromImage(qimg) pixmap pixmap.scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.video_label.setPixmap(pixmap) def stop_detection(self): self.timer.stop() if self.cap is not None: self.cap.release() self.cap None这个界面已经具备最基本的功能闭环选择图片立刻得到检测结果选择视频或摄像头后定时逐帧处理停止按钮负责释放资源。show_frame方法里值得注意的一个细节是OpenCV 的图像格式是 BGR而 Qt 的 QImage 默认使用 RGB所以必须先用cv2.cvtColor转换否则画面中的红色和蓝色会颠倒。5.4 程序入口最后提供一个入口文件便于直接启动# 文件路径main.py import sys from PyQt5.QtWidgets import QApplication from main_window import MainWindow if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())按照上面的文件结构person_detector.py、draw_utils.py、main_window.py、main.py放在同一目录即可运行。运行后选择一张包含多人的图片应该能在界面上看到绿色检测框和红色人数统计文本。6. 运行结果与效果验证6.1 运行命令在激活虚拟环境的前提下执行python main.py6.2 验证流程第一验证模型加载。程序启动后控制台应出现 Ultralytics 的版本信息且没有报错退出。第二验证图片检测。点击“选择图片”选择一张拍摄于商场、车站或街道的多人照片。预期结果是画面中的人物被绿色矩形框标出左上角或对应位置显示 person 标签和置信度画面左上角显示总人数。第三验证视频检测。选择一段人流视频窗口应持续刷新画面每一帧的人数标签随画面内容变化。如果视频帧率较低可以调节 QTimer 的间隔从 30 毫秒调整为 50 毫秒降低处理频率。第四验证置信度阈值。把滑块从 50% 调整到 80%检测框数量通常会减少因为低置信度的预测结果被过滤掉了。这一步可以直观体会置信度阈值对结果的影响。如果运行失败第一步应该看的不是界面代码而是命令行窗口中的异常堆栈。常见的几类问题会在下一节集中说明。7. 常见问题与排查思路问题现象可能原因排查方式解决方案启动时提示找不到 ultralytics虚拟环境未激活或依赖未安装执行pip list检查包列表重新执行pip install ultralytics程序提示找不到 model weights当前目录没有 yolov8n.pt检查项目目录文件列表首次运行会自动下载或手动下载放入项目目录界面打开后立即无响应推理被放在主线程或视频分辨率过高检查代码是否使用了 QTimer 子线程处理确保推理逻辑通过 QTimer 回调执行而不是在主线程中死循环图片颜色异常红色和蓝色对调没有把 BGR 转换为 RGB检查 show_frame 方法添加cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)摄像头打不开摄像头被其他程序占用关闭其他视频软件再试释放摄像头设备后重新打开检测框覆盖了大量非人体目标没有按类别 ID 过滤 person检查 person_detector.py 中的过滤逻辑确认person_class_id 0视频处理很卡CPU 推理速度不足或图像尺寸过大观察任务管理器的 CPU 占用率降低检测输入尺寸或使用 GPU 推理计数结果总是比实际人数多置信度阈值偏低引入误检逐步提高置信度阈值观察结果变化在检测器中设置更高的 conf_thres8. 最佳实践与工程建议8.1 数据集准备与模型微调如果使用 COCO 预训练权重在常规场景下行得通可以先用默认权重上线。但如果面对的密集人群场景比较特殊比如俯拍视角的候车大厅或者光照条件特殊的夜间监控建议用自己的数据做微调。准备数据时每一张图片对应一个同名的 txt 文件每行格式为class_id x_center y_center width height坐标是相对图片宽高的归一化数值。标注完成后把数据集按 8:1:1 划分为训练集、验证集和测试集并准备好 data.yaml 文件train: datasets/crowd/images/train val: datasets/crowd/images/val test: datasets/crowd/images/test nc: 1 names: [person]训练时可以在 terminal 中执行一条命令yolo detect train datadata.yaml modelyolov8s.pt epochs100 imgsz640 batch16这里选择 yolov8s 而不是 yolov8m 或 yolov8l是一个偏工程的判断在人群计数场景中检测速度的重要性通常不亚于精度。训练完成后检查 runs/detect/train 目录下的结果曲线重点关注验证集上的 mAP50 和 Precision、Recall 变化。如果 mAP50 已经超过 0.85模型基本可以用于试点。8.2 模型选择与速度权衡YOLOv8 官方提供了 n、s、m、l、x 五种规格。在人群检测项目里如果只是希望在普通电脑上跑通 Demoyolov8n 是最稳妥的选择体积小、速度快CPU 也能勉强跑出每秒几帧的效果。如果使用 GPU且画面里人群密度较高推荐 yolov8s 或 yolov8m检测框定位会更准。显存有限时可以适当降低推理尺寸比如把 imgsz 从 640 降到 480速度提升明显精度损失通常可接受。另外YOLOv8 也可以直接在推理时指定设备results self.model.predict(frame, confself.conf_thres, device0, verboseFalse)device0表示使用第一张 GPU。8.3 计数逻辑增强“检测到人”不等于“统计到人”。如果只是统计当前画面人数直接数检测框数量即可但业务常常追问的是“某个区域人数”“某段时间通过多少人”这就涉及两种增强方案。第一种是区域计数。在界面上预先设定不计区域或警戒区域判断检测框中心是否落在目标区域内只统计区域内的目标。OpenCV 的cv2.pointPolygonTest可以判断一个点是否在多边形内部。第二种是跨帧计数。画面中同一人连续出现在多帧里如果每帧都计一次数字会虚高。解决方案是引入跟踪算法给每个目标分配一个稳定的 ID然后根据 ID 去重统计。YOLOv8 的model.track()方法内置了 ByteTrack 跟踪器可以返回持续的目标 ID适合做闸机或通道通行统计。8.4 卡顿优化与线程安全在实际使用中视频流卡顿最常见的根源不是模型推理而是“推理 绘制 控件更新”挤在主循环里。即便是 QTimer 的定时回调也不代表所有逻辑都在子线程中。更严格的做法是使用 QThread工作线程只负责读帧和推理通过信号发出 QImage 和人数结果主线程只负责接收信号刷新界面。在高帧率视频场景下还要避免“处理速度跟不上显示速度”导致队列积压。简单做法是设定标志位如果上一帧尚未处理完成则丢弃当前帧确保界面显示的是最新结果而不是滞后结果。8.5 打包发布如果目标是交付给非技术用户可以用 PyInstaller 打包成 exepip install pyinstaller pyinstaller -w -F main.py打包需要注意的是模型文件路径问题。PyInstaller 会把项目文件打包进单文件但运行时模型文件不会自动解压到程序目录。更稳妥的方式是使用--add-data把权重文件打进包中并在代码中用相对路径配合sys._MEIPASS动态获取路径。这部分细节比较多建议在实际部署时单独测试。9. 总结与后续学习方向密集人群人体检测识别计数看起来是个以“检测”为核心的项目但真正决定系统好用程度的是工程化能力。YOLOv8 负责看得准PyQt5 负责交互逻辑OpenCV 负责图像处理而把这些组件稳定串在一起的是对线程模型、数据格式转换、资源释放和错误处理的把握。从实践角度建议你按三步走第一步跑通本文的完整代码理解 PyQt5 界面与 YOLOv8 推理的协作关系第二步尝试微调模型或优化计数逻辑让系统更贴合自己的业务场景第三步考虑把系统接入真实视频源比如局域网摄像头 RTSP 流替换掉当前代码中的本地文件路径。项目发展到这个阶段你已经不再只是“会用模型”而是具备了把模型落成产品的能力。最后补充一个选型判断本文选择 YOLOv8 而不是更新版本的模型核心原因在于生态成熟。YOLOv8 在文档、社区案例、TensorRT 部署支持方面都比较完整适合作为“人体检测识别计数系统”的稳健底座。如果你之后想追求更高精度可以关注 YOLO11 或引入多头注意力机制等改进思路但迁移之前先确保现有的数据、标注和评估流程已经跑通否则模型换得再快也很难回答“你的系统到底准不准”这个问题。如果有条件建议你把本文示例保存下来用自己的图片或视频试一次完整的“选择文件 → 检测 → 计数 → 调整阈值”流程。一次实际跑通胜过十次阅读代码。
返回列表