ARTICLE DETAIL

资讯详情

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

基于YOLOv8的智慧教室人数统计:环境搭建、推理计数与界面部署

基于YOLOv8的智慧教室人数统计:环境搭建、推理计数与界面部署 简介基于YOLOv8的智慧教室人数统计应用是一套完整的目标检测实战项目主要面向计算机视觉、深度学习方向的毕业设计或课程设计。项目基于YOLOv8模型实现了对教室场景中人员的实时检测与人数统计同时配有可视化操作界面简单部署即可运行适合作为毕设核心成果或课程综合实践。压缩包共8个文件大小约15.91MB包含3个Python源码文件分别承担模型训练、视频检测和可视化界面展示3个模型权重文件可直接加载使用另有2个说明文档帮助快速上手。文件结构清晰按功能划分明确。目前已有58人浏览学习。项目代码来自个人毕业设计所有代码均测试通过并附带完整数据集与部署教程。运行后可产出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果以及标签分布图等能全面支撑答辩展示和报告撰写。无论是刚接触目标检测的初学者还是需要快速完成毕设的学生都可以直接基于此项目进行修改和二次开发省去大量从零搭建的时间。1. 从毕设翻车现场说起YOLOv8智慧教室人数统计到底难在哪每年答辩季都有人拿着这类项目在讲台上翻车界面打不开、摄像头画面卡成一帧、人数统计对着空教室报出十几个。问题几乎都出在同一个地方——拿到“源码数据集部署教程”只是开始把环境跑通、把计数逻辑调准才是真正的分水岭。《基于YOLOv8的智慧教室人数统计应用》这类项目本质上是把目标检测、目标跟踪、业务计数三层逻辑串成一条流水线再加上一个可视化界面把结果呈现在屏幕上。听上去不难但每个环节都有各自的坑数据集的标注格式不对会导致训练直接报错置信度阈值设高了漏人、设低了重复计数视频流处理线程没写好界面直接卡死。这篇文章按我实际做这类项目的顺序来拆先讲环境和数据集怎么准备再讲核心的推理与计数逻辑怎么组织然后是可视化界面的线程模型最后把最常见的几个坑一次性说清楚。适合两类人看——正在做毕设需要快速跑通全流程的同学以及想把YOLOv8落地到教室、会议室这类固定场景做人数统计的工程师。不需要你有深度学习理论功底但建议你熟Python基础语法对类和线程有基本概念。2. 搭环境、理数据先让模型能在你机器上跑起来2.1 环境版本对照CPU和GPU两套方案怎么选YOLOv8项目最折磨人的不是算法是环境。这类项目源码一般基于ultralytics框架写代码的时候作者往往用的是自己机器上的版本你拿到的requirements.txt里通常只有ultralytics、opencv-python、torch这几个包名版本号可能根本没锁。先按这个思路确认版本组合# 创建一个干净的虚拟环境避免和系统Python打架 conda create -n classroom python3.9 -y conda activate classroom # 先装CPU版PyTorch在纯CPU机器上跑推理用 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cpu # 有NVIDIA显卡再补GPU版注意驱动版本和CUDA版本匹配 # pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装ultralytics和其余依赖 pip install ultralytics8.0.200 opencv-python4.8.1.78逻辑说明ultralytics对版本其实比较宽容8.0.x系列我在Python 3.9到3.11上都跑过核心冲突集中在你装了GPU版torch但实际没有可用显卡或者反过来——显卡驱动是新的但torch版本太老不认识新的CUDA运行时。没有NVIDIA显卡就直接用CPU版YOLOv8n这种nano模型在纯CPU上跑一帧大约100到200毫秒做实时统计够用不用纠结。参数说明里有一个细节容易忽略PyTorch的CPU版本安装命令带了--index-url指定了下载源如果你知道GPU版需要额外安装匹配的CUDA运行时环境就明白为什么要先把版本钉死了——torch和torchvision版本必须配套否则模型前向传播时会出现奇怪的shape错误或直接segment fault。装完跑一个验证命令# 验证环境是否可用yolov8n.pt会从官方服务器自动下载 from ultralytics import YOLO model YOLO(yolov8n.pt) result model.predict(test.jpg) print(len(result))逻辑说明这段代码做了三件事实例化模型对象、加载权重、执行一次推理。如果环境缺少依赖或模型文件路径不对这里就会直接抛异常。第一次跑会自动下载权重文件大概6MB下载完成后会保存在当前目录下。2.2 教室场景数据集的标注与目录结构“完整数据集”这四个字在项目简介里看着简单拿到手之后通常就是一堆图片和一堆txt或xml文件。要做目标检测训练数据必须组织成YOLO格式的目录结构dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 与图片同名的txt标注文件 │ └── val/ ├── data.yaml # 数据集配置文件 └── train.py # 训练脚本一般会带上data.yaml是训练入口必须自己改# data.yaml 核心配置 path: ./dataset # 数据集根目录相对路径或绝对路径 train: images/train # 训练集图片路径 val: images/val # 验证集图片路径 nc: 1 # 类别数教室人数统计一般只检测人 names: [person] # 类别名称列表参数说明nc和names是这里最容易出错的。有的数据集标的是person和chair两个类别训练时nc没改成2训练过程不报错但模型学到了错误标签映射训练完检测结果全是乱的。另外path字段建议用绝对路径尤其是你从压缩包解压到不同目录的时候相对路径的坑非常隐蔽——训练时数据集找不到报错信息是AssertionError: train dataset not found新手往往要看很久才反应过来是路径问题。如果你拿到的数据集是LabelMe标注的JSON格式或VOC格式的XML需要写一个转换脚本。我一般用这个模板把VOC转成YOLO格式的txtimport os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, class_names, output_dir): tree ET.parse(xml_file) root tree.getroot() img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) txt_name os.path.splitext(os.path.basename(xml_file))[0] .txt with open(os.path.join(output_dir, txt_name), w) as f: for obj in root.iter(object): class_name obj.find(name).text if class_name not in class_names: continue class_id class_names.index(class_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 关键转换后坐标必须归一化到0-1 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height f.write(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) class_names [person] os.makedirs(labels/train, exist_okTrue) for xml_file in os.listdir(xmls): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xmls, xml_file), class_names, labels/train)逻辑说明VOC的标注是左上角和右下角的绝对坐标而YOLO格式要求中心点坐标和宽高都是归一化后的相对值。这里有个新手极易踩的坑——宽高忘记除以图片尺寸。如果直接写入原始像素值训练时YOLO会把这些数值当作0到1之间的比例等于标注框全部错位损失曲线看起来在下降但验证集mAP非常低。参数说明class_names列表的顺序决定了类别ID的映射训练时的names配置必须和这个顺序完全一致。另外如果原始标注里有超出图片边界的框比如xmax大于图片宽度需要做截断处理否则训练时数据加载阶段会警告但不报错模型学到的框位置会偏。2.3 训练参数配置跑一个能用的模型最少要哪些调整拿到现成的源码包一般会有个训练脚本。直接用默认参数能跑但效果未必适合教室场景。教室的特点是摄像头固定在墙角或天花板俯视视角人体互相遮挡严重边缘区域人体不完整。所以训练参数要针对性的调from ultralytics import YOLO # 基于预训练权重微调比从零训练快很多且效果更好 model YOLO(yolov8n.pt) model.train( datadata.yaml, epochs100, # 数据集小的话100轮足够 imgsz640, # 输入分辨率越大越准但越慢 batch16, # 根据显存调整显存不够就调小 patience20, # 验证集指标连续20轮不提升就早停 cacheTrue, # 将图像缓存到内存加快训练速度 device0, # 用GPU训练CPU就写devicecpu workers4 # 数据加载线程数 )参数说明imgsz是教室场景最值得调的参数。俯视视角下人目标比较小如果用默认的640后排学生可能只有十几像素高模型很难学到特征如果算力允许调到960甚至1280会有明显提升但推理速度会成倍下降。实际做固定场景部署时我一般先用640训练一版看baseline再针对性用960微调。patience参数要留意早停触发不代表训练到最优了——有时验证集在60轮左右有个低谷之后又会上来如果patience设太小训练提前终止你就错过了后面的高点。训练过程中要盯两个输出runs/detect/train/目录下的results.png和weights/best.pt。前者包含损失曲线和验证指标曲线后者是每一轮保存的在验证集上表现最好的权重。项目部署时直接加载best.pt不要用最后一次epoch的last.pt——现实中经常出现最后几轮过拟合导致验证集指标下降的情况。3. 推理与人数统计从“检测出人”到“数出人数”的最后一公里3.1 模型加载与单帧推理别在置信度阈值上想当然训练完拿到best.pt下一步就是写推理脚本。要做一个真正能统计人数的应用直接调用一次predict是不够的需要把置信度、类别过滤、NMS这几个参数对结果的影响搞清楚。模型输出的原始结果是超过阈值的所有框这里面既包括人也可能包括模型误检的椅子靠背、墙上的挂钟。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) # 加载摄像头或视频文件 import cv2 # cap cv2.VideoCapture(0) # 摄像头 cap cv2.VideoCapture(classroom_video.mp4) while True: ret, frame cap.read() if not ret: break # 核心推理只保留类别0person置信度大于0.35 results model.predict( sourceframe, conf0.35, # 置信度阈值调低漏检少但误检多 iou0.45, # NMS的IoU阈值调高合并重叠框 classes[0], # 只保留person类别 verboseFalse # 关闭控制台输出避免刷屏 ) boxes results[0].boxes person_count len(boxes) # 画框 annotated results[0].plot() cv2.putText(annotated, fCount: {person_count}, (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Classroom, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明整个实时统计的核心逻辑就在这个循环里。model.predict每帧执行一次推理返回的results对象里包含检测框坐标、置信度和类别ID。classes[0]是很多初学者忽略的参数——不写的话模型会检测出它在训练集里见过的所有类别如果你训练时nc1还好如果数据集中有背景误标注成了其他类别这里就会混入非人员目标。参数出现一个容易理解错的地方conf设成0.35并不是固定的最优值。教室后排小目标置信度往往在0.25到0.4之间而前排学生脸部或者后脑勺被误检成其他人或物体的置信度可能在0.5以上。实际项目中这个参数需要在真实场景的视频上跑几遍找到漏检和误检的平衡点。流程上建议先用0.25跑一遍测试视频统计输出的框数量再可视化检查哪些是误检哪些是漏检逐步调。3.2 去重与防抖简单但有效的三类计数策略单帧检测只是第一步真正的人数统计应用要考虑连续帧之间的稳定性。一个学生在这帧被检测到下帧因为低头被漏检再下帧又出现如果直接取每帧的检测数画曲线数据会非常抖。常见做法有三种滑动窗口滤波、检测框平滑、定时统计。教室场景下我建议用定时统计滑动窗口的组合from collections import deque import time class PersonCounter: def __init__(self, window_size5, min_confidence0.35): self.window_size window_size self.min_confidence min_confidence self.history deque(maxlenwindow_size) # 保存最近N帧的计数结果 self.event_times deque(maxlen100) # 记录进出事件时间戳 def update(self, frame_count): self.history.append(frame_count) # 用中位数而非平均值能有效抵抗偶发的误检尖峰 sorted_counts sorted(self.history) return sorted_counts[len(sorted_counts) // 2] def get_event_rate(self, interval60): 计算过去interval秒内的平均人数变化趋势 now time.time() while self.event_times and self.event_times[0] now - interval: self.event_times.popleft() return len(self.event_times) # 使用示例 counter PersonCounter(window_size5) # 在推理循环中调用 # stable_count counter.update(person_count)逻辑说明这个类做的事情很简单——把最近5帧的检测人数用中位数替代当前值。为什么不用平均值因为异常值某一个误检把人数从30拉到35会把平均值拉偏而中位数对这类尖峰噪声有天然的抵抗力。event_times这个队列用于记录进场出场事件可以用来做时段分析比如统计每节课的上座率变化。参数说明window_size5意味着统计结果滞后最多5帧按25fps算就是0.2秒的延迟人眼几乎感知不到。如果现场有大量遮挡导致检测数剧烈波动可以把窗口调到10到15但代价是人数变化后的反应变慢。门禁场景比教室要求更高还需要做轨迹追踪和IOU匹配来判断进还是出这对教室里“坐在座位上不动的人”来说不是刚需不需要引入BYTETrack之类的复杂跟踪器。3.3 摄像头接入与画面来源切换USB摄像头和IP摄像头用同一套接口实际部署时有人用USB摄像头有人用教室已有的IP摄像头RTSP流。OpenCV的VideoCapture接口可以统一处理但RTSP流有两个坑延迟缓冲和断流重连。常见的做法是加一个缓冲清理和断线重试机制import cv2 import time class VideoStream: def __init__(self, source, buffer_size1): self.cap cv2.VideoCapture(source) self.cap.set(cv2.CAP_PROP_BUFFERSIZE, buffer_size) # 关键减少缓存帧数 self.source source self.last_read_time time.time() def read(self): # 超过2秒没有读到新帧尝试重连 if time.time() - self.last_read_time 2.0: self.cap.release() self.cap cv2.VideoCapture(self.source) ret, frame self.cap.read() if ret: self.last_read_time time.time() return ret, frame def release(self): self.cap.release() # 使用示例 # stream VideoStream(rtsp://192.168.1.100:554/stream1) # stream VideoStream(0) # USB摄像头逻辑说明CAP_PROP_BUFFERSIZE设置成1非常关键。默认情况下OpenCV会把摄像头采集的帧缓存到内部队列如果推理速度跟不上采集速度cap.read()读到的永远是几十毫秒前的旧帧画面看着像延迟卡顿。限制缓冲区后每次读到的都是最新的帧代价是偶尔丢帧——对人数统计不敏感总比画面越拖越后强。参数说明RTSP重连的间隔2秒要根据网络情况调整。局域网内一般1-2秒就能重连成功跨网络或无线网络建议拉长到5秒避免频繁释放重建连接导致画面反复黑屏。教室通常用有线网络接入摄像头这个设置基本一次调试就能稳定运行。4. 可视化界面把检测结果变成能看的桌面应用4.1 PySide6主窗口结构界面和推理分离是底线可视化界面是这类项目最有卖相的部分也是最容易写崩的部分。我刚接触时犯过的最大错误是把推理逻辑直接写在界面线程里结果窗口一开就未响应。正确思路是界面线程Qt主线程只负责绘制和接收用户操作推理放在独立的QThread工作线程里两个线程通过信号槽通信。import sys from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget from PySide6.QtCore import QThread, Signal, QTimer from PySide6.QtGui import QImage, QPixmap class InferenceThread(QThread): frame_ready Signal(QImage) # 每一帧处理完成后发送给界面 count_updated Signal(int) # 人数统计值更新 def __init__(self): super().__init__() self.running True self.cap cv2.VideoCapture(0) self.model YOLO(best.pt) self.counter PersonCounter() def run(self): while self.running: ret, frame self.cap.read() if not ret: continue results self.model.predict(frame, conf0.35, classes[0], verboseFalse) person_count len(results[0].boxes) stable_count self.counter.update(person_count) annotated results[0].plot() # OpenCV的BGR格式转成Qt能显示的RGB格式 rgb_image cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape qt_image QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qt_image.copy()) self.count_updated.emit(stable_count) def stop(self): self.running False self.cap.release() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(智慧教室人数统计系统) self.image_label QLabel() self.count_label QLabel(当前人数: 0) layout QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.count_label) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.thread InferenceThread() self.thread.frame_ready.connect(self.update_frame) self.thread.count_updated.connect(self.update_count) self.thread.start() def update_frame(self, image): self.image_label.setPixmap(QPixmap.fromImage(image)) def update_count(self, count): self.count_label.setText(f当前人数: {count}) def closeEvent(self, event): self.thread.stop() self.thread.wait() event.accept() # app QApplication(sys.argv) # window MainWindow() # window.show() # sys.exit(app.exec())逻辑说明这个结构拆成两个类职责非常清楚。InferenceThread继承QThread在run()方法里做无限循环的推理任务主线程只做槽函数update_frame和update_count的接收与界面更新。注意QImage(rgb_image.data, ...)之后必须调用.copy()再通过信号传递因为rgb_image.data指向的内存在函数结束后可能被释放不复制会出现画面花屏或崩溃。信号槽的参数只能传递Qt认识的类型QImage和int都是合法的。参数说明cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)这行不要省。OpenCV读图默认是BGR三通道顺序Qt的QImage.Format_RGB888期望的是RGB顺序。如果不转换画面里的蓝色和红色会互换人的肤色会偏蓝第一眼看上去还以为模型检测有问题其实是颜色通道的锅。4.2 历史数据统计与可视化SQLite落地存储实时画面只是其中一块需求教室人数统计应用通常还要能看历史数据哪个时间段人最多、每天的平均出勤率是多少。这类需求用SQLite就够了不用上MySQL。在推理线程里定期写入当前人数到数据库界面端用图表展示-- 建表语句记录每次检测统计的结果 CREATE TABLE IF NOT EXISTS seat_usage ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, person_count INTEGER NOT NULL, class_room TEXT DEFAULT A101 ); -- 按小时聚合查询 SELECT strftime(%Y-%m-%d %H:00, timestamp) AS hour_slot, AVG(person_count) AS avg_count FROM seat_usage GROUP BY hour_slot ORDER BY hour_slot;参数说明strftime的格式化字符串是按小时聚合的关键改成%Y-%m-%d就是按天聚合。教室场景下一般不用按分钟聚合因为人数在课间会有剧烈波动按小时聚合能更清楚看到每个时间段的规律。界面端可以用matplotlib的FigureCanvasQTAgg嵌入到PySide6窗口中绘制柱状图。有一个细节matplotlib的绘图线程也尽量和界面分离否则一张大图刷新时界面会掉帧。5. 避坑排查部署这套应用最常见的5个翻车现场5.1 环境冲突装了GPU版PyTorch但运行报CUDA错误现象是运行训练脚本直接抛AssertionError: Torch not compiled with CUDA enabled或者推理时卡在Device: cuda:0几秒后报错。原因通常是两个一是机器上没有NVIDIA显卡但代码里写了device0或devicecuda二是机器有显卡但驱动版本太旧torch调用CUDA失败。解决思路分两步先确认有没有可用GPU再修改代码里的设备参数。# 确认CUDA是否可用 python -c import torch; print(torch.cuda.is_available()) # 输出False就老老实实改CPU模式解决把训练脚本里device0改成devicecpu。推理脚本里model.predict时会自动检测设备不需要显式指定但如果代码里写了或模型加载后调用了.to(cuda)也要同步改。注意一个细节CPU模式下batch参数要调小一般16以内否则内存直接打满。5.2 中文路径导致权重加载失败现象是YOLO(runs/detect/train/weights/best.pt)提示文件不存在但你明明看到文件在。原因大概率是路径包含中文目录名比如C:\用户\桌面\基于YOLOv8的智慧教室...\best.pt。ultralytics底层调用torch.load时对于非ASCII路径的支持在Windows上一直有兼容问题有时报No such file or directory有时直接KeyError。解决把整个项目目录放到纯英文路径下比如D:\yolo_classroom。训练和推理时代码里不要用相对路径加中文拼接直接定义成变量统一管理。5.3 人数统计重复计数或漏计严重现象是画面上画框没有问题但统计的数字经常跳动明明座位上只有5个人却显示8个或者3个。原因有两类一是置信度阈值设低了误检把桌椅靠背当成人二是前后帧检测结果不稳定同一人这一帧被检测到下一帧漏掉再下一帧又出现直方图或平均数处理不好的话就会出现反复横跳。解决思路是前面提过的滑动窗口中位数滤波但有一个容易被忽视的细节classes[0]这个参数必须加否则训练时数据集中有意外标注成其他类别的物体也会被统计进来。另外检测框面积越过了画面边缘的比如只露了半个人头这类其实算不算“在场人数”取决于你的应用定义我建议加上面积过滤# 过滤面积过小的目标一般认为小于画面总面积0.5%的框不可靠 area_threshold frame_area * 0.005 boxes results[0].boxes valid_boxes [] for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() area (x2 - x1) * (y2 - y1) if area area_threshold: valid_boxes.append(box) person_count len(valid_boxes)逻辑说明面积过滤针对的是远端小目标和误检杂斑。教室俯视视角下后排人头的目标面积不会太小如果你发现某个误检框面积异常小那多半不是人。这个阈值需要在你自己的摄像头画面上试几次才能定下来。5.4 界面显示花屏或图像撕裂现象是推理线程运行正常控制台输出的计数没问题但界面显示的画面一半正常一半花屏。原因是QImage构造时用的显存指针在信号传递过程中被释放。前面代码里我在emit前调用了.copy()这个不能省。另一个原因是跨线程传递大尺寸QImage时如果没有复制Qt内部虽然做了引用计数但OpenCV的frame对象释放后内部的buffer就不可控了。解决就一条信号发出之前确保传给信号的数据是独立持有的副本。5.5 训练时损失曲线不降或验证集mAP为0现象是训练过程正常跑但results.png里训练损失下降到一定程度后横盘验证集mAP全程为0。原因我见过最多的是数据集标注格式错了YOLO要求label文件的每一行是类别ID x_center y_center width height但有人转格式时把坐标写成了左上角和右下角的像素值或者类别ID从1开始而不是从0开始。解决办法是写一段验证脚本随机挑几张图片和对应的label文件把标注框画在图上肉眼看一遍import cv2 import numpy as np # 挑一张训练图片画上标注框检查是否正确 img cv2.imread(dataset/images/train/000001.jpg) h, w img.shape[:2] with open(dataset/labels/train/000001.txt) as f: for line in f: parts line.strip().split() class_id int(parts[0]) x_c, y_c, box_w, box_h map(float, parts[1:]) # 反归一化回像素坐标 x1 int((x_c - box_w / 2) * w) y1 int((y_c - box_h / 2) * h) x2 int((x_c box_w / 2) * w) y2 int((y_c box_h / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(class_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(check_annotation.jpg, img)逻辑说明这一步能发现绝大多数标注问题。如果画出的框位置明显不对比如框在了完全不相关的位置排查思路依次是检查txt里的坐标是否归一化数值都小于1才对、检查类别ID和names的顺序映射、检查图片宽度高度是否按照原图尺寸做的归一化。6. 让模型在真实部署中更稳验证、导出与调优方向项目跑通之后下一步往往是把它推到更真实的场景里。这里说三个我常用的进阶操作能显著降低现场翻车概率。第一个是脱离“能跑”这个层次用指标判断模型好不好。训练完后不要只看loss曲线跑一次验证集得到精确率和召回率yolo val modelruns/detect/train/weights/best.pt datadata.yaml输出里重点看两个字段mAP50和mAP50-95。教室人数统计对漏检比对误检更敏感——漏掉一个学生比多算一个背景目标影响更大。如果召回过低优先考虑降低置信度阈值或者增加遮挡场景的标注数据。如果精确率低误检多再适当提高阈值。第二个是把模型导出成ONNX或TensorRT格式做加速推理。在教室场景如果有NVIDIA设备或者边缘盒子CPU推理满足不了帧率要求时直接导出成TensorRT能跑出几倍的性能提升# 导出成TensorRT格式注意需要GPU环境 model.export(formatengine, imgsz640, halfTrue) # 导出成ONNX配合onnxruntime在CPU上跑 model.export(formatonnx, imgsz640, dynamicTrue)逻辑说明halfTrue表示用FP16精度推理速度几乎翻倍但精度损失极小教室人数统计这种不追求极致精度的场景完全够用。ONNX格式的优势是不依赖PyTorch环境部署到没有GPU的机器上时直接onnxruntime加载省去搭环境的麻烦。动态batch的dynamicTrue参数对教室场景不是必须的静态输入尺寸往往更稳定。第三个容易被忽略的是置信度阈值和Iou阈值的联动。很多人只调conf忘了iou。在教室俯视视角下不同人体框之间几乎没有重叠iou0.45的默认值适用但如果摄像头是平视角度前排学生的框会重叠很多NMS会把一个学生身上的多个框合并成一个还是错误地保留多个取决于iou阈值设多高。一般平视场景建议降到0.3到0.35。想想自己第一次做这个方向的经历最深刻的教训是拿到这类项目资源先别急着跑训练和看界面先用拿到的一小段真实视频把模型跑一遍确认检测效果符合预期再投入时间去做界面和部署。很多人在界面美化上花了两天结果模型的准确率根本没达标最后又回头调数据白白浪费功夫。往后的习惯是先做端到端的垂直切片——从摄像头取帧到出人数哪怕是纯命令行的输出也要先把整条链路捋通再考虑可视化、数据存储这些增量需求。希望帮到你照着上面的顺序一步步来你也能把这套应用跑起来并在自己的场景里改出一个能用的版本。本文还有配套的精品资源点击获取
返回列表