☰
基于Python的深度学习课堂行为图像识别分类项目实战
2026/9/28 5:04:52 网站建设 项目流程

简介:这份资源是面向高校学生与深度学习入门者的课堂行为图像识别分类项目源码,适用于毕业设计、期末大作业等场景,帮助读者快速获得一套可运行、可复现的完整方案。压缩包共1206个文件,以1183张jpg图像样本为主体,配合14个py源码文件、3张png图示、2份md说明文档及license等辅助文件,整体约100.29MB,数据与代码分层清晰,便于按模块查阅与二次训练。项目围绕课堂行为分类任务展开,涵盖数据组织、模型搭建与训练推理等环节,源码经过严格调试,评审分达到95分以上,可直接运行验证。目前已有571人学习下载,适合希望省去从零搭建数据与代码框架、专注模型调优与论文撰写的读者参考使用。

1. 从一张课堂照片说起:这套 Python 深度学习图像识别分类项目到底解决什么问题

上课铃响十分钟,后排趴桌、中间刷手机、前排抬头看黑板——如果让你用人工数一遍,一个 60 人的班至少要花五分钟,还容易漏。基于 Python 的深度学习课堂行为图像识别分类项目,要干的就是把这件事自动化:输入一张教室监控截图或一段视频抽帧,输出每个学生当前处于「听讲、举手、低头、趴桌、转头」中的哪一类。它属于典型的图像分类任务,只不过类别不是猫狗,而是课堂行为。

这套源码适合三类人:正在做课程设计、毕设的在校生,需要一份能跑通、能改参数的完整工程;想入门深度学习图像识别的开发者,需要一个比 MNIST 更贴近真实场景的练手项目;以及做智慧教室、教学行为分析的从业者,想先验证技术可行性再决定要不要投入。核心链路并不复杂——数据标注、模型训练、推理部署,难点在于课堂场景本身的光照变化、遮挡和类别不均衡。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲。

2. 课堂行为识别的技术选型:为什么是 CNN 而不是别的

2.1 图像分类任务的本质与 CNN 的适配性

图像分类的本质是学习一个从像素到类别的映射函数。一张 224×224 的 RGB 图片有 15 万个像素值,如果直接用全连接网络,参数量会爆炸且丢失空间信息。卷积神经网络(CNN)通过局部感受野、权值共享和池化操作,恰好匹配了图像的平移不变性和局部相关性。课堂行为识别里,「举手」这个动作无论出现在画面左上角还是右下角,特征应该被同样识别——这正是卷积核滑动扫描带来的能力。

常见做法是用迁移学习:拿在 ImageNet 上预训练好的骨干网络(ResNet18、MobileNetV3、EfficientNet-B0),替换最后的全连接层为课堂行为的类别数,再在自建数据集上微调。这样做的好处是,即使你的课堂数据集只有几千张,也能训出可用的模型。从零训练一个 CNN 在几千张图上几乎必然过拟合,这是血泪经验。

选骨干网络时看三个指标:参数量、推理延迟、精度。ResNet18 约 11M 参数,在 CPU 上单张推理约 30ms,适合课堂实时性要求不极端的场景;MobileNetV3-Small 只有 2.5M 参数,适合部署到边缘设备;EfficientNet-B0 精度更高但训练更吃显存。我一般先用 ResNet18 跑通全流程,再根据部署环境换骨干。

2.2 数据集的构建与标注规范

课堂行为数据集没有现成的公开大规模版本,必须自建。采集方式有两种:一是从教室监控录像按固定间隔抽帧,二是用手机在教室后排定点拍摄。抽帧间隔建议 1~2 秒一帧,太密会导致相邻帧高度相似、信息冗余,太疏会漏掉短时动作。

标注用 LabelImg 或 CVAT,每张图打一个主类别标签。类别定义要互斥且可判定,推荐五类:听讲(抬头看前方)、举手(手臂举起)、低头(看桌面/手机)、趴桌(头枕手臂)、转头(侧脸偏离前方)。标注时容易犯的错是边界模糊——「低头」和「趴桌」在侧视角下很难区分,解决办法是固定拍摄机位,并在标注规范里写明判定标准,比如「头部低于肩线且持续超过 2 秒记为趴桌」。

