news 2026/9/28 15:28:32

YOLOv8+PyQt5密集人群计数系统实战:从环境配置到界面优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv8+PyQt5密集人群计数系统实战:从环境配置到界面优化

简介:这是一份面向高校学生与深度学习入门者的毕业设计参考资源,围绕YOLOv8与PyQt5构建密集人群计数检测系统,适合需要完成目标检测类课题、希望快速搭建可视化演示界面的开发者。系统支持单张图片、视频文件与摄像头实时流三种检测方式,可实时输出人群数量与检测框,兼顾算法性能与交互体验。压缩包共184个文件,约59.54MB,包含20个py源码文件、3个pt权重文件、2个ui界面文件,以及大量jpg测试图片、png图标、xml标注与pyc缓存等,覆盖从模型推理到界面展示的完整工程结构。目前已有146人学习,读者可参考其YOLOv8检测流程、PyQt5界面布局与多源输入处理逻辑,快速理解人群计数项目的实现思路,并在此基础上完成自己的毕业设计或课程实践。

1. 密集人群计数系统拆包:YOLOv8 加 PyQt5 到底能跑出什么效果

密集人群计数这个方向,每年毕业设计季都会被翻出来做一遍。原因很直接:场景好理解,地铁口、景区入口、商场中庭,一眼就能看出需求;技术栈也成熟,YOLOv8 做检测头,PyQt5 做界面壳,两三天能搭出能跑的原型。但真正上手就会发现,能跑和能用之间隔着一堆细节——检测框抖动导致计数跳变、密集遮挡下漏检成片、视频流和界面刷新不同步直接卡死。

这份资源包给的是一个完整可运行的参考实现,核心是 YOLOv8 目标检测加 PyQt5 图形界面,支持图片、视频文件、摄像头实时流三种输入方式,检测结果直接在界面上叠加显示并输出人数统计。它适合两类人:一是正在做毕业设计、需要一套能演示能答辩的完整代码;二是刚接触 YOLOv8 部署、想找一个带 GUI 的端到端案例来练手。包里带了若干测试图片(40030016.jpg、40030170.jpg 等),拿到手不用自己找数据就能先跑通流程。下面按实际拆包顺序,从环境配置到界面逻辑再到踩坑记录,把这份资源怎么用、哪里容易翻车讲清楚。

2. 环境配置与依赖安装:把 YOLOv8 和 PyQt5 装进同一个 Python 环境

2.1 版本选型:为什么 Python 3.8 到 3.10 是安全区

YOLOv8 依赖 ultralytics 包,PyQt5 依赖 Qt5 运行时,两者对 Python 版本的要求有重叠但不完全一致。实测下来,Python 3.8 到 3.10 是最稳的区间。3.11 以上部分 ultralytics 版本会报torch编译兼容问题,3.7 以下 PyQt5 的新版本又装不上。如果你用的是 Ubuntu 20.04,系统自带的 Python 3.8 刚好在安全区内,不用额外折腾。

显卡方面,这个项目在 CPU 上也能跑,只是帧率会掉到个位数。如果有 GTX 1660 Ti 或更高规格的显卡,装 CUDA 版的 PyTorch 后检测速度会有明显提升。没有独显也不影响功能验证,先跑通再考虑加速。

2.2 依赖安装:一条命令背后的版本约束

资源包里没有显式的 requirements.txt,但根据代码引用可以反推出核心依赖。我一般会先建虚拟环境,再按下面的顺序装:

# 创建虚拟环境,Python 版本建议 3.8-3.10 python -m venv crowd_env source crowd_env/bin/activate # Windows 用 crowd_env\Scripts\activate # 先装 PyTorch,CPU 版够用,有显卡换对应 CUDA 版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 再装 ultralytics 和 PyQt5 pip install ultralytics pyqt5 opencv-python numpy # 验证关键包版本 python -c "import ultralytics; print(ultralytics.__version__)" python -c "from PyQt5.QtCore import QT_VERSION_STR; print(QT_VERSION_STR)"

