
简介基于YOLOv8的智能会议室参会人数统计项目是一份面向计算机相关专业学生与初学者的完整实战资源适合毕业设计、课程设计或大作业场景解决会议场景下多人目标检测与人数统计的需求。压缩包共8个文件以Python脚本、PyTorch模型权重和说明文档为主整体约15.91MB覆盖模型训练、视频检测、可视化界面等核心模块并内置训练好的模型权重下载后按README指引即可快速运行。目前已有34人学习下载可作为同类目标检测任务的参考实现。项目内代码均经过测试并成功运行可产出核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线以及验证集预测结果和标签分布图方便在答辩或课程展示中直观验证效果完整数据集与部署说明进一步降低了上手门槛既可直接使用也可在此基础之上二次开发。1. 为什么会议室人数统计听着简单做起来全是坑会议室参会人数统计听起来就是「打开摄像头数一下有几个人头」但真做起来会发现事情没这么顺摄像头通常是俯拍角度后排人头小到模型认不出来投影仪一亮背景亮斑把人影边缘整个吞掉坐着的人只露出半个头肩检测框在连续帧里抖来抖去人数从 6 跳到 11 再跳回 6。「基于YOLOv8的智能会议室参会人数统计」这个项目解决的问题不是「能不能检测到人」而是「在会议室这种半静态、强遮挡、固定视角的场景下怎么可靠地告诉你现在屋里到底有几个人」。它把目标检测、视频流处理、人数去重、可视化界面和部署串成一条完整链路适合课程设计和毕业设计也适合想快速验证「检测 计数 界面」这套打法的从业者。下面按做这个方案的真实顺序从选型拆到踩坑。2. 方案选型YOLOv8 为什么是会议室人数统计的可靠选择2.1 先想清楚人数统计到底在统计什么做方案之前先分清楚需求。静态图场景只需要在某一时刻数一次人头比如门禁抓拍会议室通常不是这个用法摄像头常开观众席上的人一坐就是半小时系统要持续给出「当前有多少人」的数字这就需要视频流处理。视频流计数有两条技术路线。第一条是朴素的「每帧检测、每帧计数」把当前帧里 YOLOv8 输出的 person 框数量直接当成人数。这条路线实现成本低但检测框在帧间会有轻微抖动漏检一两帧人数马上跳变演示时看起来很业余。第二条是「检测 跟踪」先检测出人再用跟踪器给每个人分配一个稳定的 ID按 ID 去重计数。会议室里的人长时间保持坐姿ID 一旦建立就很稳定即便中间有几帧漏检跟踪器也能靠运动预测补回来。我做这类项目时一般建议走第二条路线但不用上 DeepSort 这种重方案。会议室场景下人的运动幅度小用轻量跟踪就能解决问题后面第 5 章会展开讲。下表的结论是我多年下来比较认可的选型依据方案实现成本人数输出稳定性适用场景纯检测 帧内计数低差检测抖动直接传导静态抓拍、快速验证检测 轻量跟踪IOU/ByteTrack中好能扛住帧间漏检会议室、教室、门店密度估计CSRNet 类高依赖训练数据质量密集人群不需要坐标框2.2 为什么不是 OpenCV 背景差分、不是人头密度图有人会问会议室背景固定能不能用 OpenCV 背景差分检测人背景差分在会议室场景属于典型的「实验室里成立、落地就翻车」人长时间坐着不动会逐渐被模型吸收成背景窗帘被风吹动、投影仪内容切换、有人开关灯都会触发大范围误报。会议室恰恰是「背景在变、人不动」的反直觉组合背景差分几乎必跪。密度估计方案如 CSRNet、DM-Count的问题在于标注成本和输出形态。这类模型训练需要逐点标注人群密度图数据准备工作量远超画框而且输出是一张热力值图只能告诉你「这片区域大概有多少人」给不出每个人在哪、更做不了 ID 跟踪会议室门禁联动这种需求它接不住。YOLOv8 这类 anchor-free 检测器在会议室这种中等密度场景下是更实际的选项COCO 预训练权重里 person 类已经很强不需要从零训练输出天然带框和置信度往下接跟踪、接界面都很顺。标题里提到「完整数据集」实际意义就是给你一个在通用 person 模型基础上做会议室微调的起点不是让你从随机权重开始炼。2.3 模型选型n / s / m 三档怎么选YOLOv8 有 n / s / m / l / x 五档会议室人数统计用到 l 和 x 属于浪费算力。n 最快但精度相对低s 在速度和精度之间最均衡m 适合对精度有执念且机器不太差的场景。选择依据主要是部署硬件纯 CPU 机器用 n入门独显用 s嵌入式板子用 n 再导出量化模型。做毕设演示这件事我的建议是别盲目追高。如果现场机器是普通笔记本的核显跑 s 会卡到影响演示节奏老老实实用 n 加 imgsz640四平八稳。如果机房给的是 GTX 1660Ti 这类 6G 显存的入门卡那上 s 没有任何压力训练时间和显存占用都可控。至于 m适合你打算把模型做成产品原型、后面还要换更大数据集的场景。从网络结构角度看YOLOv8 的 backbone 用了 C2f 模块和 SPPF 金字塔池化neck 是 PAN-FPN 双向融合这些设计让它对中小尺寸目标比较友好。会议室俯拍画面里的人头占比偏小尤其后排可能只有三四十个像素结构上选对模型档次比调参更重要。2.4 整体流程拆解从视频帧到界面数字整个项目的落地路径可以拆成一条链视频流或视频文件 → 丢帧 → letterbox 预处理 → YOLOv8 推理 → NMS 去重 → 跟踪 ID 关联 → 人数统计与平滑 → 可视化界面显示与记录。训练环节在这条链之前数据准备和模型调优的产出是 best.pt 权重文件推理环节加载它。部署教程里做的事情本质上就是把这条链固定成两个入口一条命令行入口负责训练和验证一条图形界面入口负责加载模型、读视频、显示结果。界面是给答辩和演示看的训练和推理是给功能兜底的两条腿缺一不可。接下来按这条链的顺序先讲数据准备再讲训练调参然后讲最容易翻车的几个细节最后说界面和部署。3. 数据集准备与标注把俯拍会议室画面变成 YOLO 训练集3.1 采集规则一个会议室该拍哪些画面数据集质量直接决定后面所有步骤的成败。会议室场景的采集要覆盖真实使用中会出现的组合维度至少包含不同人数档位1 人、3 人、5 人、8 人、12 人以上、不同座位区域分布、开灯和关灯两种状态、投影仪开启时的强光干扰、窗户侧光、有人从门口进出、个别单人背对镜头坐着。数量上按做毕设和课设的惯例300 到 800 张的标注量已经能微调出可演示的模型。单张图内人数不用刻意追求很多2 到 15 人的区间比较合理覆盖程度比数量更重要。采集时尽量保留原始分辨率不要为了省硬盘压得太狠训练时会统一缩放到 imgsz原始大图是留给自己的一条后悔药。3.2 标注规范与工具用哪款工具、按什么标准框标注工具用 LabelImg 或 X-AnyLabeling 都行。LabelImg 老牌稳定导出 VOC XML 格式方便X-AnyLabeling 界面更好用支持自动标注辅助。对只画矩形框的任务两者没有本质差别挑个安装顺手的就行。环境上用 Python 3.8 以上的虚拟环境安装避免系统 Python 环境被搞乱。标注规范是新手最容易忽略的坑。会议室的特殊性在于坐姿正面前方的人可以看到上半身俯拍镜头下的人可能只露头肩后排被前排挡住的人可能只露半个头。我的做法是统一用 person 这一类坐姿只露头肩的人框头肩站姿完整的人框全身遮挡超过一半的目标不标或只标可见部分。这里的关键不是「框得准」而是「框得一致」——如果一半标注是头肩、一半是全身模型会不知道该学什么。会议室俯拍画面还有一个增强上的特殊性训练时不要做上下翻转flipud0.0人不会倒着开会水平翻转fliplr可以保留实测对视角泛化有帮助。3.3 把 VOC 转成 YOLO 格式转换脚本与四个边界坑LabelImg 导出的是 VOC 格式 XMLYOLOv8 需要的是每张图对应一个 txt每一行是「类ID cx cy w h」的归一化坐标。这个转换脚本几乎是每个做检测项目的必修课下面给一个可以直接照着改的版本import xml.etree.ElementTree as ET import os from glob import glob # 类别映射本项目只有 person 一个类别 CLASSES {person: 0} def convert_voc_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() # 尺寸从 XML 里读不要自己去读图片避免尺寸不一致 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 边界处理越界坐标裁剪到图像范围内 x1, y1 max(x1, 0), max(y1, 0) x2, y2 min(x2, img_w), min(y2, img_h) # 过滤掉宽或高为 0 的目标 if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{CLASSES[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) xml_files glob(xmls/*.xml) os.makedirs(labels, exist_okTrue) for xml_path in xml_files: name os.path.basename(xml_path).replace(.xml, .txt) convert_voc_to_yolo(xml_path, os.path.join(labels, name))脚本逻辑不复杂但有几个边界处必须说明。第一图像尺寸一定从 XML 的 size 节点读不要为了省事直接cv2.imread取尺寸LabelImg 偶尔会记录到原图尺寸之外的框两者对不上会产出大于 1 或小于 0 的异常坐标。第二越界框要裁剪而不是丢弃很多标注工具的框会自动外扩几个像素直接min/max裁回图像范围内即可。第三类 ID 从 0 开始计数YOLOv8 的 names 列表按索引对应这点和 VOC 里字符串类名不同。第四txt 文件名必须和图片文件名完全一致连后缀都对齐规则YOLOv8 训练时按前缀匹配图像和标签文件。3.4 数据集划分与 data.yaml 配置训练集、验证集、测试集的划分要写脚本固定随机种子不要手动挑。会议室视频里连续帧高度相似如果只按顺序切分很容易出现训练集和验证集里出现同一镜头的情况指标虚高。做法是先对文件列表做 shuffle再按比例切分import os import random from glob import glob random.seed(42) # 固定种子保证每次划分结果一致 xmls glob(xmls/*.xml) random.shuffle(xmls) n len(xmls) train xmls[: int(n * 0.8)] val xmls[int(n * 0.8) : int(n * 0.9)] test xmls[int(n * 0.9) :]划分完的目录结构要固定成 YOLOv8 约定的样子images/train、images/val、labels/train、labels/val四个目录图片和 txt 同名。然后写 data.yamlpath: ./dataset train: images/train val: images/val names: 0: person注意names从 0 开始数量必须比标注中的最大类 ID 大 1否则训练报错。数据准备完成后我不急着训练而是先随机挑几张图把 YOLO 格式的框画回原图看一眼确认没有坐标错位、目标遗漏。这一步 10 分钟能省掉后面定位数据问题的半天。4. 训练与参数调优从环境配置到看懂损失曲线4.1 环境配置CPU 和 GPU 两条路都要能走通环境配置是「ubuntu20.04 搭建 YOLOv8 环境 CPU 版本」这类热搜最常见的提问来源。其实步骤很短创建虚拟环境、装 PyTorch、装 ultralytics。没有 N 卡的机器装 CPU 版 PyTorch有 N 卡就装 CUDA 版两套命令差别只在 torch 的安装源# 创建虚拟环境Python 3.8 以上 python -m venv yolo_env source yolo_env/bin/activate # CPU 机器装 CPU 版 torch 和 ultralytics pip install torch torchvision pip install ultralytics # GPU 机器先确认驱动和 CUDA再装 torch nvidia-smi pip install torch torchvision pip install ultralyticstorch 和 torchvision 的版本要匹配这是老生常谈的坑。直接用 pip 默认源会拉互相兼容的版本别手动指定一堆小版本号。装完跑一个验证命令确认 torch 能看到 GPUpython -c import torch; print(torch.__version__); print(torch.cuda.is_available())GPU 机器返回True就说明环境通了。如果nvidia-smi能看到显卡但torch.cuda.is_available()返回 False一般是 PyTorch 版本和显卡驱动不匹配换新版本 torch 或升级驱动二选一。环境配置这个环节没什么玄学绝大多数问题都是 Python 版本和 torch 版本配对造成的。4.2 最小训练命令从预训练权重到自己的 best.pt环境就绪后训练命令比大多数人想象中短。核心就是一条yolo detect train下面这个是我用过很多次的配置yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch8 \ device0 \ patience30 \ projectruns/detect \ namemeeting_room \ seed42 \ optimizerauto这条命令里每个参数都有实际意义。modelyolov8n.pt指定预训练权重ultralytics 会在首次运行时自动下载这就是很多人问的「yolov8 哪里下载」的入口——不需要去别的地方找权重这条命令自己会拉。datadata.yaml指向上一章写好的数据集配置。device0表示用第一块 GPUCPU 机器改成devicecpu。project和name控制训练输出目录训练日志、权重、曲线图都会落在runs/detect/meeting_room下。训练中断了怎么办不用从头再来resumeTrue参数就是后悔药。接上一条训练yolo detect train resumeTrue它会自动从上次的断点恢复。注意 resume 依赖训练目录里的last.pt所以训练没跑完别删目录。内存小的机器可以把 workers 从默认值调低比如加workers2否则数据加载进程会把内存吃满。4.3 imgsz、batch、lr0三个必调参数与它们的边界训练参数里最值得花时间理解的是 imgsz、batch、lr0这三个直接决定能不能训练出能用的模型对应热搜词里「yolov8 模型训练参数含义」的诉求。下面给一份按会议室场景整理的参数表参数推荐值说明imgsz640 或 736会议室俯拍人头小640 起步后排密集建议 736batch4~16由显存决定6G 显存配 640 分辨率建议 8~16lr00.005数据量小于 500 张时默认 0.01 容易震荡flipud0.0会议室场景关闭上下翻转fliplr0.5水平翻转对视角泛化有帮助mosaic1.0小数据集上可关掉避免目标被切碎imgsz 是第一个要决策的参数。检测器的训练尺寸和推理尺寸必须一致否则训练时模型学的是 736 下的目标尺度推理时喂 640 的图目标相对变小漏检率直接上升。会议室俯拍画面里后排人头可能只有 30 像素我一般建议用 736代价是训练时间和显存占用上涨。batch 主要受显存约束。6G 显存的 GTX 1660Ti 跑 640 分辨率 batch 8 没问题上 736 就降到 batch 4。CPU 训练内存 16G 以上才建议跑 batch 4再小不如换云 GPU。lr0 默认 0.01 是 COCO 大规模数据的经验值自己只有几百张图时学习率大会导致 loss 震荡降到 0.005 更稳妥优化器选auto或AdamW都行小数据集上 AdamW 收敛更平缓。4.4 训练完看什么从损失曲线到验证指标训练结束后runs/detect/meeting_room目录下会自动生成results.png这就是「yolov8 画损失函数曲线图」的直接产物图形包含 train 和 val 的 box loss、cls loss、dfl loss 以及 mAP 曲线。不要只盯 train 曲线重点看 val 侧有没有和 train 明显背离train loss 一路下降、val loss 在某个 epoch 后开始回升这是过拟合的信号best.pt会停在回升之前的位置。曲线图是给人看的做数据分析时我更推荐直接读results.csvimport pandas as pd df pd.read_csv(runs/detect/meeting_room/results.csv) print(df.columns.tolist()) # 不同版本列名可能有前导空格先打印确认 mAP_col metrics/mAP50-95(B) best_epoch df[mAP_col].idxmax() print(fbest epoch: {best_epoch 1}, mAP50-95: {df[mAP_col].max():.4f})不同版本 ultralytics 的列名格式不完全一致打印列名再取索引比盲写安全。训练完成后单独跑一次验证命令输出 precision、recall、mAP50、mAP50-95 四个指标yolo detect val datadata.yaml modelruns/detect/meeting_room/weights/best.pt会议室任务的目标是「该漏的少漏、该错的不错」mAP50-95 到 0.7 以上、且实测场景不漏后排就可以进入部署环节了。指标只是门槛真正的好坏要在真实场景视频里跑过才算数。5. 避坑与排查会议室人数统计最容易翻车的五个细节5.1 人数反复横跳检测框不稳是根源现象视频里人数从 5 跳到 9 再跳回 5看起来像系统在乱报。原因有两层第一检测模型在某些帧置信度不足同一帧里人漏了又补回来框数自然抖第二没有任何帧间关联机制每一帧都是独立数数抖动被原样放大。会议室的人长时间不动框闪的原因多半不是人动了而是模型在困难的帧上没稳住。解决分两步。第一步给检测结果加置信度过滤conf0.35左右起步把边缘误检压掉一部分第二步做帧间平滑最简单的是 EMA# 帧间指数平滑平滑系数越大越迟钝 ema 0.7 * ema 0.3 * current_count show_count round(ema)更稳妥的做法是加跟踪给每个人分配 ID连续 N 帧确认后才计入总数离开画面后延迟若干帧才扣除。会议室场景不需要复杂的运动模型按检测框中心做最近邻匹配的轻量跟踪就够用ByteTrack 是效果和实现成本最平衡的选择。5.2 后排漏检俯拍视角的人头就是小现象前排人数统计没问题后排漏掉两三成画面越远越严重。原因是目标尺寸和训练尺度不匹配640 训练尺寸下后排人头可能只有二三十像素特征在小尺度分支里没有被充分表达。这不是模型玄学问题是分辨率分配问题。解决手段按优先级排一是训练和推理都上 736 或 832二是训练增强里开 scale 增强让模型见过更多尺度变化三是如果摄像头分辨率很高且 GPU 撑不住高 imgsz就把大幅画面切块tile推理每块单独检测再合并结果。另外摄像头安装角度也有影响接近正垂直的俯拍角度下目标形态最差稍微倾斜一点反而更容易检测。5.3 CPU 部署慢界面卡成 PPT现象检测功能一切正常但界面打开摄像头后帧率极低拖动窗口都卡顿。原因是推理在界面线程里同步执行YOLOv8 的 PyTorch 推理在纯 CPU 上每帧要几百毫秒到一秒多界面刷新自然被堵死。解决分两个层面。第一绝对不能把推理放在 UI 线程里用 QThread 把摄像头读取、推理、画框放在工作线程UI 线程只负责接收 QImage 刷新画面这是治本。第二把推理后端从 PyTorch 换成 ONNX Runtimeyolo export modelruns/detect/meeting_room/weights/best.pt formatonnximport onnxruntime as ort sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider])CPU 上 ONNX Runtime 比 PyTorch 推理快一截因为去掉了动态图的调度开销。注意 ONNX 版的输入输出约定和训练时一致输入要做 letterbox 归一化输出是类似(1, 84, 8400)的格式8400 是各个尺度特征图相加的锚点数具体数值随 imgsz 变化解析时写通用逻辑不要硬编码。5.4 标注不统一模型学歪了现象训练指标不难看但换一段真实会议视频就漏检。原因通常不在模型参数而在标注本身每个人框的粗细不同有人框头肩有人框全身后排小目标干脆没标模型学到的目标形态是撕裂的。解决方法是建立标注规范和自查习惯。统一头肩框规则遮挡目标按规则取舍然后每条数据从三个维度自查有没有漏标、有没有框错、有没有越界框。做这个项目时我有一个固定习惯——每标注 100 张就随机挑 10 张把框画回去看一遍肉眼扫完再继续后面省下的返工时间远比这 20 分钟多。5.5 换一间会议室就失效过拟合的典型症状现象训练用的会议室跑得很好换隔壁一间灯管位置不同、桌椅颜色不同漏检率明显上升。原因很简单数据集里大量样本来自同一个机位、同一个光照环境模型记住了这个会议室的特征而不是通用的人的特征。解决要分两步想。如果是毕设演示承认边界用训练场景做主要展示再录一段目标场景的视频做二次验证。如果是想做成通用产品就必须在数据侧下功夫多个会议室采集、不同时间点补拍、加入了光照扰动增强。会议室场景的泛化难度不高人这个类别在 COCO 里已经被学得很扎实只要数据不是太偏微调出来的模型换场地后通常只是精度下降不会完全失效。6. 可视化界面与部署落地从权重文件到双击运行的统计工具项目最后一个环节是把模型包成能演示的界面。技术选型上PyQt5 和 Tkinter 之间我建议 PyQt5控件、布局、信号机制都更成熟答辩演示也好看。核心思路是工作线程负责推理界面线程只显示结果两者用信号连接。界面里最小可跑的逻辑框架是这样from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QLabel, QApplication import cv2 from ultralytics import YOLO class Worker(QThread): frame_ready pyqtSignal(QImage, int) # 信号一帧图像 当前人数 def __init__(self, source): super().__init__() self.model YOLO(best.pt) # 加载训练好的权重 self.cap cv2.VideoCapture(source) def run(self): while True: ok, frame self.cap.read() if not ok: break results self.model(frame, verboseFalse) count len(results[0].boxes) # 当前帧检测到的人数 frame results[0].plot() # 画好框的画面 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qimg, count)代码里几个关键点说一下。模型加载放在线程初始化里不要放在 run 里每次循环加载。results[0].boxes取检测框列表len()就是这一帧的检测数配合第 5 章说的平滑逻辑用在界面上。plot()画完的图要转 RGB 再转 QImage否则界面上颜色会偏蓝偏乱。UI 侧只需要在槽函数里更新 QLabel 的 pixmap 和人数文本控件所有耗时推理都在 Worker 线程完成。部署时的验证方法也很简单录一段 15 个人进进出出的视频跑完后对比每一帧的统计人数和真实人数误差控制在 ±1 以内算合格再测一段单人连续坐 20 分钟的录像看人数是否保持稳定不跳变。验收这一关在我这里比训练指标更重要指标是开发者视角实际视频里的稳定性才是用户视角。这个方向做完之后再往上走的路子也清晰导出 ONNX 后可以接嵌入式平台跑实时推理界面端加历史曲线和 Excel 导出或者把单摄像头扩展成多会议室并发。我做过很多个检测类小项目一个习惯是每次收尾都把「最小可复现命令」完整写进 README——训练命令、验证命令、导出命令、启动界面命令四行跑不通时先查环境而不是查模型。希望这个方案的拆解对你做毕设或课程设计有实际帮助。本文还有配套的精品资源点击获取