简介:面向需要完成课程设计、期末大作业或毕业设计的计算机相关专业学生,这份Python项目基于深度学习的智能坐姿检测系统,提供可直接运行的源码与配套数据集;整个资源包共15个文件、约48KB,包含11个Python源码文件、2个数据文件、1个模型权重文件及1个提示音频文件,覆盖数据加载、模型训练、姿态识别、界面展示与简单演示等环节,目录结构清晰,便于按模块理解和二次开发。系统经过严格调试,解压后即可运行,适合用做人机交互、健康监测或课堂行为分析等方向的实验基础;项目内不仅包含训练与测试脚本,还提供预训练模型权重和示例音频,能快速验证效果并直观感受坐姿检测流程。资源在CSDN上已有1063人学习或浏览,属于同类课题中较受欢迎的参考案例。对于希望快速搭建毕设框架、节省前期数据准备时间的开发者,这套源码加数据集的组合提供了从模型到演示的完整闭环,是一个值得参考的起步模板。
1. 智能坐姿检测到底在检测什么:先搞清楚分类、关键点与“坐姿分数”的关系
工位摄像头前坐了一下午,脖子前倾、腰背悬空,这些姿势在监控画面里其实一眼就能看出来,但对深度学习模型来说却没那么简单。基于深度学习的智能坐姿检测,本质上是解决一个问题:如何用视觉模型连续判断“一个人当前坐得对不对”,并把这个判断输出成可被业务使用的信号。你拿到的这套 Python 实现,走的是图像分类路线——把坐姿归成“端正、前倾、左靠、右靠”等几类,再配合一轮关键点检测计算角度,输出一个坐姿分数。它不依赖任何可穿戴设备,一个普通 USB 摄像头就够,适合做学习工位提醒、网课坐姿分析、康复训练辅助这类场景。这篇笔记会把方案拆开,讲清选型理由、数据组织、训练参数和落地时的坑,照着做,一套最小系统两三天就能跑起来。
2. 技术选型:为什么坐姿检测优先用图像分类而不是姿态估计
2.1 先做分类再做关键点:从标注成本和验收标准倒推
常见做法是先用图像分类把“坐姿类型”定下来,再做关键点检测去计算角度,而不是一上来就人体姿态估计。原因很实际:姿态估计要标 17 个关键点甚至更多,每张图标注成本高不说,坐姿场景下大量关键点互相遮挡——侧坐时另一侧的肩、肘基本看不见,标错一个点,算出的角度就偏差十几度。分类则只需要给整张图贴一个标签,弱监督也能学出“头的位置、背的曲度”这种隐性特征,标注成本低一个量级,换新场景时重新标一批图也快。
从验收标准倒推也支持这个选择。大部分坐姿检测需求要的不是“肩关节旋转了多少度”,而是“当前是否驼背、是否需要提醒”。分类输出天然匹配这个需求,阈值调起来也直观。关键点计算的角度更适合做“坐姿分数”这种连续量,作为第二层输出,把分类结果和角度融合给出提醒。
我一般会把整个系统拆成两层:第一层是坐姿分类器(主干模型),第二层是关键点检测(辅助模型)。标题里这套源码的核心也是这个思路:分类器决定“是哪一类坐姿”,关键点决定“偏离了多少”。两个模型独立训练,比端到端多任务好调试——分类器翻车时不会拖累关键点模型。
2.2 模型选型:ResNet18 还是 MobileNetV3
坐姿检测的输入是摄像头画面,不是 ImageNet 那种物体居中、背景干净的标准图。工位背景里可能有显示器、水杯、同事走动,这些干扰让模型必须有一定的容量去学“人本身的姿态”,而不是背景特征。ResNet18 在这个数据量级上表现稳定,迁移学习效果也好,适合先跑通流程。
如果目标机器是树莓派、Jetson Nano 或老电脑,MobileNetV3 是更现实的选择。它用深度可分离卷积把参数量压到很低,推理速度比 ResNet18 快 2-3 倍,代价是精度略降。实际项目里我会两个都跑一遍:先用 ResNet18 确认数据质量,再切 MobileNetV3 验证速度,最终部署版本通常取后者。
还有一个容易忽略的点:输入分辨率。坐姿检测关注的是“上半身的轮廓”,不是“看清屏幕上的字”,输入分辨率设 224×224 已经足够。盲目上 448×448,训练时间和显存翻倍,精度提升却有限,因为坐姿类别之间的差异主要来自躯干角度,低频信息占主导。
2.3 数据划分与标签体系:坐姿分类的“分类边界”要先定死
坐姿不是一个严格的科学概念,“坐直”和“略微前倾”之间的界限是模糊的。所以标签体系必须按业务口径来定,而不是按物理角度来定。常见做法是四分类加一分类的背景:
| 标签 | 定义 | 典型表现 |
|---|---|---|
| upright | 端正坐姿 | 头肩在一条垂直线附近,背靠椅背 |
| lean_forward | 前倾 | 头部明显超出膝盖垂直线,背部弯曲 |
| lean_left | 左侧倾 | 肩膀连线明显倾斜,重心偏左 |
| lean_right | 右侧倾 | 肩膀连线明显倾斜,重心偏右 |
| background | 非坐姿 | 无人、站立、转身、遮挡严重 |
背景类非常重要。实际运行中摄像头经常拍到空椅子或人站起来的瞬间,如果没有背景类,模型会强行把这几类归到某个坐姿里,造成误报。数据切分上,我习惯按“人”来划分而不是按“帧”来划分——同一个人连续拍摄的几千帧画面如果既在训练集又在验证集,验证集准确率会虚高很多。按人划分,训练集、验证集、测试集各占 70%、15%、15%,能真实反映模型在陌生人身上的表现。
3. 数据准备:构建坐姿数据集的三步走与标注规范
3.1 采集与目录组织:从手机拍摄到监控视角的统一
坐姿数据的来源通常有两类:一是直接用公开坐姿数据集做预训练,二是自己用摄像头采集业务场景数据。两类数据混用时,最常翻车的点是视角不一致——公开数据多是正侧面视角,而实际部署的摄像头是斜上方 30 度俯拍。如果只拿公开数据训练后直接上线,模型会在真实视角下掉点。建议采集时覆盖三个视角:正前方、斜上方 30 度、左右侧 45 度,每个视角下每类坐姿采集 300-500 张。
目录组织按 PyTorch 的 ImageFolder 约定来,后续用 torchvision.datasets.ImageFolder 直接加载,省去写自定义 Dataset 的麻烦。脚本如下,负责把原始素材按标签分类拷贝到目标目录,并顺手按 7:2:1 划分训练集、验证集和测试集:
import os import shutil import random from pathlib import Path # 原始素材目录结构:raw/upright/*.jpg, raw/lean_forward/*.jpg ... # 目标目录结构:dataset/{train, val, test}/{upright, lean_forward, ...} RAW_ROOT = Path("raw") DST_ROOT = Path("dataset") SPLIT = {"train": 0.7, "val": 0.2, "test": 0.1} def split_and_copy(raw_root: Path, dst_root: Path): for label_dir in raw_root.iterdir(): if not label_dir.is_dir(): continue label = label_dir.name images = list(label_dir.glob("*.jpg")) + list(label_dir.glob("*.png")) random.shuffle(images) # 按比例累积切分,保证每个标签内部的比例一致 n_train = int(len(images) * SPLIT["train"]) n_val = int(len(images) * SPLIT["val"]) parts = { "train": images[:n_train], "val": images[n_train:n_train + n_val], "test": images[n_train + n_val:], } for split_name, imgs in parts.items(): out_dir = dst_root / split_name / label out_dir.mkdir(parents=True, exist_ok=True) for img in imgs: shutil.copy2(img, out_dir / img.name) if __name__ == "__main__": random.seed(42) split_and_copy(RAW_ROOT, DST_ROOT)这段脚本的核心是按“标签目录”读取并随机打乱,再按比例复制到训练、验证、测试目录。逻辑说明一下:shuffle必须在切分之前,否则前 700 张全是上午拍的、后 100 张全是下午拍的,时间分布会直接影响验证集效果。随机种子固定为 42,保证多次运行划分一致。copy2保留原文件的元信息,方便追溯素材来源。
采集时还要注意帧去重。视频抽帧得到的连续帧高度相似,如果不做间隔采样,模型等于反复看同一张图,泛化能力很弱。我一般每 10 帧抽 1 帧,或者用帧间差分跳过画面变化小于阈值的片段,这个步骤能显著减少数据冗余。
3.2 标注规范与歧义样本处理
标注是这套系统里最容易被低估的环节。四个坐姿类别之间的边界本来就模糊,“略微前倾”和“端正”的判断因人而异,如果多人协同标注,一致性会极差。我的做法是:先由一个经验最丰富的人标注第一版 500 张图,定下“边界样本”的参考标准,再让其他人参照这个标准标;标完做一轮交叉校验,抽样 20% 由第一人复核。
针对歧义样本,不要硬塞进某一类,而是单开一个ambiguous目录,训练时直接丢弃或在损失函数里降权。模型不需要学会区分“人类都分不清的坐姿”,强行学习只会让特征边界更混乱。实测中丢弃歧义样本后,验证集准确率反而提升 1-2 个百分点,因为模型的特征空间更简洁了。
另一个标注规范是:凡是画面中没有人脸、只有后脑勺或侧脸的样本,统一归入对应坐姿类,不要因为“看不到脸”就丢。实际工位摄像头大多在侧前方,后脑勺样本占了近一半,这部分恰恰是部署时的主力场景。
3.3 数据增强:让模型在真实光线下不翻车
坐姿检测增强策略和 ImageNet 分类不完全一样。ColorJitter 和 RandomAffine 是主力,RandomResizedCrop 反而要慎用——随机的裁剪比例太大时,会把“头”裁掉,模型被迫靠背景判断,学出错误特征。推荐组合如下:
from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2), transforms.RandomRotation(degrees=10, fill=(114, 114, 114)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) val_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ])参数说明:RandomCrop的裁剪范围是 256 到 224,相比直接 Resize 到 224,增加了轻微平移,能模拟摄像头安装位置的微小偏差。RandomRotation限制在 10 度以内,坐姿类别对角度敏感,超过 15 度的旋转会让“端正”变成“后仰”。ColorJitter 的亮度扰动系数调到 0.3,模拟早晚光线变化,但不要加 Hue 扰动——色相偏移会让肤色和衣服颜色失真,模型容易学到诡异的颜色特征。
验证集不做随机增强,只做 Resize 和归一化,保证评价指标可复现。验证集的分辨率必须和训练集最终输入一致,否则 acc 曲线会波动得没法看。
4. 用 PyTorch 训练坐姿分类模型:最小可复现代码与关键参数
4.1 从迁移学习到训练主循环:完整可跑的脚本
坐姿数据集规模通常在几千到几万张,从头训练 CNN 几乎不可能收敛,迁移学习是必选项。torchvision 自带的 ResNet18 在 ImageNet 上预训练过,把最后一层全连接替换成 5 类输出,冻结前面几层只微调后面几层,训练速度快且不容易过拟合。完整的最小训练脚本如下:
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, models, transforms from torch.optim import lr_scheduler import time import copy # ------------------ 1. 数据加载 ------------------ train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop((224, 224)), 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]), ]) train_dataset = datasets.ImageFolder("dataset/train", transform=train_transform) val_dataset = datasets.ImageFolder("dataset/val", transform=train_transform) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4) # ------------------ 2. 模型构建 ------------------ model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) num_features = model.fc.in_features model.fc = nn.Linear(num_features, 5) # 5类:4个坐姿 + 1个背景 # 冻结前三个 Block,只微调最后两个 Block 和 fc 层 for name, param in model.named_parameters(): if "layer4" in name or "fc" in name or "layer3" in name: param.requires_grad = True else: param.requires_grad = False # ------------------ 3. 训练配置 ------------------ device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4, weight_decay=1e-4) scheduler = lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.7) # ------------------ 4. 训练循环 ------------------ best_acc = 0.0 for epoch in range(20): model.train() running_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) scheduler.step() # 验证 model.eval() correct = 0 total = 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() val_acc = correct / total print(f"epoch {epoch+1}, loss {running_loss/len(train_dataset):.4f}, val_acc {val_acc:.4f}") if val_acc > best_acc: best_acc = val_acc best_model = copy.deepcopy(model.state_dict()) torch.save(best_model, "best_pose_model.pth")训练逻辑说明:这里只解冻了 layer3、layer4 和 fc 层,前面三个 Block 的参数不再更新。原因是 ImageNet 预训练模型的前几层学到的是边缘、纹理等通用特征,这些特征在坐姿数据集上依然有效;解冻太多层,小数据集上很容易灾难性过拟合。fc 层是随机初始化的,必须用较小的学习率 1e-4,如果和预训练层用同一个 1e-3 学习率,随机初始化的 fc 层梯度会把整个模型带偏。
关键参数说明:batch_size=32是多数 8GB 显存显卡的舒适区间。weight_decay=1e-4做 L2 正则化,坐姿数据量不大,不加正则化 fc 层很快会记住训练集特征。StepLR每 5 个 epoch 把学习率乘以 0.7,让损失在平台期继续下降。copy.deepcopy保存的是验证集上最好的权重,不是最后一个 epoch 的权重——最后一个 epoch 往往已经开始过拟合,用验证集最优模型做推理才是常规操作。
训练 20 个 epoch 足够观察趋势。如果 loss 前 3 个 epoch 不降,先别调模型结构,检查数据加载是否正常——最常见的问题是ToTensor之前没有Resize,导致不同尺寸的图在一个 batch 里撑爆了张量维度。
4.2 从损失曲线到混淆矩阵:判断模型是否真的学会了
只看 val_acc 一个数字评估模型风险很大。一个常见的假象是:背景类占了 30% 的样本,模型把背景类全学对了,acc 就有 0.3 的保底,哪怕坐姿四类全错,acc 也有 0.3,看不出严重问题。所以必须用分类报告和混淆矩阵看每一类的表现:
from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds = [] all_labels = [] with torch.no_grad(): for inputs, labels in val_loader: inputs = inputs.to(device) outputs = model(inputs) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) # 打印每一类的 precision / recall / f1-score class_names = train_dataset.classes print(classification_report(all_labels, all_preds, target_names=class_names)) # 混淆矩阵,配合视觉检查错误样本 cm = confusion_matrix(all_labels, all_preds) np.set_printoptions(precision=2, suppress=True) print(cm)运行后重点看两类混淆:lean_forward和upright是否大量互相误判,lean_left和lean_right是否对称误判。如果左右混淆严重,先检查是不是 RandomCrop 把画面裁偏了——裁剪后人体中心偏移,模型看到的“左倾”和“右倾”特征被破坏。如果前倾和端正混淆,通常需要把这两类的样本量加大,或者调整竖直方向的裁剪比例。
不要跳过这一步直接进入调参。混淆矩阵能告诉你“是模型能力不足,还是数据标注本身不一致”。如果抽样看误判样本,发现标注员自己都把图标错了,那问题在数据不在模型。
4.3 类别不均衡与难样本挖掘
真实业务里“端正坐姿”占了 70% 以上,前倾、侧倾只占不到 30%,训练时模型会偏向多数类。常见的处理方式是给损失函数加权重,把少数类的 loss 放大。PyTorch 里直接在 CrossEntropyLoss 传权重即可:
class_counts = [5023, 1245, 830, 902, 2401] # 各类样本数,按 classes 顺序 total = sum(class_counts) weights = [total / (len(class_counts) * c) for c in class_counts] weights = torch.tensor(weights, dtype=torch.float32).to(device) criterion = nn.CrossEntropyLoss(weight=weights)权重计算逻辑是“总数 /(类别数 × 该类样本数)”:样本少的类别权重高,让模型更关注这些难类。但权重不宜过大,超过 5 会造成少数类过度拟合,表现为训练集 acc 高、验证集 acc 掉。如果加权重后效果不明显,更有效的做法是难样本挖掘:把验证集里预测错的图找出来,人工看一遍,把其中真正有代表性的难例加入训练集,比单纯调权重更直接。
5. 坐姿检测避坑指南:光线、过拟合与“玄学”问题排查
5.1 现象:训练集 acc 96%,验证集 acc 只有 62%
原因是最典型的过拟合。坐姿数据大多是同一个环境连续拍的,训练集和验证集背景高度相似但人物不同,模型记住了“穿红衣服的人坐直了”,而不是“头肩在一条直线”。
解决分三步:先降低模型容量或增强正则化,把 fc 层加 Dropout;再检查数据划分是否按人切分,切分时有没有把同一个人的画面同时放进训练和验证;最后把数据增强强度加上——特别是 ColorJitter 和 RandomHorizontalFlip。实测中 ResNet18 换成 ResNet34 并不能解决过拟合,数据增强和正确划分的作用远大于模型容量。
5.2 现象:模型在上午光线好的时候准,下午拉上窗帘就不行了
这是域漂移。坐姿分类器依赖的光照条件在训练数据里没有覆盖,下午的色温和亮度分布跟上午完全不同,模型输出概率分布明显发散。
解决方式是训练时把 ColorJitter 的亮度扰动加大到 0.4,同时收集一天内不同时间段的样本补充训练集。如果采集成本高,也可以用图像归一化减少光照影响,但效果有限。最靠谱的做法是把摄像头安装位置的曝光参数固定,关闭自动白平衡和自动曝光,让输入图像的光照分布尽量稳定——这是工程上的“偷懒”手段,但非常有效。
5.3 现象:模型分不清坐姿,却学会了“认人”
观察到的现象是:某个同事坐姿不规范但总被识别成端正,换个同事同样的坐姿就被正确识别。原因是训练数据里每个人的坐姿规律性太强——有人习惯性坐直,有人总是前倾,样本少时模型直接记住了人的身份。
解决方式是按人划分数据集,确保训练集里的人不会出现在验证集和测试集。采集时每个类别至少覆盖 5 个人,每个人在多个时段采集多段视频,模型才能学到“姿势”而不是“脸”。如果项目已经跑了一半才发现这个问题,唯一的后悔药是重新采集数据,没有捷径。
5.4 现象:推理帧率只有 8 FPS,达不到实时提醒的标准
原因大概率不是模型太大,而是输入尺寸没降下来。很多人从训练迁移到推理时,直接用 448×448 或 640×480 的原始画面送进 ResNet18,而训练时用的 224×224,推理输入尺寸不一致导致计算量暴增。
解决方式是把推理输入 Resize 到 224×224,同时用 OpenCV 的 DNN 模块或 ONNX Runtime 替代原始 PyTorch 推理。ONNX Runtime 在 CPU 上通常比 PyTorch 快 30%-50%,Python 侧只有几行代码直接替换model.eval()的推理入口。另外一个容量手段是换成 MobileNetV3-Small,精度掉 1-2 个点,帧率能翻倍。
注意:推理输入尺寸一定要和训练时的最终输入尺寸严格一致,否则归一化后的分布都变了,模型输出会不稳定。
6. 把模型跑起来:从 checkpoint 到实时推理的最小落地路径
6.1 导出 ONNX 并用 ONNX Runtime 推理
训练完的.pth权重只适合 PyTorch 环境推理,部署到生产环境最省事的方式是导出 ONNX,用 ONNX Runtime 加载运行。导出时把动态轴设成 batch,方便单帧推理和批量推理共用同一个模型:
import torch from torchvision import models # 加载训练好的权重 model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) model.fc = torch.nn.Linear(model.fc.in_features, 5) model.load_state_dict(torch.load("best_pose_model.pth", map_location="cpu")) model.eval() # 导出 ONNX,输入尺寸固定为 1x3x224x224 dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, "pose_model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, opset_version=12, )推理脚本用 ONNX Runtime 加载,核心代码是sess.run:
import cv2 import numpy as np import onnxruntime as ort # 加载 ONNX 模型,CPU 执行即可 sess = ort.InferenceSession("pose_model.onnx", providers=["CPUExecutionProvider"]) def preprocess(frame): # 保持与训练一致的预处理顺序 img = cv2.resize(frame, (224, 224)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 mean = np.array([0.485, 0.456, 0.406], dtype=np.float32) std = np.array([0.229, 0.224, 0.225], dtype=np.float32) img = (img - mean) / std img = np.transpose(img, (2, 0, 1)) # HWC -> CHW return np.expand_dims(img, axis=0).astype(np.float32) cap = cv2.VideoCapture(0) class_names = ["background", "upright", "lean_forward", "lean_left", "lean_right"] while True: ret, frame = cap.read() if not ret: break input_tensor = preprocess(frame) outputs = sess.run(["output"], {"input": input_tensor})[0] pred_idx = int(np.argmax(outputs[0])) conf = float(np.max(outputs[0])) label = class_names[pred_idx] if conf > 0.6 and label != "background": cv2.putText(frame, f"{label}: {conf:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow("pose", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break预处理逻辑说明:cvtColor把 BGR 转 RGB 再归一化,顺序不能反——ONNX 模型在 RGB 空间训练,如果直接拿 BGR 数据推理,预测概率会乱。这里的置信度阈值 0.6 是经验值,业务要求严就调高到 0.8,代价是更多帧被忽略;要求宽就调到 0.5,代价是误报变多。
6.2 坐姿分数:把分类概率变成连续反馈
分类结果适合做“是否提醒”的开关,但很多场景需要“坐姿好不好,变化趋势怎样”的连续量。我通常会把分类概率加权成一个坐姿分数:score = p_upright * 1.0 + p_background * 0.0 + p_lean * 0.3,然后做 10 帧滑动平均,曲线平滑后看起来就是一条“坐姿质量曲线”。
这个分数的好处是能直观反映用户在一小时内的坐姿变化趋势,而不是一次误报就弹窗。分数低于阈值的连续时长超过 30 秒才触发提醒,能过滤掉大量瞬时抖动。代码里只需要维护一个collections.deque(maxlen=10)存历史分数,每帧算完 append 后取平均即可。
提示:提醒策略要留“免打扰”时段,连续高频提醒会让用户直接关掉摄像头,系统就失去了意义。合理的策略是每 15 分钟最多提醒两次,或者只在坐姿分数持续低时才提醒。
我踩过最大的坑是模型训练时忽略背景类,导致空椅子被识别成“端正坐姿”,坐姿分数一直虚高。加上背景类后,空椅子场景的概率分布变成了“background”主导,分数曲线才恢复正常。这个教训让我后来做任何分类任务都先加一个“空场景”类。希望这些思路能帮你在自己的坐姿检测项目上少走几趟弯路。
本文还有配套的精品资源,点击获取