这里有个顺序问题:一定要先装 PyTorch 再装 ultralytics。ultralytics 安装时会检查 torch 是否存在,如果先装 ultralytics,pip 会自动拉一个可能不匹配的 torch 版本,后面再改就麻烦了。opencv-python用于视频帧读取和图像预处理,numpy 是隐式依赖但建议显式装上,避免版本冲突。

装完后跑一下yolo predict model=yolov8n.pt source=40030016.jpg,如果能输出检测结果图,说明 YOLOv8 环境没问题。PyQt5 的验证更简单,python -c "from PyQt5.QtWidgets import QApplication; print('ok')"不报错就行。

2.3 模型权重放置:yolov8n.pt 该放哪

YOLOv8 的预训练权重文件(yolov8n.pt、yolov8s.pt 等)不会自动出现在项目目录里。ultralytics 首次运行时会从官方源下载,但如果你在离线环境或者下载速度慢,就需要手动放置。常见做法是在项目根目录建一个weights文件夹,把下载好的 .pt 文件放进去,然后在代码里用相对路径引用。

资源包里没有附带权重文件,这是正常的——权重文件动辄几十兆,不适合打包进 zip。你需要自己下载 yolov8n.pt(约 6MB)或 yolov8s.pt(约 22MB)。n 版速度最快精度最低,s 版平衡一些,密集人群场景建议先用 n 版跑通流程,再根据漏检情况决定是否换更大的模型。

注意:权重文件的路径在代码里通常是硬编码的,拿到包后先全局搜一下.pt关键字,确认它期望的路径和你实际放置的位置一致,否则会报FileNotFoundError。

3. 检测核心逻辑:YOLOv8 推理、计数与界面数据绑定

3.1 推理入口:从一帧图像到检测框列表

YOLOv8 的推理接口非常简洁,ultralytics 把预处理、前向计算、NMS 后处理都封装在了一次调用里。核心代码大概长这样:

from ultralytics import YOLO import cv2 # 加载模型,首次运行会自动下载权重 model = YOLO('yolov8n.pt') def detect_frame(frame): """ 输入:BGR 格式的 numpy 图像数组 输出:带标注的图像、检测框列表、人数 """ results = model(frame, conf=0.35, iou=0.45, classes=[0]) # classes=[0] 只检测人 annotated = results[0].plot() # 直接在原图上画框 boxes = results[0].boxes count = len(boxes) if boxes is not None else 0 return annotated, boxes, count

conf=0.35是置信度阈值,低于这个值的检测框会被丢弃。密集人群场景下这个值不能设太高,否则远处的小目标全被过滤掉;也不能太低,否则背景误检会让人数虚高。0.3 到 0.4 是常见区间,具体看你的画面质量。iou=0.45控制 NMS 的合并力度,人群密集时框与框重叠严重,这个值可以适当调高到 0.5 左右,减少误合并。classes=[0]是关键参数,COCO 数据集里人的类别 ID 是 0,不加这个限制模型会把背包、手机都检出来。

3.2 计数逻辑:为什么直接 len(boxes) 会跳变

最直觉的计数方式就是数检测框数量,但实际跑起来会发现人数在相邻帧之间频繁跳动。原因有三个:一是视频压缩导致的帧间差异让模型对同一目标的置信度波动;二是遮挡导致目标时隐时现;三是 NMS 阈值在临界点附近不稳定。

常见做法是加一个简单的滑动窗口平滑:

from collections import deque class CountSmoother: def __init__(self, window_size=5): self.window = deque(maxlen=window_size) def update(self, count): self.window.append(count) # 取中位数比均值更抗离群值 sorted_w = sorted(self.window) return sorted_w[len(sorted_w) // 2]

窗口大小 5 意味着用最近 5 帧的中位数作为当前显示值。中位数比均值好的地方在于,偶尔一帧误检出几十个人,均值会被拉高,中位数则不受影响。这个类在视频和摄像头模式下都要用,图片模式不需要。

3.3 PyQt5 界面与检测线程的绑定

