ARTICLE DETAIL

资讯详情

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

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

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 # 先装 PyTorchCPU 版够用有显卡换对应 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 是否存在如果先装 ultralyticspip 会自动拉一个可能不匹配的 torch 版本后面再改就麻烦了。opencv-python用于视频帧读取和图像预处理numpy 是隐式依赖但建议显式装上避免版本冲突。装完后跑一下yolo predict modelyolov8n.pt source40030016.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, conf0.35, iou0.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, countconf0.35是置信度阈值低于这个值的检测框会被丢弃。密集人群场景下这个值不能设太高否则远处的小目标全被过滤掉也不能太低否则背景误检会让人数虚高。0.3 到 0.4 是常见区间具体看你的画面质量。iou0.45控制 NMS 的合并力度人群密集时框与框重叠严重这个值可以适当调高到 0.5 左右减少误合并。classes[0]是关键参数COCO 数据集里人的类别 ID 是 0不加这个限制模型会把背包、手机都检出来。3.2 计数逻辑为什么直接 len(boxes) 会跳变最直觉的计数方式就是数检测框数量但实际跑起来会发现人数在相邻帧之间频繁跳动。原因有三个一是视频压缩导致的帧间差异让模型对同一目标的置信度波动二是遮挡导致目标时隐时现三是 NMS 阈值在临界点附近不稳定。常见做法是加一个简单的滑动窗口平滑from collections import deque class CountSmoother: def __init__(self, window_size5): self.window deque(maxlenwindow_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, source0): super().__init__() self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) smoother CountSmoother(window_size5) 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比较耗时如果帧率要求高可以在子线程里就转好再发信号。source0表示默认摄像头传视频文件路径就是视频模式传图片路径就是单张模式。资源包里的 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, countresults[0].plot()返回的图像已经自动处理了坐标映射不需要手动缩放。但如果你要自己画框或者导出检测坐标就要注意boxes.xyxy返回的是原图坐标系下的值可以直接用。4.2 视频文件模式帧率控制与跳帧策略视频文件模式比摄像头多了一个问题处理速度可能快于或慢于视频原始帧率。如果每帧都检测CPU 上可能只能跑到 5 FPS视频看起来像幻灯片如果跳帧又可能漏掉关键画面。我一般会加一个帧间隔参数def process_video(video_path, frame_skip2): cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_idx 0 smoother CountSmoother(window_size5) 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_skip2表示每两帧检测一次中间帧复用上一次的检测结果。这样在 CPU 上能把有效帧率翻倍代价是检测结果的时间分辨率降低。对于人群计数这种变化缓慢的场景跳帧带来的误差可以接受。4.3 摄像头实时流延迟累积与缓冲区清理摄像头模式最容易被忽略的问题是 OpenCV 的缓冲区。cv2.VideoCapture默认会缓存若干帧如果你的检测速度跟不上摄像头采集速度缓冲区会越积越多界面显示的永远是几秒前的画面。解决办法是设置缓冲区大小为 1cap 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, imgsz1280)代价是速度下降约一半。同时把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, persistTrue, classes[0], conf0.35) if results[0].boxes.id is not None: count len(set(results[0].boxes.id.int().tolist()))persistTrue让跟踪器在帧间保持状态。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, dtypenp.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 countpointPolygonTest返回正值表示点在多边形内部。区域顶点可以在界面上让用户手动标定也可以硬编码。这个功能在答辩时很加分因为它体现了对实际场景的理解。6.3 导出检测结果把每一帧的人数存成 CSV答辩或写报告时需要数据支撑把每帧的人数导出成 CSV 是最直接的方式import csv def export_counts(counts, output_pathcounts.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参数没传它把画面里的背包、椅子、手机全检出来了。从那以后我每次换模型或者改推理参数都强制走一遍单张图片的验证流程确认输出框的数量和位置合理再跑视频。这个习惯帮我省了很多次重新排查的时间。希望这份拆解能帮你把这份资源顺利跑起来少走一些我走过的弯路。本文还有配套的精品资源点击获取
返回列表