简介这份资源是面向高校学生与深度学习入门者的毕业设计参考项目围绕YOLOv8与PyQt5构建密集人群计数检测系统可解决公共场所人流统计、安防监控等场景下的实时计数需求。项目将YOLOv8的高效目标检测能力与PyQt5的图形界面结合支持单张图片、视频文件与摄像头实时流三种检测方式并能在界面中直观展示检测与计数结果适合作为计算机视觉方向的课程设计或毕设蓝本。压缩包共184个文件约59.54MB包含20个py源码、26个pyc编译文件、3个pt权重、2个ui界面文件以及103张jpg样本图、15张png素材和7个xml标注文件另附qrc、css、ttf等界面资源结构完整便于二次开发。目前已有146人学习关注。读者可从中获取完整的检测流程代码、界面布局方案与预训练权重快速理解YOLOv8的Anchor-Free检测头与损失函数设计并在此基础上完成功能扩展与论文撰写。1. 密集人群计数检测系统从 YOLOv8 到 PyQt5 的工程化落地密集人群计数检测系统本质上解决的是“一张图里到底有多少人、人都在哪”的问题。和普通目标检测不同密集场景下人头小、遮挡重、尺度变化剧烈直接拿通用检测器硬跑漏检和重复框会多到没法看。这个标题里的组合——YOLOv8 做检测骨干、PyQt5 做桌面交互——是毕业设计和中小型工程交付里非常常见的一套方案训练侧用 YOLOv8 快速拿到可用的密集人头检测权重部署侧用 PyQt5 把推理、可视化、统计封装成一个能双击运行的桌面程序。它适合两类人一类是要在有限时间内做出可演示、可答辩、可交付系统的学生和初级工程师另一类是想把检测模型从脚本推进到带界面的实际工具、但不想碰 Web 前后端的开发者。下面按“数据怎么准备、模型怎么训、界面怎么接、坑在哪”的顺序把这条链路讲透。2. 数据准备与标注密集人头数据集怎么处理才不返工2.1 密集场景的数据集选型与格式转换密集人群计数常用的公开数据集有 ShanghaiTech、UCF-QNRF、NWPU-Crowd 这几类标注形式分两种一种是点标注每个人头一个坐标点一种是框标注每个人头一个矩形框。YOLOv8 训练需要的是框标注所以如果你拿到的是点标注数据集第一步就是做点转框。常见做法是给每个点生成一个固定尺寸的框比如 16×16 或 20×20 像素具体尺寸取决于图像分辨率和人头平均像素大小。我一般会先统计一下数据集中人头的平均像素宽度再决定框的边长。如果图像是 1920×1080人头平均宽度大概在 20 到 40 像素之间框边长取 24 到 32 比较稳。框太小模型学不到有效特征框太大密集区域框会大量重叠NMS 阶段容易互相抑制。转换脚本用 Python 写核心逻辑就是读点标注文件生成 YOLO 格式的 txt。YOLO 格式每行是class_id x_center y_center width height全部归一化到 0 到 1。import os import cv2 import numpy as np # 点标注转 YOLO 框标注 # 输入images 目录 points 目录每张图对应一个 txt每行 x y # 输出labels 目录每张图对应一个 txt每行 class xc yc w h归一化 IMG_DIR dataset/images PT_DIR dataset/points LBL_DIR dataset/labels BOX_SIZE 28 # 框边长根据人头平均像素调整 os.makedirs(LBL_DIR, exist_okTrue) for pt_name in os.listdir(PT_DIR): if not pt_name.endswith(.txt): continue img_name pt_name.replace(.txt, .jpg) img_path os.path.join(IMG_DIR, img_name) if not os.path.exists(img_path): continue img cv2.imread(img_path) h, w img.shape[:2] lines [] with open(os.path.join(PT_DIR, pt_name), r) as f: for line in f: parts line.strip().split() if len(parts) 2: continue x, y float(parts[0]), float(parts[1]) # 归一化中心点 xc, yc x / w, y / h bw, bh BOX_SIZE / w, BOX_SIZE / h # 裁剪到 [0,1]防止越界 xc min(max(xc, 0), 1) yc min(max(yc, 0), 1) lines.append(f0 {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(LBL_DIR, pt_name), w) as f: f.write(\n.join(lines))这段代码的关键参数是BOX_SIZE它直接决定框的物理尺寸。转换完成后一定要抽查几张图的标注可视化确认框能盖住人头而不是偏到肩膀上。另一个容易忽略的点是图像和标注文件名必须严格一一对应YOLOv8 按文件名匹配差一个字符就会报“找不到标签”。2.2 数据集划分与 YOLOv8 目录结构YOLOv8 要求的数据集目录结构是固定的images/train、images/val、labels/train、labels/val再加一个data.yaml描述路径和类别。划分比例一般 8:1:1 或 7:2:1密集人群场景我倾向 8:1:1因为验证集太大反而浪费训练数据。# 目录结构示例 dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml内容path: ./dataset train: images/train val: images/val nc: 1 names: [person]这里nc: 1表示只检测一类“人”。密集人群计数通常只关心人头不需要区分性别、姿态所以单类是最稳的选择。如果你用的是现成的 COCO 预训练权重注意 COCO 里 person 是第 0 类和这里的类别索引一致迁移学习时分类头可以少改甚至不改。提示划分数据集时不要随机打散后直接复制建议先固定随机种子生成文件列表再按列表移动否则每次重新划分都会导致验证集变化训练曲线没法对比。3. YOLOv8 训练密集人头检测模型参数怎么设、曲线怎么看3.1 环境搭建与预训练权重选择训练环境推荐 Ubuntu 20.04 CUDA 11.8 或 Windows CUDA 11.8PyTorch 版本选 2.0 以上。如果手头只有 CPU也能跑通但训练时间会成倍增加建议至少用 GTX 1660 Ti 级别的显卡6GB 显存跑 YOLOv8n 或 YOLOv8s 在 640 分辨率下 batch size 设 8 到 16 没问题。安装命令pip install ultralytics # 如果需要指定 PyTorch 版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118预训练权重用yolov8n.pt或yolov8s.pt。密集人群场景我一般从yolov8s起步n 版本虽然快但小目标召回率在密集区域会明显吃亏。权重文件从 Ultralytics 官方发布渠道获取不要用来路不明的权重否则训练时 loss 异常很难排查。3.2 训练命令与关键参数含义yolo detect train \ modelyolov8s.pt \ datadataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience20 \ device0 \ projectruns/crowd \ nameexp1逐个说参数imgsz640是输入分辨率密集小目标可以提到 960 或 1280但显存占用会平方级增长batch16根据显存调爆显存就降到 8lr00.01是初始学习率YOLOv8 默认 SGD 下这个值比较稳如果 loss 震荡厉害可以降到 0.005patience20表示 20 轮验证指标不提升就早停密集场景建议设 20 到 30太小容易在前期误停lrf0.01是最终学习率系数控制余弦退火的终点。训练过程中重点看三个东西box_loss是否稳定下降、mAP50是否持续上升、验证集的可视化结果有没有大量漏检。如果box_loss下降但mAP50不涨大概率是过拟合或者标注框质量有问题。YOLOv8 训练结束后会自动生成results.csv和confusion_matrix.png用这两份文件就能判断模型有没有学到东西。3.3 损失曲线与指标解读训练完成后在runs/crowd/exp1目录下会生成results.png里面包含训练和验证的 box_loss、cls_loss、mAP50、mAP50-95。密集人群场景下mAP50能到 0.7 以上就算可用0.8 以上算不错。如果mAP50卡在 0.5 左右上不去优先检查标注框是否偏大或偏小其次检查是否有大量小目标被缩放到 640 后变得不可见。import pandas as pd import matplotlib.pyplot as plt # 读取 YOLOv8 训练日志并绘制损失曲线 df pd.read_csv(runs/crowd/exp1/results.csv) df.columns df.columns.str.strip() plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(df[train/box_loss], labeltrain_box) plt.plot(df[val/box_loss], labelval_box) plt.legend() plt.title(Box Loss) plt.subplot(1, 2, 2) plt.plot(df[metrics/mAP50(B)], labelmAP50) plt.plot(df[metrics/mAP50-95(B)], labelmAP50-95) plt.legend() plt.title(mAP) plt.tight_layout() plt.savefig(loss_curve.png)这段脚本的作用是把results.csv里的关键列画出来方便写报告或答辩时展示。注意列名里有空格和括号读取后要strip()处理否则会报 KeyError。注意如果验证集 mAP 远低于训练集 mAP先别急着加数据增强优先确认验证集和训练集的标注风格是否一致。我见过有人训练集用点转框、验证集用原始框标注结果模型在验证集上表现一塌糊涂。4. PyQt5 界面集成把检测模型封装成桌面工具4.1 PyQt5 环境安装与界面布局设计PyQt5 安装本身不复杂但版本冲突是高频翻车点。推荐用 Python 3.8 到 3.10PyQt5 选 5.15.x 系列。pip install PyQt55.15.9 pip install opencv-python ultralytics界面布局我一般分三块左侧是图像显示区右侧是控制面板选择图片、开始检测、显示人数底部是状态栏。用QHBoxLayout和QVBoxLayout嵌套就能搭出来不需要 Qt Designer 也能写。from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QFileDialog) from PyQt5.QtGui import QPixmap, QImage import cv2 import sys class CrowdApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(密集人群计数检测系统) self.resize(1000, 600) # 左侧图像显示 self.image_label QLabel(请选择图片) self.image_label.setFixedSize(720, 540) # 右侧控制面板 self.btn_open QPushButton(选择图片) self.btn_detect QPushButton(开始检测) self.count_label QLabel(人数--) right_layout QVBoxLayout() right_layout.addWidget(self.btn_open) right_layout.addWidget(self.btn_detect) right_layout.addWidget(self.count_label) right_layout.addStretch() main_layout QHBoxLayout() main_layout.addWidget(self.image_label) main_layout.addLayout(right_layout) container QWidget() container.setLayout(main_layout) self.setCentralWidget(container) self.btn_open.clicked.connect(self.open_image) self.btn_detect.clicked.connect(self.detect) self.image_path None def open_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.jpg *.png)) if path: self.image_path path pixmap QPixmap(path).scaled(720, 540) self.image_label.setPixmap(pixmap) def detect(self): if not self.image_path: return from ultralytics import YOLO model YOLO(runs/crowd/exp1/weights/best.pt) results model(self.image_path, imgsz640, conf0.3) annotated results[0].plot() count len(results[0].boxes) self.count_label.setText(f人数{count}) # OpenCV BGR 转 Qt RGB annotated cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch annotated.shape qimg QImage(annotated.data, w, h, ch * w, QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(qimg).scaled(720, 540)) if __name__ __main__: app QApplication(sys.argv) window CrowdApp() window.show() sys.exit(app.exec_())这段代码把模型推理和界面刷新串起来了。关键点是results[0].plot()返回的是带框的 BGR 图像Qt 显示需要转成 RGB否则颜色会偏蓝。conf0.3是置信度阈值密集场景可以降到 0.25 提高召回但误检也会增加需要根据实际效果权衡。4.2 推理线程与界面卡顿处理上面那段代码有个明显问题检测直接在 UI 线程里跑图片大或模型慢的时候界面会卡死。正确做法是把推理放到QThread里通过信号槽把结果传回主线程刷新界面。from PyQt5.QtCore import QThread, pyqtSignal import cv2 from ultralytics import YOLO class DetectThread(QThread): finished pyqtSignal(object, int) # 图像, 人数 def __init__(self, image_path, model_path): super().__init__() self.image_path image_path self.model_path model_path def run(self): model YOLO(self.model_path) results model(self.image_path, imgsz640, conf0.3) annotated results[0].plot() count len(results[0].boxes) self.finished.emit(annotated, count)主线程里连接信号self.thread DetectThread(self.image_path, runs/crowd/exp1/weights/best.pt) self.thread.finished.connect(self.on_detect_finished) self.thread.start()on_detect_finished里做图像转换和界面更新。这样界面在推理期间仍然可以响应拖动和点击体验会好很多。模型加载建议放在线程外只做一次避免每次检测都重新加载权重那会白白浪费几秒钟。提示PyQt5 里所有界面更新必须在主线程做子线程只能发信号。直接在子线程里操作QLabel或setPixmap会导致程序随机崩溃这个坑我踩过不止一次。5. 避坑与排查密集人群检测系统最常见的 5 个翻车点5.1 现象训练 loss 正常但检测结果全是重叠框原因密集区域人头间距小于框边长NMS 阶段大量框互相抑制后又因为置信度接近而反复保留。解决把BOX_SIZE调小到接近人头实际宽度同时在推理时把iou阈值从默认 0.7 降到 0.5 到 0.6减少重叠框被误保留的概率。5.2 现象PyQt5 界面显示图片颜色发蓝原因OpenCV 读图是 BGR 顺序Qt 的QImage默认按 RGB 解析。解决在转QImage之前执行cv2.cvtColor(img, cv2.COLOR_BGR2RGB)这一步不能省。5.3 现象labelme安装后无法启动或与 PyQt5 冲突原因labelme 依赖的 PyQt5 版本和你项目里的版本不一致pip 会互相覆盖。解决给标注工具单独建一个虚拟环境标注和训练环境隔离。训练环境里只装ultralytics和推理需要的包不要混装标注工具。5.4 现象验证集 mAP 很高但实际图片漏检严重原因验证集和实际场景分布不一致比如验证集都是白天、稀疏场景实际图片是夜间、密集场景。解决重新划分验证集确保验证集覆盖实际部署场景的光照、密度和角度。如果实际场景数据太少至少做一轮针对性微调。5.5 现象打包成 exe 后运行报找不到模型文件原因PyInstaller 打包时没有把best.pt和data.yaml一起打进去或者代码里用了相对路径。解决用--add-data把权重文件加进去代码里用sys._MEIPASS拼接资源路径不要写死绝对路径。6. 进阶技巧用置信度分层和密度图后处理提升计数精度检测框数量直接当人数在密集场景下会有两个偏差一是遮挡导致漏检二是低置信度框被一刀切掉。我一般会做两层后处理。第一层是置信度分层统计把conf在 0.1 到 0.3 之间的框单独拿出来人工抽检一批如果大部分确实是真人头就把推理阈值降到 0.15 到 0.2用召回换精度。第二层是密度图校验对检测框中心点做高斯核密度估计生成一张密度热力图和原图叠加后看高密度区域是否和实际人群聚集区吻合。如果密度图峰值位置和检测框分布明显错位说明框的位置有系统偏差需要回头检查标注。import numpy as np import cv2 def density_map(boxes, img_shape, sigma8): # boxes: N x 4, xyxy h, w img_shape[:2] density np.zeros((h, w), dtypenp.float32) for box in boxes: cx int((box[0] box[2]) / 2) cy int((box[1] box[3]) / 2) if 0 cx w and 0 cy h: density[cy, cx] 1 density cv2.GaussianBlur(density, (0, 0), sigma) return densitysigma控制热力图的平滑程度密集场景取 6 到 10 比较合适。密度图积分结果就是估计人数可以和检测框数量做交叉验证。如果两者差距超过 15%说明检测结果需要复核。另一个实用技巧是导出 ONNX 或 TensorRT 做推理加速。YOLOv8 自带导出命令yolo export modelruns/crowd/exp1/weights/best.pt formatonnx imgsz640导出后在 PyQt5 里用onnxruntime推理CPU 上也能比 PyTorch 快 2 到 3 倍。如果部署到 RK3588 这类边缘设备走 RKNN 转换流程核心是把 ONNX 转成 RKNN 模型再在板端用rknn-toolkit-lite加载推理输入输出尺寸和归一化参数要和训练时严格对齐。我自己做这类系统最大的教训是别一上来就调模型结构先把数据标注和验证集分布搞对80% 的精度问题都出在数据上。模型选型和界面封装反而是后面水到渠成的事。希望帮到你。本文还有配套的精品资源点击获取