PyQt5 的界面刷新必须在主线程,而 YOLOv8 推理是计算密集型任务,放在主线程会直接卡死界面。标准做法是用 QThread 把检测循环跑在子线程里,通过信号槽把结果传回主线程更新 UI。

from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectThread(QThread): frame_ready = pyqtSignal(object, int) # 图像, 人数 def __init__(self, source=0): super().__init__() self.source = source self.running = True def run(self): cap = cv2.VideoCapture(self.source) smoother = CountSmoother(window_size=5) while self.running and cap.isOpened(): ret, frame = cap.read() if not ret: break annotated, _, raw_count = detect_frame(frame) smooth_count = smoother.update(raw_count) self.frame_ready.emit(annotated, smooth_count) cap.release() def stop(self): self.running = False self.wait()

frame_ready信号携带两个参数:标注后的图像和当前人数。主线程收到信号后,把图像转成 QImage 再转成 QPixmap 显示在 QLabel 上,人数更新到 QLCDNumber 或 QLabel。这里有个性能细节:图像格式转换(BGR → RGB → QImage)比较耗时,如果帧率要求高,可以在子线程里就转好再发信号。

source=0表示默认摄像头,传视频文件路径就是视频模式,传图片路径就是单张模式。资源包里的 40030016.jpg 等图片可以直接作为 source 传入测试。

4. 三种输入模式的切换与参数调优

4.1 图片模式:单帧检测的边界处理

图片模式最简单,读一帧、检测、显示、结束。但有两个边界情况要注意:一是图片尺寸过大时,YOLOv8 内部会缩放到 640×640 再推理,检测框坐标需要映射回原图尺寸;二是图片格式不是标准 BGR 时(比如 PNG 带透明通道),cv2.imread 读进来是四通道,直接传给模型会报错。

def detect_image(image_path): frame = cv2.imread(image_path) if frame is None: raise ValueError(f"无法读取图片: {image_path}") # 确保是三通道 if len(frame.shape) == 3 and frame.shape[2] == 4: frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR) annotated, boxes, count = detect_frame(frame) return annotated, count

results[0].plot()返回的图像已经自动处理了坐标映射,不需要手动缩放。但如果你要自己画框或者导出检测坐标,就要注意boxes.xyxy返回的是原图坐标系下的值,可以直接用。

4.2 视频文件模式:帧率控制与跳帧策略

视频文件模式比摄像头多了一个问题:处理速度可能快于或慢于视频原始帧率。如果每帧都检测,CPU 上可能只能跑到 5 FPS,视频看起来像幻灯片;如果跳帧,又可能漏掉关键画面。

我一般会加一个帧间隔参数:

def process_video(video_path, frame_skip=2): cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) frame_idx = 0 smoother = CountSmoother(window_size=5) while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_idx % frame_skip == 0: annotated, _, raw_count = detect_frame(frame) smooth_count = smoother.update(raw_count) # 显示 annotated 和 smooth_count frame_idx += 1 cap.release()

frame_skip=2表示每两帧检测一次,中间帧复用上一次的检测结果。这样在 CPU 上能把有效帧率翻倍,代价是检测结果的时间分辨率降低。对于人群计数这种变化缓慢的场景,跳帧带来的误差可以接受。

4.3 摄像头实时流:延迟累积与缓冲区清理

摄像头模式最容易被忽略的问题是 OpenCV 的缓冲区。cv2.VideoCapture默认会缓存若干帧,如果你的检测速度跟不上摄像头采集速度,缓冲区会越积越多,界面显示的永远是几秒前的画面。

解决办法是设置缓冲区大小为 1:

cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

这样每次cap.read()拿到的都是最新帧。另外,摄像头模式下建议把frame_skip设为 1,因为实时性比流畅度更重要。如果检测一帧需要 200ms,那就接受 5 FPS 的刷新率,至少画面是实时的。

提示:不同摄像头驱动对CAP_PROP_BUFFERSIZE的支持程度不一样,有些摄像头设了也不生效。如果发现延迟仍然很大,可以在每次 read 之前连续调用两次cap.grab()丢弃旧帧。

5. 避坑与排查:密集人群场景下最容易翻车的五个点