数据量方面,每个类别至少 500 张,五类合计 2500 张起步。如果某类样本明显偏少(比如举手在真实课堂中很少出现),需要用数据增强或过采样补齐。类别不均衡是课堂行为识别最隐蔽的坑,模型会倾向于预测多数类,表面准确率很高但少数类召回率极低。

2.3 训练环境搭建与依赖安装

环境用 Python 3.8~3.10 都行,推荐 3.9。深度学习框架选 PyTorch,理由是调试直观、社区资源多。如果你还在纠结 python 安装教程,建议直接用 Anaconda 建虚拟环境,避免污染系统 Python。

# 创建虚拟环境 conda create -n classroom python=3.9 -y conda activate classroom # 安装 PyTorch(以 CUDA 11.8 为例,无 GPU 则去掉 --index-url 那行) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装辅助库 pip install numpy pandas matplotlib opencv-python pillow scikit-learn tqdm tensorboard

这段命令做了三件事:建独立环境、装 PyTorch 及 torchvision、装数据处理和可视化库。参数说明:--index-url指定 PyTorch 官方 CUDA 版本源,如果你的显卡驱动只支持 CUDA 11.6,就去 PyTorch 官网查对应命令替换。opencv-python用于视频抽帧和推理时的图像预处理,tensorboard用于监控训练曲线。装完后用python -c "import torch; print(torch.cuda.is_available())"验证 GPU 是否可用,返回 False 说明 CUDA 版本不匹配或驱动太旧。

提示:不要用pip install torch裸装,默认会装 CPU 版,训练时速度差十倍以上。

3. 从源码到跑通:数据加载、模型训练与推理的完整链路

3.1 数据集目录结构与 DataLoader 配置

源码工程通常按以下结构组织,你拿到压缩包后先确认目录层级:

classroom_behavior/ ├── data/ │ ├── train/ │ │ ├── listen/ │ │ ├── hand_up/ │ │ ├── bow_head/ │ │ ├── lie_down/ │ │ └── turn_head/ │ ├── val/ │ │ └── (同上五类) │ └── test/ │ └── (同上五类) ├── models/ ├── utils/ ├── train.py ├── predict.py └── config.py

PyTorch 的ImageFolder要求每个类别一个子文件夹,文件夹名即类别名。训练集、验证集、测试集按 7:2:1 划分,划分时注意同一段视频的帧不能同时出现在训练集和验证集,否则验证精度会虚高——这是数据泄漏,很多人翻车在这里。

import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集增强:随机裁剪、翻转、颜色抖动 train_tf = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 验证/测试集只做缩放和归一化 val_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder('data/train', transform=train_tf) val_ds = datasets.ImageFolder('data/val', transform=val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4, pin_memory=True) print(train_ds.classes) # 输出类别列表,确认顺序

逻辑说明:训练集用RandomResizedCrop和RandomHorizontalFlip做增强,模拟课堂中不同距离和朝向的变化;ColorJitter应对教室灯光差异。归一化的均值和标准差是 ImageNet 的统计值,因为骨干网络是在 ImageNet 上预训练的,输入分布要一致。参数说明:batch_size=32在 8GB 显存下跑 ResNet18 比较稳,显存不够就降到 16;num_workers=4是数据加载线程数,Windows 下如果报错就改成 0;pin_memory=True加速 GPU 传输。

3.2 模型定义与迁移学习微调

import torch.nn as nn from torchvision import models def build_model(num_classes=5, backbone='resnet18', pretrained=True): if backbone == 'resnet18': model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT if pretrained else None) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) elif backbone == 'mobilenet_v3': model = models.mobilenet_v3_small(weights=models.MobileNet_V3_Small_Weights.DEFAULT if pretrained else None) in_features = model.classifier[-1].in_features model.classifier[-1] = nn.Linear(in_features, num_classes) return model model = build_model(num_classes=5, backbone='resnet18') device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device)

