news 2026/9/28 14:56:53

YOLOv8教室人数统计实战:从训练到Gradio可视化部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv8教室人数统计实战:从训练到Gradio可视化部署

简介:这份资源是面向计算机、人工智能、通信工程、自动化等专业学生与教师的YOLOv8目标检测实战项目,聚焦智慧教室场景下的人数统计任务,可作为毕业设计、课程设计或大作业的完整参考方案。压缩包共8个文件,包含3个Python脚本、3个模型权重文件与2个说明文本,整体约15.91MB,脚本分别承担可视化界面、模型训练与视频检测等功能,权重文件可直接加载推理,说明文档则提供部署与使用指引。项目已完整跑通,可输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图,便于答辩时展示实验过程与效果。目前已有58人学习,适合希望快速上手YOLOv8、需要一站式源码与数据集支撑的读者参考,也可在此基础上修改扩展实现其他检测功能。

1. 智慧教室人数统计:从 YOLOv8 检测框到可视化界面的完整落地路径

智慧教室人数统计这件事,听起来像是把 YOLOv8 跑起来数框就完事了,但真正做过的人都知道,从模型推理到界面展示之间隔着一堆工程细节。我最近刚把一套基于 YOLOv8 的教室人数统计方案从零跑通,包含数据集处理、模型训练、推理优化和可视化界面,整个过程踩了不少坑,也积累了一些可以复用的经验。这套方案的核心思路很直接:用 YOLOv8 检测画面中的人体目标,统计检测框数量作为人数,再通过可视化界面实时展示结果。它适合做毕设、课程设计,也适合作为目标检测入门到部署的练手项目。如果你手头有教室监控画面或者想用公开数据集快速验证,这篇文章会按「数据准备 → 模型训练 → 推理优化 → 界面集成 → 避坑排查」的顺序,把每个环节的参数和代码都摊开讲清楚。

2. 数据集准备与 YOLOv8 训练环境搭建

2.1 教室场景数据集从哪里来、怎么标

教室人数统计的数据集有两个来源:一是自己采集教室监控截图,用 Labelme 或 LabelImg 标注;二是用公开的人群检测数据集做迁移。自己采集的话,建议覆盖不同时段(上午、下午、傍晚)、不同座位密度(稀疏、中等、满座)、不同遮挡情况(前排遮挡后排、立柱遮挡)。标注时只标一类person,标注框尽量贴紧人体可见部分,不要为了框全而把被遮挡的部分也画进去,否则模型会学到错误的边界。

用 Labelme 标注完成后,需要转成 YOLO 格式。YOLO 的标签文件是.txt,每行格式为class_id x_center y_center width height,坐标都要归一化到 0~1。转换脚本如下:

import json import os from pathlib import Path def labelme_to_yolo(json_dir, output_dir, class_names): """ 将 Labelme 的 JSON 标注转换为 YOLO 格式的 txt 文件 json_dir: 存放 .json 文件的目录 output_dir: 输出 .txt 的目录 class_names: 类别名称列表,如 ['person'] """ json_dir = Path(json_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) for json_file in json_dir.glob("*.json"): with open(json_file, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_names: continue class_id = class_names.index(label) points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 归一化并计算中心点与宽高 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") txt_path = output_dir / (json_file.stem + ".txt") with open(txt_path, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": labelme_to_yolo( json_dir="datasets/labelme_json", output_dir="datasets/labels", class_names=["person"] )

这段代码的关键点在于坐标归一化:x_center和y_center是框中心点除以图像宽高,width和height是框的宽高除以图像宽高。归一化后的值必须在 0~1 之间,如果出现负数或大于 1,说明标注框超出了图像边界,需要检查原始标注。转换完成后,按 8:1:1 划分训练集、验证集和测试集,目录结构如下:

datasets/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/

然后创建data.yaml:

path: ./datasets train: images/train val: images/val test: images/test nc: 1 names: ['person']

nc是类别数,教室人数统计只有person一类,所以设为 1。names的顺序必须和标注时的class_names一致,否则训练出来的模型会把类别搞混。

2.2 YOLOv8 环境搭建:CPU 版本也能跑,但要注意这些参数

YOLOv8 的环境搭建有两种路线:CPU 版本和 GPU 版本。如果只是做课程设计或者验证流程,CPU 版本完全够用,但训练速度会慢很多。我一般推荐用 Ubuntu 20.04 或者 Windows + WSL2,Python 版本选 3.8~3.10,太高或太低都可能遇到依赖冲突。

安装命令如下:

# 创建虚拟环境 python -m venv yolov8_env source yolov8_env/bin/activate # Windows 用 yolov8_env\Scripts\activate # 安装 PyTorch(CPU 版本) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics pip install ultralytics # 验证安装 yolo checks

yolo checks会输出当前环境的信息,包括 PyTorch 版本、CUDA 是否可用、YOLOv8 版本等。如果 CUDA 显示不可用但你有 NVIDIA 显卡,说明 PyTorch 装成了 CPU 版本,需要卸载后重新安装对应 CUDA 版本的 PyTorch。CPU 版本训练时,把workers设为 0 或 2,避免多进程数据加载在 Windows 上出问题。

训练命令:

yolo detect train \ data=datasets/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ workers=4 \ device=0 \ patience=20 \ save=True \ project=runs/train \ name=classroom_person

参数说明:model=yolov8n.pt用的是 nano 版本,参数量最小,适合 CPU 推理和边缘设备部署;如果 GPU 显存充足,可以换成yolov8s.pt或yolov8m.pt。imgsz=640是输入图像尺寸,教室场景如果人物较小,可以提到 1280,但显存占用会翻倍。batch=16在 CPU 上可能内存不够,改成 8 或 4。patience=20表示 20 个 epoch 验证集指标不提升就早停,避免过拟合。device=0指定第一块 GPU,CPU 训练改成device=cpu。

训练完成后,权重文件保存在runs/train/classroom_person/weights/best.pt。用这个权重做推理:

yolo detect predict \ model=runs/train/classroom_person/weights/best.pt \ source=datasets/images/test \ conf=0.5 \ iou=0.45 \ save=True

conf=0.5是置信度阈值,低于这个值的检测框会被过滤掉;iou=0.45是 NMS 的 IoU 阈值,用来合并重叠框。教室场景如果人挨得比较近,IoU 可以调到 0.5~0.6,避免把相邻的人合并成一个框。

3. 人数统计逻辑与可视化界面集成

3.1 从检测框到人数:计数逻辑与去重策略

YOLOv8 输出的是检测框列表,每个框包含坐标、置信度和类别。人数统计最直接的做法就是统计person类别的框数量。但实际场景中会遇到两个问题:一是同一帧画面中,一个人可能被检测出多个框(比如背包被误检成人);二是视频流中,同一人在连续帧中被重复计数。

对于单帧图像,直接用len(boxes)统计即可。对于视频流,需要引入简单的跟踪逻辑。我一般用两种方案:一种是基于 IoU 的帧间匹配,另一种是调用 ByteTrack 或 BoT-SORT 跟踪器。YOLOv8 内置了跟踪功能,命令如下:

yolo track \ model=runs/train/classroom_person/weights/best.pt \ source=classroom_video.mp4 \ tracker=bytetrack.yaml \ conf=0.5 \ save=True

tracker=bytetrack.yaml启用 ByteTrack 跟踪器,它会为每个检测框分配一个 track ID,统计人数时只需要统计唯一 ID 的数量。但 ByteTrack 在人群密集场景下 ID 切换比较频繁,需要配合conf和iou调参。如果只是做课程设计,单帧统计已经足够,视频流统计可以用「每 N 帧统计一次,取滑动平均」的方式降低抖动。

Python 代码实现单帧统计:

from ultralytics import YOLO import cv2 model = YOLO("runs/train/classroom_person/weights/best.pt") def count_person(image_path, conf_thres=0.5): """ 统计单张图像中的人数 image_path: 图像路径 conf_thres: 置信度阈值 返回: 人数、标注后的图像 """ results = model(image_path, conf=conf_thres)[0] person_count = 0 img = cv2.imread(image_path) for box in results.boxes: cls_id = int(box.cls[0]) if cls_id == 0: # person 类别 person_count += 1 x1, y1, x2, y2 = map(int, box.xyxy[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"person {box.conf[0]:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.putText(img, f"Total: {person_count}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3) return person_count, img

这段代码的逻辑是:遍历所有检测框,只统计cls_id == 0的框,并在图像上画出矩形框和置信度。最后在左上角显示总人数。conf_thres可以根据实际效果调整,教室场景如果误检较多,可以提到 0.6;如果漏检较多,降到 0.3~0.4。

3.2 可视化界面:用 Gradio 快速搭一个能演示的 Web 界面

可视化界面是毕设和课程设计的加分项。我试过 PyQt、Streamlit 和 Gradio,最后发现 Gradio 最适合快速搭建演示界面,代码量少,支持图片上传、视频上传和实时摄像头。安装命令:

pip install gradio

界面代码:

import gradio as gr from ultralytics import YOLO import cv2 import numpy as np model = YOLO("runs/train/classroom_person/weights/best.pt") def detect_image(image, conf_thres): """ Gradio 图片检测回调 image: numpy 数组,来自 Gradio 上传 conf_thres: 置信度阈值 """ results = model(image, conf=conf_thres)[0] person_count = 0 img = image.copy() for box in results.boxes: cls_id = int(box.cls[0]) if cls_id == 0: person_count += 1 x1, y1, x2, y2 = map(int, box.xyxy[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"Count: {person_count}", (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 3) return img, f"当前人数:{person_count}" with gr.Blocks(title="智慧教室人数统计") as demo: gr.Markdown("## 智慧教室人数统计系统") with gr.Row(): with gr.Column(): input_image = gr.Image(label="上传教室图片", type="numpy") conf_slider = gr.Slider(0.1, 0.9, value=0.5, label="置信度阈值") btn = gr.Button("开始检测") with gr.Column(): output_image = gr.Image(label="检测结果") output_text = gr.Textbox(label="统计结果") btn.click(fn=detect_image, inputs=[input_image, conf_slider], outputs=[output_image, output_text]) demo.launch(server_name="0.0.0.0", server_port=7860)

gr.Blocks是 Gradio 的块式布局,比gr.Interface更灵活。gr.Image(type="numpy")把上传的图片转成 numpy 数组,直接传给 YOLOv8 推理。conf_slider让用户实时调整置信度阈值,方便演示不同参数下的效果。demo.launch(server_name="0.0.0.0")让界面监听所有网卡,局域网内其他设备也能访问。如果部署在服务器上,记得开放 7860 端口。

提示:Gradio 默认只允许本地访问,server_name="0.0.0.0"改成这个才能外部访问。如果遇到端口占用,换一个端口即可。

4. 避坑与常见问题排查

4.1 训练 loss 不下降,mAP 卡在 0.3 上不去

现象:训练了 50 个 epoch,box_loss 和 cls_loss 都在波动但不下降,验证集 mAP@0.5 一直在 0.3 左右。

原因:最常见的原因是标注格式错误。YOLO 的标签必须是归一化后的class_id x_center y_center width height,如果直接用了像素坐标,模型学到的就是错误的位置信息。另一个原因是数据集中负样本太多,或者标注框把整个画面都框进去了。

解决:用yolo detect train之前,先跑一遍数据校验脚本,检查标签文件是否有越界值。可以用labelImg或labelme重新检查标注。另外,把data.yaml里的nc和names确认一遍,类别数不对也会导致 loss 异常。

4.2 CPU 推理速度太慢,单帧要 2 秒以上

现象:用 CPU 跑 YOLOv8n,单张 640x640 图像推理耗时 2 秒以上,视频流完全卡顿。

原因:CPU 版本 PyTorch 默认用单线程推理,没有启用 MKL 或 OpenMP 加速。另外,模型输入尺寸太大也会拖慢速度。

解决:推理时把imgsz降到 320 或 416,速度能提升 2~3 倍。另外,用model.export(format="onnx")导出 ONNX 模型,再用onnxruntime推理,CPU 上速度比 PyTorch 快 30%~50%。如果还是慢,考虑用 OpenVINO 做进一步优化。

4.3 可视化界面图片上传后显示空白

现象:Gradio 界面上传图片后,输出区域一片空白,没有检测结果。

原因:Gradio 的gr.Image(type="numpy")返回的是 RGB 格式,而 OpenCV 默认是 BGR 格式。如果直接把 numpy 数组传给cv2.rectangle和cv2.putText,颜色会错乱,但更常见的问题是图像通道数不对导致显示异常。

解决:在detect_image函数里加一行img = cv2.cvtColor(image, cv2.COLOR_RGB2BGR),处理完再转回 RGB 返回。或者直接用 PIL 库画框,避免 OpenCV 的通道问题。

4.4 视频流统计人数跳变严重

现象:视频流中人数一会儿 15 一会儿 23,波动很大。

原因:单帧检测本身有抖动,加上没有做帧间平滑,导致计数不稳定。

解决:引入滑动平均,比如取最近 10 帧的计数中位数作为当前人数。或者用 ByteTrack 跟踪器,统计唯一 track ID 的数量。如果场景中人流量不大,还可以加一个「人数变化超过阈值才更新显示」的逻辑,减少视觉抖动。

4.5 模型把椅子、书包误检成人

现象:检测结果中出现了大量非人物的框,置信度还不低。

原因:训练数据中负样本不足,或者标注时把一些类似人物的物体也标成了 person。

解决:在训练集中加入包含椅子、书包、海报等干扰物的负样本图像,不标注任何目标。另外,推理时提高conf阈值到 0.6~0.7,过滤掉低置信度的误检。如果误检集中在某个区域,可以用 ROI 裁剪,只对教室座位区域做检测。

5. 进阶技巧:用 ONNX 导出和 TensorRT 加速推理

如果部署环境有 NVIDIA GPU,把 YOLOv8 导出成 TensorRT 引擎能获得 3~5 倍的推理加速。导出命令:

yolo export model=runs/train/classroom_person/weights/best.pt format=engine half=True device=0

half=True启用 FP16 精度,速度更快,精度损失很小。导出后的.engine文件可以直接用YOLO("best.engine")加载推理。如果没有 GPU,用 ONNX 导出也能在 CPU 上获得不错的加速:

yolo export model=runs/train/classroom_person/weights/best.pt format=onnx opset=12 simplify=True

opset=12是 ONNX 算子集版本,兼容性最好;simplify=True会简化计算图,减少冗余算子。导出后用onnxruntime推理:

import onnxruntime as ort import numpy as np import cv2 session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) input_name = session.get_inputs()[0].name def preprocess(image, input_size=640): """图像预处理:缩放、归一化、转 NCHW""" img = cv2.resize(image, (input_size, input_size)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1)) # HWC -> CHW img = np.expand_dims(img, axis=0) # CHW -> NCHW return img def infer(image_path): """ONNX 推理并统计人数""" img = cv2.imread(image_path) input_tensor = preprocess(img) outputs = session.run(None, {input_name: input_tensor}) # outputs[0] 形状为 [1, 84, 8400],前 4 个是框坐标,后面是类别分数 predictions = outputs[0][0] person_count = 0 conf_thres = 0.5 for pred in predictions.T: scores = pred[4:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > conf_thres and class_id == 0: person_count += 1 return person_count if __name__ == "__main__": count = infer("test_classroom.jpg") print(f"检测到人数:{count}")

这段代码的关键是理解 ONNX 输出格式:YOLOv8 的输出形状是[1, 84, 8400],其中 84 = 4 个框坐标 + 80 个类别分数(COCO 数据集),8400 是候选框数量。教室人数统计只有 1 类,所以输出形状是[1, 5, 8400]。遍历时取pred[4:]作为类别分数,argmax得到类别 ID,判断是否为person并累加。

注意:ONNX 推理时没有内置 NMS,需要自己实现或调用cv2.dnn.NMSBoxes。如果直接统计所有超过置信度阈值的框,会出现大量重叠框导致人数虚高。

我自己的习惯是:训练阶段用 PyTorch 验证效果,部署阶段优先导出 ONNX 或 TensorRT,推理代码里一定要加 NMS。另外,教室场景的光照变化很大,如果模型在某个时段效果明显下降,不要急着重新训练,先检查是不是摄像头曝光或白平衡变了,调整摄像头参数往往比调模型更快。希望帮到你。

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

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

致好久不见的你:从消息到重逢,主动联系旧友的实操指南

致好久不见的你——这句话在我手机备忘录里存了大半年,一直没有发出去。起因很普通。某个周末深夜,手机相册弹出一则“去年的今天”,照片里是几个围着火锅大笑的人。我盯着那几个人辨认了好一会儿,才想起来其中两张脸属于谁。一个…

作者头像 李华
网站建设 2026/9/28 14:54:30

PyTorch猫狗公鸡三分类实战:从环境配置到模型部署全流程指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 14:54:12

CrewAI智能体开发:封装S3读取工具的全流程实践

作为一个长期在数据管道和AI Agent之间来回折腾的人,我越来越觉得“给智能体配好工具”才是落地过程中最容易被低估的环节。模型选得再好,计划排得再漂亮,最后拉不到数据、读不了文件,整个流程就是空中楼阁。今天想拆解的这个小项…

作者头像 李华
网站建设 2026/9/28 14:54:11

AI资讯日报热词背后的实战指南:从Agent到本地部署

1. 从热搜词里读AI走向:这届资讯日报该怎么看先说结论:我不太喜欢把“AI资讯日报”写成新闻清单,什么“某某公司发布新模型”“某某产品完成融资”,一天十条转发,看完就忘。真正值得看的,是那些反复出现、让…

作者头像 李华
网站建设 2026/9/28 14:53:51

电力系统动态状态估计:EKF与UKF算法的Matlab实现与对比

在电力系统的在线监测与运行控制里,动态状态估计一直是个绕不开的核心话题。用扩展卡尔曼滤波(EKF)和无迹卡尔曼滤波(UKF)去跟踪发电机功角、转速这些动态状态,是目前学术研究和工程尝试里最主流的做法。这…

作者头像 李华
网站建设 2026/9/28 14:53:50

YOLO小数据集实战:1531张罐头与鲜奶瓶检测全流程

简介:本资源为面向YOLO系列算法目标检测的罐头与瓶子数据集,包含鲜奶、瓶子等类别,适合从事目标检测模型训练与验证的开发者、学生及研究人员使用。数据集已划分好训练与测试集,并附带data.yaml配置文件,可直接适配yol…

作者头像 李华