5.1 人数跳变严重,相邻帧差出十几个

现象:视频模式下人数显示在 20 到 35 之间反复横跳,肉眼观察画面里人数并没有明显变化。

原因:YOLOv8 对遮挡目标的置信度本身就在阈值附近波动,加上 NMS 的随机性,导致某些帧多检出几个、某些帧少检出几个。没有做任何平滑处理时,原始计数直接反映到了界面上。

解决:加滑动窗口中位数平滑,窗口大小 5 到 10 之间。如果跳变仍然明显,把conf从 0.35 降到 0.3,让检测更稳定,同时用iou调高到 0.5 减少框合并的随机性。

5.2 密集区域漏检成片,远处的人完全检不出来

现象:画面前排的人能检出,后排密集区域只检出零星几个,实际人数远大于显示人数。

原因:YOLOv8 默认输入尺寸是 640×640,密集小目标在缩放后像素太少,特征提取不充分。另外conf阈值设太高也会过滤掉这些小目标的低置信度检测。

解决:把推理尺寸调大,model(frame, imgsz=1280),代价是速度下降约一半。同时把conf降到 0.25 左右。如果还不行,考虑换 yolov8s 或 yolov8m 模型,小模型的特征提取能力有限。

5.3 PyQt5 界面卡死,点击按钮无响应

现象:启动检测后界面直接白屏,按钮点不动,窗口拖拽也卡顿。

原因:检测循环写在了主线程里,YOLOv8 推理阻塞了 Qt 的事件循环。

解决:把检测逻辑移到 QThread 子线程,通过信号槽更新 UI。注意子线程里不能直接操作 UI 组件,所有界面更新必须通过信号触发主线程执行。

5.4 摄像头打开失败或读到黑屏

现象:cv2.VideoCapture(0)返回 True 但read()一直返回 False,或者画面全黑。

原因:摄像头被其他程序占用,或者 Linux 下权限不足,或者摄像头索引不是 0。

解决:先确认没有其他程序在用摄像头。Linux 下检查/dev/video*权限,必要时把用户加入 video 组。索引从 0 试到 3,有些笔记本的内置摄像头是 1 而不是 0。

5.5 模型权重下载慢或下载失败

现象:首次运行时报Downloading yolov8n.pt然后卡住或超时。

原因:ultralytics 默认从 GitHub 下载权重,网络不稳定时容易失败。

解决:手动下载 yolov8n.pt 放到项目目录,然后在代码里把YOLO('yolov8n.pt')改成YOLO('./yolov8n.pt')指定本地路径。或者设置环境变量YOLO_WEIGHTS_DIR指向本地权重目录。

6. 从能跑到好用:三个提升检测质量的实操技巧

6.1 用跟踪算法稳住 ID,减少重复计数

单纯靠检测框计数,同一个人在不同帧里被反复计数是常态。加一个轻量级跟踪器(比如 ByteTrack)可以给每个检测框分配稳定 ID,计数时只统计当前帧中 ID 的数量,而不是框的数量。ultralytics 内置了跟踪接口:

from ultralytics import YOLO model = YOLO('yolov8n.pt') results = model.track(frame, persist=True, classes=[0], conf=0.35) if results[0].boxes.id is not None: count = len(set(results[0].boxes.id.int().tolist()))

persist=True让跟踪器在帧间保持状态。boxes.id是每个检测框的跟踪 ID,用 set 去重后就是当前画面中的独立人数。这个方式比纯计数稳定得多,代价是增加了跟踪的计算开销,CPU 上帧率会再降一些。

6.2 区域计数:只统计画面中特定区域的人数

实际场景中往往只关心某个区域,比如地铁口的闸机通道。可以在画面上画一个多边形区域,只统计中心点落在区域内的检测框:

import cv2 import numpy as np def count_in_region(boxes, region_points): """ boxes: YOLOv8 的 boxes 对象 region_points: 多边形顶点列表 [(x1,y1), (x2,y2), ...] """ region = np.array(region_points, dtype=np.int32) count = 0 for box in boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 if cv2.pointPolygonTest(region, (cx, cy), False) >= 0: count += 1 return count