这里把 ResNet18 最后的全连接层替换成 Dropout + 新全连接,Dropout 率 0.3 是为了在小数据集上抑制过拟合。pretrained=True加载 ImageNet 预训练权重,这是迁移学习的关键。如果你用 MobileNetV3,注意它的分类头叫classifier而不是fc,改错地方会导致替换无效、训练不动。

3.3 训练循环与学习率调度

import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR from tqdm import tqdm criterion = nn.CrossEntropyLoss() optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=30) def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total = 0, 0, 0 for imgs, labels in tqdm(loader, desc='train'): imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * imgs.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += imgs.size(0) return total_loss / total, correct / total @torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total = 0, 0, 0 for imgs, labels in loader: imgs, labels = imgs.to(device), labels.to(device) outputs = model(imgs) loss = criterion(outputs, labels) total_loss += loss.item() * imgs.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += imgs.size(0) return total_loss / total, correct / total best_acc = 0 for epoch in range(30): tr_loss, tr_acc = train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc = evaluate(model, val_loader, criterion, device) scheduler.step() print(f'Epoch {epoch+1}: train_acc={tr_acc:.4f} val_acc={val_acc:.4f}') if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), 'models/best.pth')

逻辑说明:用 AdamW 优化器,它比 SGD 在微调场景下收敛更快;weight_decay=1e-4是 L2 正则,抑制过拟合。CosineAnnealingLR让学习率按余弦曲线从 1e-3 降到接近 0,前期大步走、后期小步调。每个 epoch 结束后在验证集上评估,只保存验证精度最高的权重,避免保存过拟合的最后一轮。参数说明:T_max=30对应总 epoch 数,如果你改成 50 轮,这里也要改;lr=1e-3是微调的常用起点,如果 loss 震荡厉害就降到 5e-4。

3.4 单张图片与视频流的推理

from PIL import Image import torch.nn.functional as F CLASSES = ['bow_head', 'hand_up', 'lie_down', 'listen', 'turn_head'] def predict_image(img_path, model, device): model.eval() img = Image.open(img_path).convert('RGB') tensor = val_tf(img).unsqueeze(0).to(device) with torch.no_grad(): logits = model(tensor) probs = F.softmax(logits, dim=1)[0] idx = probs.argmax().item() return CLASSES[idx], probs[idx].item() # 视频抽帧推理 import cv2 cap = cv2.VideoCapture('classroom.mp4') frame_interval = 30 # 每30帧取一帧 count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if count % frame_interval == 0: cv2.imwrite('tmp.jpg', frame) label, conf = predict_image('tmp.jpg', model, device) print(f'Frame {count}: {label} ({conf:.2f})') count += 1 cap.release()

推理时注意CLASSES的顺序必须和ImageFolder的classes一致,否则标签会错位。unsqueeze(0)是加 batch 维度。视频抽帧间隔根据实际需求调,课堂行为分析一般 1 秒一帧足够。如果要做多人检测,需要先接 YOLO 做人体框,再对每个框做行为分类,这套源码通常只做单图分类,多人场景要自己扩展。

4. 课堂行为识别落地时最容易翻车的五个地方

4.1 验证集精度 95%,实际部署一塌糊涂

现象:训练时验证集准确率很高,但拿新教室的图片测试,准确率掉到 60% 以下。原因:训练集和验证集来自同一段视频或同一机位,模型学到了背景特征而非行为特征。比如所有「举手」都出现在画面右侧,模型实际学的是「右侧=举手」。解决:划分数据集时按视频段或按机位划分,确保验证集的拍摄条件与训练集不同;同时增加背景随机裁剪增强,逼迫模型关注人体区域。

4.2 模型把所有样本都预测成「听讲」

