YOLOv11 + 大模型本地部署+界面展示 在智慧课堂行为识别项目
YOLOv11 在智慧课堂行为识别项目
目录
项目概述
本教程演示如何使用 YOLOv11 在智慧课堂场景下实现 学生行为识别(举手、聊天、饮水、写作、使用手机、空闲等 6 类)。项目结构位于工作区 D:\OpenClaw_Local\.openclaw_state\workspace\yolo11,核心代码包括:
train_action.py– 训练入口final.yaml– 数据集配置gpu.py、jiance.py– GPU 管理 & 检测脚本video_Clip.py、video_cut.py– 视频切片工具
目标:读者只需按照本文步骤即可完成从数据收集、标注、训练到推理的全流程。
一、环境准备
适用于 Windows 10/11(PowerShell)和 Linux(bash)
推荐使用 Anaconda 创建独立环境,以免与系统库冲突。
# 1. 创建并激活 conda 环境
conda create -n yolo11_env python=3.10 -y
conda activate yolo11_env
# 2. 安装核心依赖(ultralytics 包包含 YOLOv11)
pip install ultralytics==8.2.20 # 兼容 YOLOv11
pip install opencv-python tqdm pandas seaborn matplotlib
# 3. 验证 GPU(可选)
python -c "import torch; print('CUDA available:', torch.cuda.is_available())"
⚠️ 注意:如果机器无 GPU,训练会自动回落到 CPU,速度会显著下降。建议使用至少 6GB 显存 的显卡(如 RTX 3060)。
二、数据准备
数据来源:可参考中的数据集(参考文献),包括课堂录像、行为标注文件 (
.txt)、关键点标注 (.json)。
目录结构(参考final.yaml):
yolo11/
├─ D:/yolo11_test/
│ ├─ train_v2.txt # 训练图片路径列表
│ ├─ images/ # 所有图片/帧
│ └─ labels/ # YOLO 格式 txt 标注
├─ final.yaml # 数据集配置文件
2.1 数据集根目录
在 final.yaml 中已配置:
path: D:/yolo11_test/final_project # 数据集根目录
train: D:/yolo11_test/train_v2.txt # 训练集列表(相对路径)
val: images # 验证集(暂用与训练相同)
names:
0: focused
1: chatting
2: drinking
3: writing
4: phone
5: null
kpt_shape: [7, 3] # 7 个关键点,每点 (x, y, visibility)
skeleton: [[1,2],[0,1],[0,2],[3,1],[4,2],[5,0],[6,0]]
如何生成
train_v2.txt:将所有标注图片的相对路径写入每行,例如:
images/img_00001.jpg
images/img_00002.jpg
...
2.2 标注格式
- YOLO 检测标注:每行
class_id cx cy w h(相对坐标)。 - 关键点标注(姿态检测)保存在
labels/*.txt中,格式:
class_id x1 y1 v1 x2 y2 v2 ... x7 y7 v7
标注工具推荐:
labelImg(检测) +labelme(关键点),或使用cvat标注平台。
2.3 数据预处理脚本(可选)
仓库自带 auto_label.py,用于自动从视频中抽帧并生成 train_v2.txt。
python auto_label.py \
--video D:/raw_videos/classroom1.mp4 \
--output_dir D:/yolo11_test/images \
--interval 30 # 每 30 帧抽一帧
三、模型训练
下面演示使用 train_action.py(已封装的训练入口)进行训练。
3.1 关键配置(train_action.py)
from ultralytics import YOLO
if __name__ == '__main__':
# 加载预训练模型(YOLOv11 预训练权重)
model = YOLO(r"D:\yolo11_test\runs\pose\classroom_action_v1\weights\best.pt")
results = model.train(
data="final.yaml", # 数据集配置文件
epochs=150,
imgsz=640,
batch=8,
device=0, # GPU ID,若无 GPU 改为 "cpu"
name="classroom_v2_final", # 结果保存目录名
scale=0.9, # 数据增强:随机缩放
mosaic=1.0,
mixup=0.1,
optimizer="auto",
patience=100, # 早停策略
cache=False,
)
3.2 运行训练
# 进入项目根目录
cd D:\OpenClaw_Local\.openclaw_state\workspace\yolo11
# 执行训练脚本
python train_action.py
日志位置:
runs/pose/classroom_v2_final/目录下会生成weights/best.pt(最佳模型)以及训练日志results.csv、events.out.tfevents...。
3.3 训练期间常用参数解释
| 参数 | 含义 |
|---|---|
epochs | 训练轮数,默认 150。 |
batch | 每批样本数,显存足够时可调大。 |
scale | 随机缩放比例(0.9 表示 90%~110%)。 |
mosaic | Mosaic 增强,拼接 4 张图提升小目标检测。 |
mixup | MixUp 增强,混合两张图。 |
patience | 若验证集 mAP 连续 patience 次未提升则提前终止。 |
四、模型推理
训练完成后,可使用 jiance.py 对单张图片或视频进行行为检测。
4.1 单张图片推理
# jiance.py 示例
from ultralytics import YOLO
import cv2
model = YOLO(r"D:\OpenClaw_Local\.openclaw_state\workspace\yolo11\runs\pose\classroom_v2_final\weights\best.pt")
img = cv2.imread(r"D:\yolo11_test\images\sample1.jpg")
results = model(img, save=True, show=True) # 保存至 runs/detect/predict/
- 输出:
runs/detect/predict/中会生成带检测框和关键点的图片sample1.jpg。
4.2 视频推理(实时)
python jiance.py \
--source D:/raw_videos/classroom_test.mp4 \
--save True
jiance.py参数可参考文件头部注释(已在源码中注明)。
五、结果可视化与部署
5.1 结果可视化(Matplotlib)
可以使用 poseggjj.py 绘制关键点热力图。
import matplotlib.pyplot as plt
from poseggjj import draw_keypoints
img = cv2.imread('sample1.jpg')
kp = results[0].keypoints # (7,3)
draw_keypoints(img, kp)
plt.show()
5.2 pyqt6界面展示
下面给出一个完整的代码,供 CSDN 读者快速部署。
import sys
import os
import cv2
import csv
import numpy as np
import requests
import matplotlib.pyplot as plt
from datetime import datetime
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QFileDialog, QComboBox, QMessageBox, QFrame, QTextEdit
)
from PyQt6.QtGui import QImage, QPixmap
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from ultralytics import YOLO
# ==========================================
# 程序整体架构
#a.AI 逻辑线程:异步调用本地部署的 DeepSeek-R1 模型生成教学建议
#b.视觉处理线程:实时处理视频流并进行行为识别
#c.主程序界面:集成所有功能,提供用户交互界面
#
# ==========================================
# 模块一:AI 逻辑线程 (DeepSeek-R1-1.5B)
# ==========================================
class OllamaThread(QThread):
"""异步调用 Ollama,防止大模型推理时卡住 UI 界面"""
advice_ready = pyqtSignal(str) # 自定义信号,用于传递AI建议
def __init__(self, stats):
super().__init__()
self.stats = stats # 接收行为统计数据
def run(self):
url = "http://localhost:11434/api/generate"
# 针对 1.5B 模型优化的 Prompt
prompt = (
f"当前课堂统计:专注{self.stats['focused']}人,写字{self.stats['writing']}人,"
f"讨论{self.stats['chatting']}人,玩手机{self.stats['phone']}人。"
f"请以教育专家的身份,给老师一条 50 字以内的改进建议。"
)
try:
payload = {
"model": "deepseek-r1:1.5b", # 更换为适合 3050 的 1.5B 版本
"prompt": prompt,
"stream": False,
"options": {"num_ctx": 512} # 上下文长度512
}
# 发送HTTP POST请求到Ollama本地服务
response = requests.post(url, json=payload, timeout=30)
if response.status_code == 200:
full_text = response.json().get("response", "")
# 过滤掉思维链 <think> 标签
clean_text = full_text.split("</think>")[-1].strip() if "</think>" in full_text else full_text
self.advice_ready.emit(clean_text)
else:
self.advice_ready.emit("AI 服务忙碌,请稍后再试。")
except Exception as e:
self.advice_ready.emit(f"AI 连接异常: {str(e)}")
# ==========================================
# 模块二:视觉处理线程 (YOLOv11)
# ==========================================
class VideoThread(QThread):
"""高性能视频流处理线程"""
change_pixmap_signal = pyqtSignal(np.ndarray, dict) # 信号:传递处理后的帧和统计结果
def __init__(self, source=0, model=None):
super().__init__()
self.source = source # 视频源:0-摄像头,或文件路径
self.running = True # 线程运行控制标志
self.model = model # YOLO模型实例
def run(self):
cap = cv2.VideoCapture(self.source) # 打开视频源
while self.running:
ret, frame = cap.read() # 读取一帧
if not ret: break
# 执行推理
annotated_frame, counts = ClassroomApp.run_inference(frame, self.model)
# 发射信号,传递处理结果
self.change_pixmap_signal.emit(annotated_frame, counts)
cap.release() # 释放资源
def stop(self):
self.running = False
self.wait()
# ==========================================
# 模块三:主程序界面与核心逻辑
# ==========================================
class ClassroomApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("智慧课堂行为监测系统 - 毕业设计演示版")
self.setMinimumSize(1400, 900) # 设置最小窗口尺寸
# 初始化模型
self.model = YOLO(r"D:\yolo11_test\runs\pose\classroom_v2_final\weights\best.pt")
self.last_counts = {"focused": 0, "chatting": 0, "writing": 0, "phone": 0}
self.init_ui()
def init_ui(self):
"""UI 布局初始化"""
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QHBoxLayout(main_widget)
# --- 左侧:实时视频展示区 ---
left_layout = QVBoxLayout()
self.display_label = QLabel("等待接入视频源...")
self.display_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.display_label.setStyleSheet("background-color: #2c3e50; color: white; border-radius: 8px;")
left_layout.addWidget(self.display_label, stretch=10)
# 视频控制工具条
ctrl_frame = QFrame()
ctrl_layout = QHBoxLayout(ctrl_frame)
self.source_select = QComboBox()
self.source_select.addItems(["本地摄像头 (0)", "视频文件分析..."])
self.btn_start = QPushButton("📹 开始监测")
self.btn_stop = QPushButton("🛑 停止")
self.btn_test_img = QPushButton("🖼️ 图片测试")
for btn in [self.btn_start, self.btn_stop, self.btn_test_img]:
ctrl_layout.addWidget(btn)
ctrl_layout.insertWidget(0, self.source_select)
left_layout.addWidget(ctrl_frame)
# --- 右侧:数据看板与 AI 建议区 ---
right_layout = QVBoxLayout()
# 实时统计卡片
right_layout.addWidget(QLabel("📊 行为实时统计"))
self.stats_label = QLabel("等待数据...")
self.stats_label.setStyleSheet(
"font-size: 15px; color: #2c3e50; background: #ecf0f1; padding: 15px; border-radius: 5px;")
right_layout.addWidget(self.stats_label)
# AI 建议展示
right_layout.addSpacing(20)
right_layout.addWidget(QLabel("🤖 AI 专家实时分析"))
self.ai_box = QTextEdit()
self.ai_box.setReadOnly(True)
self.ai_box.setStyleSheet("font-size: 13px; color: #2c3e50; background: #fdfefe; border: 1px solid #dcdfe6;")
right_layout.addWidget(self.ai_box)
# 功能按钮区
self.btn_get_ai = QPushButton("🧠 获取 AI 分析建议")
self.btn_get_ai.setStyleSheet("background: #27ae60;")
self.btn_show_chart = QPushButton("📈 生成统计图表")
self.btn_show_chart.setStyleSheet("background: #8e44ad;")
right_layout.addWidget(self.btn_get_ai)
right_layout.addWidget(self.btn_show_chart)
main_layout.addLayout(left_layout, stretch=7)
main_layout.addLayout(right_layout, stretch=3)
# 信号绑定
self.btn_start.clicked.connect(self.start_video_logic)
self.btn_stop.clicked.connect(self.stop_video_logic)
self.btn_test_img.clicked.connect(self.test_single_image)
self.btn_get_ai.clicked.connect(self.request_ai_advice)
self.btn_show_chart.clicked.connect(self.show_analysis_chart)
# ==========================================
# 模块四:核心功能函数 (检测/存盘/绘图)
# ==========================================
@staticmethod
def run_inference(frame, model):
"""高精度推理:imgsz=1280 保证人全检出"""
results = model.predict(
source=frame,
imgsz=1280, # 大尺寸输入,提高小目标检测精度
conf=0.25, # 置信度阈值
iou=0.45, # NMS的IoU阈值
verbose=False # 不输出详细信息
)
# 统计四类行为数量
counts = {"focused": 0, "chatting": 0, "writing": 0, "phone": 0}
for box in results[0].boxes:
label = model.names[int(box.cls[0])] # 获取类别名称
if label in counts: counts[label] += 1
return results[0].plot(), counts # 返回标注后的图像和统计结果
def save_to_csv(self, stats, advice):
"""自动存档功能:为论文提供原始实验数据"""
file_path = "classroom_analysis_log.csv"
file_exists = os.path.isfile(file_path)
try:
with open(file_path, "a", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
if not file_exists: # 文件不存在时写入表头
writer.writerow(["时间", "总人数", "听课", "写字", "讨论", "手机", "AI建议"])
now = datetime.now().strftime("%H:%M:%S")
# 写入一行数据
writer.writerow([now, sum(stats.values()), stats['focused'],
stats['writing'], stats['chatting'], stats['phone'], advice.replace("\n", " ")])# 替换换行符,避免CSV格式错误
except Exception as e:
print(f"存档失败: {e}")
def show_analysis_chart(self):
"""数据可视化:弹窗展示当前课堂比例"""
labels = ['专注', '写字', '讨论', '手机']
sizes = [self.last_counts['focused'], self.last_counts['writing'],
self.last_counts['chatting'], self.last_counts['phone']]
if sum(sizes) == 0: return
plt.rcParams['font.sans-serif'] = ['SimHei'] # 解决中文乱码设置,SimHei字体解决matplotlib中文显示问题
plt.figure("课堂行为比例分析", figsize=(6, 5))
plt.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=140,
colors=['#3498db', '#2ecc71', '#f1c40f', '#e74c3c'])#蓝色-专注,绿色-写字,黄色-讨论,红色-手机
plt.title(f"当前课堂状态 (样本人数: {sum(sizes)})")
plt.show()
def update_display(self, frame, stats):
"""更新 UI 显示内容"""
self.last_counts = stats# 保存最新统计
# OpenCV图像转Qt图像
h, w, ch = frame.shape
q_img = QImage(frame.data, w, h, ch * w, QImage.Format.Format_BGR888)
# 缩放并显示
self.display_label.setPixmap(
QPixmap.fromImage(q_img).scaled(
self.display_label.size(),
Qt.AspectRatioMode.KeepAspectRatio
)
)
# 更新统计标签
total = sum(stats.values())
self.stats_label.setText(
f"<b>总人数: {total}</b><br>听课: {stats['focused']} | 写字: {stats['writing']}<br>讨论: {stats['chatting']} | 手机: {stats['phone']}")
def request_ai_advice(self):
"""获取 AI 建议并自动存档"""
self.ai_box.setText("AI 正在根据实时数据生成建议...")
self.btn_get_ai.setEnabled(False)
self.ai_thread = OllamaThread(self.last_counts)
self.ai_thread.advice_ready.connect(self.on_ai_ready)# AI线程信号连接
self.ai_thread.start()
def on_ai_ready(self, advice):
self.ai_box.setText(advice)
self.btn_get_ai.setEnabled(True)
self.save_to_csv(self.last_counts, advice) # 自动触发存档
# --- 视频与测试图片切换逻辑 ---
def start_video_logic(self):
source = 0
if self.source_select.currentIndex() == 1:
fname, _ = QFileDialog.getOpenFileName(self, "选择视频文件")
if fname:
source = fname
else:
return
self.video_thread = VideoThread(source, self.model)
self.video_thread.change_pixmap_signal.connect(self.update_display)# 视频线程信号连接
self.video_thread.start()
self.btn_start.setEnabled(False)
self.btn_stop.setEnabled(True)
def stop_video_logic(self):
if hasattr(self, 'video_thread') and self.video_thread.isRunning():
self.video_thread.stop()
self.btn_start.setEnabled(True)
self.btn_stop.setEnabled(False)
def test_single_image(self):
fname, _ = QFileDialog.getOpenFileName(self, '选择测试图片', '', 'Images (*.jpg *.png)')
if fname:
self.stop_video_logic()
img = cv2.imread(fname)
if img is not None:
res_img, counts = self.run_inference(img, self.model)
self.update_display(res_img, counts)
if __name__ == "__main__":
app = QApplication(sys.argv)
# 全局样式设置
app.setStyleSheet("QPushButton { height: 35px; border-radius: 5px; color: white; font-weight: bold; } "
"QPushButton#btn_start { background: #3498db; }")
window = ClassroomApp()
window.show()
sys.exit(app.exec())
运行:
python v_ui.py,上传视频或图片即可得到检测结果。
六、常见问题 & FAQ
| 问题 | 解决方案 |
|---|---|
| 显存不足 | 降低 batch(如 4)或 imgsz(如 416),开启 --cache=False |
| 训练卡在某一步 | 检查 final.yaml 中路径是否全部可访问;确认 train_v2.txt 中无空行 |
| 关键点不对齐 | 确保标注时使用相同分辨率;kpt_shape 必须保持 7×3 |
模型推理报错 torch.cuda.OutOfMemoryError | 加 device='cpu' 或使用 torch.cuda.empty_cache() 手动释放显存 |
| 保存路径为空 | model.train(..., save=True) 默认保存到 runs/pose/<name>/weights/,可在 results.save_dir 查看 |
七、参考资源
- YOLOv11 官方文档: https://github.com/ultralytics/ultralytics
- aistudio 飞桨:https://aistudio.baidu.com
- 姿态关键点标注工具:https://app.cvat.ai
- GitHub 示例仓库: https://github.com/ggjj-hub/yolo-classroom-headup-rate
🎉 完成
按照本指南,你可以快速完成:
- 环境搭建✅
- 数据准备✅
- 模型训练✅
- 推理与可视化✅
- 简易部署✅
如果还有细节需要补充(例如 数据集的具体标注说明),请直接在本文下方留言,我会进一步完善。祝你实验顺利,项目成功!
更多推荐


所有评论(0)