pointPolygonTest返回正值表示点在多边形内部。区域顶点可以在界面上让用户手动标定,也可以硬编码。这个功能在答辩时很加分,因为它体现了对实际场景的理解。

6.3 导出检测结果:把每一帧的人数存成 CSV

答辩或写报告时需要数据支撑,把每帧的人数导出成 CSV 是最直接的方式:

import csv def export_counts(counts, output_path='counts.csv'): with open(output_path, 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['frame', 'count']) for i, c in enumerate(counts): writer.writerow([i, c])

counts是每帧平滑后的人数列表。导出的 CSV 可以直接丢进 Excel 画曲线图,也可以用来计算平均人数、峰值人数等统计指标。如果用了跟踪算法,还可以额外导出每个 ID 的出现帧范围,分析每个人的停留时间。

6.4 一个我踩过的坑:模型切换后忘记改类别过滤

有次我把 yolov8n 换成 yolov8s 做对比测试,发现人数突然多了好几倍。排查了半天才想起来,新模型的classes参数没传,它把画面里的背包、椅子、手机全检出来了。从那以后我每次换模型或者改推理参数,都强制走一遍单张图片的验证流程,确认输出框的数量和位置合理,再跑视频。这个习惯帮我省了很多次重新排查的时间。

希望这份拆解能帮你把这份资源顺利跑起来,少走一些我走过的弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:27:16

Sigrity Aurora阻抗分析Design Setup高频报错与优化技巧详解

1. 为什么阻抗分析总卡在第一步:Design Setup Workflow的痛与解做信号完整性仿真的人,十有八九都在Sigrity Aurora里和阻抗分析打过交道。这个功能本身不算复杂,但真正让人头疼的往往是进入仿真之前的Design Setup阶段——模型导不进去、层叠…

作者头像 李华
网站建设 2026/9/28 15:25:57

从PS4神作拆解游戏性能优化:榨干硬件的取舍艺术

聊到“榨干PS4性能”,我脑子里第一反应不是某个数字跑分,而是那三年的震撼感:一台2013年发售、GPU算力只有1.84TFLOPs、CPU还是AMD Jaguar八核低压货色的机器,硬生生跑出了《神秘海域4》《荒野大镖客2》《最后生还者2》这种放在今…

作者头像 李华
网站建设 2026/9/28 15:25:31

AIGC摄影全流程:AI置景、合成精修与多工具协同实战

AIGC这三个字母从行业术语变成工作日常,我没少花冤枉钱。以前拍一张带科技感的电商主图,要么租棚、要么置景,预算和时间全烧在“把概念变成实物”这件事上。现在我的工作流是反过来的:先用AI把脑子里那团模糊的想法变成高完成度的…

作者头像 李华
网站建设 2026/9/28 15:25:17

基于DenseUnet的CT切片左右肺分割实战:从训练到推理的完整指南

简介:本资源面向医学图像分割方向的初学者与算法工程师,提供一套基于DenseUnet的CT肺部左右肺分割完整实战方案,覆盖从数据准备到模型评估的全流程。压缩包共约2000个文件,以1984张png格式的CT切片与掩膜图像为主体,另…

作者头像 李华
网站建设 2026/9/28 15:24:56

GitHub Trending日榜筛选:DLSS版本管理与Claude Code Skills落地

周四早上,我把GitHub Trending的日榜过了一遍,实话实说,2026-09-24这期日榜的信息量比平时大不少。挂在前面几位的仓库不再是清一色的新AI框架,反而是一大批"让工具真正能被用起来"的项目:游戏玩家在翻DLSS版…

作者头像 李华
网站建设 2026/9/28 15:23:24

樱花内网穿透:专为《我的世界》联机优化的零配置隧道方案

1. 为什么“樱花”成了《我的世界》联机玩家的首选穿透方案?在《我的世界》Java版联机实践中,最常被卡住的不是红石电路,也不是末地折跃门坐标,而是——你的服务器明明开着,朋友却连不上。输入IP和端口后,客…

作者头像 李华