现象:训练 loss 正常下降,但混淆矩阵显示「听讲」类的召回率接近 100%,其他类几乎为 0。原因:课堂中「听讲」样本天然占多数,类别不均衡导致模型退化为多数类预测器。解决:用加权交叉熵,权重设为类别频率的倒数;或者用 WeightedRandomSampler 在 DataLoader 层面过采样少数类。改完后看 macro-F1 而不是 accuracy。

4.3 训练到第 5 轮 loss 就变成 nan

现象:前几轮正常,突然 loss 变 nan,梯度爆炸。原因:学习率太大,或者数据里有损坏图片导致输入异常值。解决:先把学习率降到 1e-4 试;再用脚本遍历数据集,用 PIL 打开每张图,捕获异常文件并删除。另外加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)兜底。

4.4 GPU 显存够但训练速度极慢

现象:nvidia-smi 显示 GPU 利用率只有 20%,训练一个 epoch 要十几分钟。原因:数据加载是瓶颈,num_workers设太小或磁盘 IO 慢。解决:把num_workers调到 CPU 核数的一半,pin_memory=True,如果数据集在机械硬盘上就拷到 SSD。还可以预先把图片 resize 到 256×256 存成小图,减少解码开销。

4.5 推理时中文类别名显示乱码

现象:预测结果输出bow_head而不是「低头」,或者中文标签在终端显示为方块。原因:类别名用的是文件夹英文名,没有做映射;终端编码不是 UTF-8。解决:在代码里维护一个EN2CN字典做映射;Windows 终端执行chcp 65001切 UTF-8。这个不算技术难点,但交付给非技术用户时经常被问。

5. 把精度再往上推一档:类别不均衡下的 Focal Loss 与阈值调优

跑通全流程之后,真正拉开差距的是对少数类的处理。课堂行为识别里「举手」和「趴桌」的样本量往往只有「听讲」的十分之一,用普通交叉熵训出来的模型在这两类上召回率很难看。我一般会换 Focal Loss,它通过调制因子降低易分类样本的权重,让模型聚焦难样本。

import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alpha=None, gamma=2.0, reduction='mean'): super().__init__() self.alpha = alpha # 类别权重张量,形状 [num_classes] self.gamma = gamma self.reduction = reduction def forward(self, inputs, targets): ce = F.cross_entropy(inputs, targets, weight=self.alpha, reduction='none') pt = torch.exp(-ce) focal = ((1 - pt) ** self.gamma) * ce return focal.mean() if self.reduction == 'mean' else focal.sum() # 按类别频率设置 alpha,频率越低权重越高 class_counts = [500, 120, 480, 2000, 300] # 对应五个类别的样本数 total = sum(class_counts) alpha = torch.tensor([total / (5 * c) for c in class_counts], dtype=torch.float32).to(device) criterion = FocalLoss(alpha=alpha, gamma=2.0)

gamma=2.0是原论文的推荐值,越大越聚焦难样本,但太大会导致训练不稳定,一般 1.5~2.5 之间调。alpha按类别频率倒数归一化,让少数类的 loss 贡献更大。换 Focal Loss 后重点看 macro-F1 和少数类召回率,整体 accuracy 可能略降但实际可用性提升明显。

另一个技巧是推理阶段的阈值调优。默认取 softmax 最大值的类别,但在误判代价不对称的场景(比如把「趴桌」误判成「听讲」比反过来更严重),可以给特定类别加偏置。做法是在验证集上网格搜索每个类别的偏置值,最大化你关心的指标。这个操作不复杂但很有效,我通常能再榨出 2~3 个点的少数类召回。

最后说一个验证方法:不要只看一次划分的结果。用 5 折交叉验证,每次换不同的验证集划分,看指标的均值和方差。如果方差很大,说明模型对数据划分敏感,需要更多数据或更强的正则。这套流程走下来,一个可用的课堂行为识别模型大概能在五类任务上做到 85% 以上的 macro-F1,前提是数据质量和标注一致性过关。技术本身不玄学,翻车大多翻在数据和评估上。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询