Le2i跌倒检测数据集实战:从关键点提取到LSTM时序分类
2026/9/17 1:52:28 网站建设 项目流程

跌倒检测这几年一直是从智能安防到养老监护里比较刚需的方向。Le2i Fall Detection Dataset 是我见过被引用最多、也最适合新手入门的视频跌倒数据集之一。这篇文章就围绕它来写:怎么把数据集下载下来、怎么把视频预处理成能喂给模型的时间序列、怎么搭一个像样的分类器来训练,以及训练过程中最容易踩的坑。如果你最近正好在看 YOLO、关键点提取或者时序模型相关的话题,那这套流程其实是同一个思路。

数据集本身不算大,全部视频加起来一两百个片段,但足够让你把“视频数据 -> 特征 -> 模型 -> 评估”这条链路完整跑通。对新手来说,它的好处有三个:公开可下载、标注简单明确、场景相对可控。对老手来说,它也是一个很好的 benchmark,拿它做基线,再迁移到自己的监控场景里,改动成本很低。这篇教程我会尽量按实际操作的顺序来写,代码也都是可以直接跑的。

1. 先认识一下 Le2i Fall Detection Dataset

1.1 这个数据集到底是什么

Le2i 是法国一个实验室的缩写,全称是 Laboratoire Electronique, Informatique et Image,这个数据集由他们发布,最早用在论文里做室内跌倒检测。它包含几个不同室内场景的视频,常见版本有 Home(家庭)、Coffee Room(咖啡间)、Office(办公室)、Lecture Room(教室)等,每个场景都有多个人做动作,动作分为两类:一类是跌倒(Fall),一类是日常活动(Activity of Daily Living,ADL),比如坐下、起立、蹲下、走路、弯腰捡东西等等。注意这里的标注不是目标检测框,而是行为标签,这也是后续选择技术路线的重要依据。

每个视频的时长几秒到几十秒不等,分辨率通常在 320x240 左右,帧率一般标 25fps。整体数据量不算大,一共几十段到上百段视频,具体版本不同数量略有差异。这么小的数据量,直接拿它训练一个复杂的视频分类模型很容易过拟合,因此后面我会引导大家先用关键点抽特征,再喂给轻量级时序模型,这也是绝大多数论文的实际做法。

1.2 数据文件结构与标注格式

拿到压缩包解压之后,你会看到若干场景文件夹,每个文件夹里是若干 .avi 视频。我见过的多个版本里,标注信息有两种常见形式:一种是每个视频旁边放一个 .txt 的标注文件;另一种是单独一个表格文件汇总所有视频信息。不同版本字段命名不一样,但核心信息就两件事:这个视频里是否发生了跌倒,以及跌倒发生在哪几帧。

以常见的文本标注为例,可能有这样几行:

fall 25 72 adl 0 24 stand 73 120

含义大概是某个标签从第几帧到第几帧。也有版本直接标“视频名 + 起始帧 + 结束帧 + 类型”。拿到数据之后,我强烈建议第一步先打开标注文件看看字段名,不要想当然去写代码解析,否则对不齐数据会浪费一整天。

1.3 拿到数据后第一步的检查项

这里分享一个我在拿到任何公开数据集之后都会做的检查清单:

  • 视频是否能正常解码。用 OpenCV 读一遍,看有没有读不满帧、花屏、最终帧数为 0 的问题。
  • 标注文件行数是否和视频长度匹配。有的版本标注会多出几行,或者某些视频没有标注。
  • 区分场景信息。Le2i 数据集有多个场景,如果混在一起训练,模型可能学到的是“房间背景”而不是“人的动作”,后面我会专门讲怎么切分训练集和验证集。
  • 检查是否有多人同时出现在画面里。Le2i 有些视频里其实不止一个人,这会影响关键点提取,需要想好是取置信度最高的那个,还是干脆把这类样本剔除。

我第一次用时没检查视频解码,结果有个视频 OpenCV 读到一半直接崩了,训练到一半才发现,特别被动。建议你在预处理代码里加上 try-except 和日志输出,哪个视频出错直接跳过去,别把整个流程卡死。

2. 数据集的获取与预处理:从视频到关键点序列

2.1 数据集下载途径与版本差异

关于下载,这里说明两个点。第一,Le2i 的原始数据通常在项目主页或论文主页发布,部分版本需要填写学术用途声明。如果你在学校或者公司内网,直接搜“Le2i Fall Detection Dataset download”一般能找到官方入口;GitHub 上也有一些学术项目把数据做成了镜像,下载速度可能更快,但注意比对文件清单是否完整。第二,不同传播版本视频数量可能不同,有的版本把标注合并成了一个 Excel,有的则放在每个场景文件夹里。我建议优先选择带原始 .avi 文件的版本,因为很多搬运版本为了压缩体积转成了 .mp4,虽然也能用,但帧率、编码格式可能和标注错位。

下载完先解压,看一眼文件夹结构,记住视频和标注的存放位置,后面预处理代码要按这个路径来写。这一步简单,但很多人会忽略“版本一致性问题”,后期训练集和标注对不上就会非常头疼。如果你发现自己手里的标注是 Excel 格式,建议先转成统一的 .txt 或 CSV,后面处理会省很多事。

2.2 抽帧与关键点提取:MediaPipe 和 OpenPose 怎么选

跌倒检测要建模的是“动作随时间的变化”,我们不能直接把一整段视频的所有原始像素塞进网络(除非你有很深的显存和很大的数据量),所以要先抽特征。最常用的做法是人体关键点序列。

目前主流的人体关键点提取工具有两个:OpenPose 和 MediaPipe。

  • OpenPose 的精度高,支持多人和手部、面部关键点,但安装依赖重、运行时间长,CPU 上速度很感人。
  • MediaPipe 由 Google 出品,安装一条命令搞定,CPU 也能跑,单人姿态估计的稳定性在 Le2i 这种室内场景里已经够用了。

我个人的建议是:如果你只是想快速跑通跌倒检测,先选 MediaPipe;如果你追求极致精度,愿意为训练和推理速度付出成本,再考虑 OpenPose。另外,如果你本身对 YOLOv8 更熟,也可以用 YOLOv8-pose 来做关键点提取,效果也更符合日常检测流程,但这里为了控制篇幅,我以 MediaPipe 为例。

下面是一段抽帧 + 提取关键点的示例代码,它会遍历指定目录下的所有视频,对每一帧提取 33 个关键点坐标和置信度,然后保存成 .npy 文件。注意我这里为了演示删掉了很多日志和异常处理,实际用的时候建议加上进度条和断点续传。

import cv2 import mediapipe as mp import numpy as np from pathlib import Path mp_pose = mp.solutions.pose pose = mp_pose.Pose(static_image_mode=False, model_complexity=1, min_detection_confidence=0.5) input_root = Path("Le2i/") output_root = Path("le2i_keypoints/") output_root.mkdir(exist_ok=True) for video_path in sorted(input_root.rglob("*.avi")): cap = cv2.VideoCapture(str(video_path)) if not cap.isOpened(): print(f"skip {video_path}") continue frames_kpts = [] while True: ret, frame = cap.read() if not ret: break frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = pose.process(frame_rgb) if results.pose_landmarks is None: # 没检测到人,用全零占位 frames_kpts.append(np.zeros((33, 3), dtype=np.float32)) continue kpts = [] for lm in results.pose_landmarks.landmark: kpts.append([lm.x, lm.y, lm.visibility]) frames_kpts.append(np.array(kpts, dtype=np.float32)) cap.release() # 保存为 (T, 33, 3) 的数组 arr = np.stack(frames_kpts, axis=0) out_path = output_root / (video_path.stem + ".npy") np.save(out_path, arr) print(f"{video_path.name}: {arr.shape}")

这段代码有三个值得注意的点。第一,no detection 时用全零占位,但后续训练时这些帧应该被特殊处理,比如直接丢弃或通过插值补上,不能让全零帧混进关键点序列里当有效信息。第二,MediaPipe 的坐标是归一化到 [0,1] 的,分辨率变化不影响这个值,但对绝对位置敏感,后期做中心化归一化时要注意。第三,用 model_complexity=1 在 CPU 上速度还能接受,如果你显存足够,可以用 2 提高精度。

2.3 关键点清洗与降噪处理

关键点提取完之后,数据里往往会有一些噪声和缺失帧,如果不处理就直接训练,模型会被这些异常点带偏。我常用的清洗手段有三个。

第一个是连续缺失帧的插值。如果某一帧或者连续几帧没检测到人,可以用前后有效帧做线性插值。这里有个前提:缺失帧不能太多,如果连续丢了几百帧,插出来的值也没什么意义,这种情况下建议整段删掉。插值代码可以用 pandas 的 interpolate,也可以自己写循环,逻辑不复杂。

第二个是坐标平滑。MediaPipe 在单帧上做姿态估计,时间维度上可能会有轻微抖动,比如手肘坐标跳来跳去。可以通过滑动窗口均值或者 Savitzky-Golay 滤波把这种高频抖动抹掉。窗口大小建议 3 到 5,太大反而会把真实的快速动作给平滑没了。跌倒过程本身是迅速下坠,过度平滑会让模型分不清“快速坐下”和“跌倒”。

第三个是坐标归一化。在 Le2i 里,摄像头位置固定,人物在房间不同位置,远近差异很大。如果我们直接拿原始像素坐标训练,模型会学习到“靠近画面左侧的人更容易跌倒”这种错误规律。所以,归一化非常关键。一般做法是取脖子和骨盆的中点作为原点,然后除以肩宽(左肩到右肩的欧氏距离)。这样不管人站在画面哪个位置、离镜头是远是近,关键点坐标都会被标准化到相似的空间。

def normalize_pose(kpts): # kpts: (33, 3) x, y, visibility left_shoulder = kpts[11] right_shoulder = kpts[12] left_hip = kpts[23] right_hip = kpts[24] center_shoulder = (left_shoulder[:2] + right_shoulder[:2]) / 2.0 scale = np.linalg.norm(left_shoulder[:2] - right_shoulder[:2]) + 1e-6 normalized = kpts.copy() normalized[:, 0] = (kpts[:, 0] - center_shoulder[0]) / scale normalized[:, 1] = (kpts[:, 1] - center_shoulder[1]) / scale return normalized

用脖子和肩膀作为归一化锚点,是因为上半身在跌倒过程里变化最明显;如果用骨盆做锚点,人在弯腰时骨盆位置也会移动,反而引入额外偏移。我自己的建议是:归一化做完后,随机抽几段序列可视化一下,确认不同人、不同位置的姿态在归一化后确实对齐了,再进入下一步。

2.4 构建带标签的训练样本

数据清洗完,下一步就是把连续的关键点序列变成“一段一段带标签的窗口”。为什么要切窗口?因为 LSTM 或者 TCN 这类时序模型需要固定长度的输入,而且整段视频太长,直接丢进去既慢又难收敛。

切窗口的时候,标注怎么对齐是个核心问题。假设一个视频里有跌倒事件,标注告诉你跌倒发生在第 25 到 72 帧。我们设定窗口长度 W=24,步长 S=6,那么从第 0 帧开始,依次取 [0:24]、[6:30]、[12:36]……只要这个窗口的区间和 [25, 72] 有重叠,就标记为正样本;完全没有重叠,就标记为负样本。

def build_dataset_from_npy(video_keypoints, annot_events, window=24, stride=6): T = len(video_keypoints) windows, labels = [], [] t = 0 while t + window <= T: label = 0 for ev in annot_events: if ev["label"] == "fall" and not (t + window - 1 < ev["start"] or t > ev["end"]): label = 1 break windows.append(video_keypoints[t: t + window]) labels.append(label) t += stride return np.array(windows), np.array(labels)

构建样本的时候,还有一个容易被忽视的点:训练集和验证集怎么划分。如果直接把所有窗口随机切分到训练集和验证集,同一个视频的相邻窗口会同时出现在两边,模型相当于已经“看过”验证集的内容,结果会虚高。正确的做法是先按视频编号划分,比如把 80% 的视频归入训练,20% 的视频归入验证,然后再各自切窗口。如果能按人物 ID 划分就更严格,不过 Le2i 并不总能提供人物 ID,所以按视频切分是更常见的做法。

3. 模型设计思路:为什么选择“关键点 + 时序分类”而不是纯粹的 YOLO

3.1 两条技术路线的对比

最近 YOLOv8 很火,很多人会问:能不能直接用 YOLO 做跌倒检测?其实可以,但容易走进死胡同。YOLO 是目标检测模型,输出的是“框”和“类别”。你可以在 Le2i 上重新训练 YOLO 检测 “person” 类别,再结合跟踪、框宽高比变化去判断有没有跌倒。这个方案有一个前提:你需要大量已经标注了人体框和跌倒状态的数据,而 Le2i 的标注不是框,是行为标签,所以纯 YOLO 路线在 Le2i 上其实很别扭。

相比之下,“关键点 + 时序分类”这条路线和 Le2i 的数据特性天然匹配。跌倒本质上是一个动态过程,人在跌倒前后,关键点轨迹有明确规律:重心先升高,然后快速下移,躯干从直立变成水平,最后倒地静止。关键点序列可以很好地表达这种规律,而且相比原始视频,输入维度小很多,模型也更容易学到动作本质,不容易被背景干扰。

我把两种方案放在表格里对比:

方案标注需求模型复杂度对背景鲁棒性推荐场景
YOLO 检测 + 规则后处理需要检测框标注,Le2i 需要自己标注中高中,容易受其他物体干扰有现成检测框数据
关键点 + LSTM/TCN直接复用 Le2i 行为标签较高,关键点天然去掉背景信息在本数据集快速实验

如果你确实想用 YOLO 思路做,也不是不行,但需要自己做两件事:一是把 Le2i 的视频标注成人体框,二是设计一套规则或者小分类器来判断“框的轨迹”是不是跌倒。第二步其实又绕回了时序分类问题,所以对初学者来说,直接走关键点路线会更顺。

3.2 时间窗口和多帧组合的设计

时序模型不是把一整段视频丢进去就好。Le2i 单个视频有几秒到几十秒,把整段作为一个样本,输入长度不定,处理起来很麻烦,而且动作类别比例也不均匀。更常规的做法是把序列切成固定长度的窗口,比如 24 帧,对应不到 1 秒(25fps)。一个完整的跌倒动作通常持续 1 到 2 秒,所以 24 帧窗口不算太长,但也足够看到“从站立到摔倒”的关键轨迹。如果你希望窗口覆盖完整跌倒动作,可以选 32 帧或 48 帧,但窗口越大会引入更多无关静态帧,模型容易偷懒学“静止状态”,反而不利于捕捉瞬间动作。

步长(stride)决定相邻窗口的重叠程度。步长越小,样本越多,训练越稳定,但相邻窗口高度相似,有信息冗余;步长太大又可能漏掉跌倒瞬间。我自己常用 W=24、S=6,这样大概重叠 75%,在一个 100 帧的视频里能切出 13 个左右的窗口,对数据增强也有帮助。你也可以考虑用随机裁剪的方法做数据增强,每个 epoch 对同一个视频随机切一个偏移量不同的窗口,效果比固定切窗更抗过拟合。

3.3 轻量级分类模型:LSTM 与 TCN

处理关键点序列,最经典的模型是 LSTM。LSTM 的优点是能自动建模时间依赖,但对长度很长的序列会慢,而且容易过拟合。Le2i 数据量小,所以 LSTM 隐藏层不宜太深,一般一层或两层,隐藏单元 64 到 128 就够。我在 PyTorch 里的实现大概这样:

import torch import torch.nn as nn class FallClassifier(nn.Module): def __init__(self, input_dim=66, hidden_dim=128, num_layers=2, num_classes=2, dropout=0.5): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True, dropout=dropout) self.head = nn.Sequential( nn.Linear(hidden_dim, 64), nn.ReLU(), nn.Dropout(dropout), nn.Linear(64, num_classes) ) def forward(self, x): # x: (B, T, input_dim) out, _ = self.lstm(x) # 取最后时刻的输出 out = out[:, -1, :] return self.head(out)

这里 input_dim 我写的是 66,意思是每个关键点只取 x 和 y 两个坐标,33 个关键点就是 66 维。你也可以把 visibility 置信度拼进去,变成 99 维,但我在实验里发现提升不明显,反而增加了过拟合风险,所以默认就只用 x 和 y。

除了 LSTM,TCN(Temporal Convolutional Network)这两年也很推荐。它本质是一维因果卷积,训练速度快,梯度稳定,在动作识别任务里经常能超过 LSTM。如果你愿意折腾,可以把 LSTM 换成 TCN,输入仍然是关键点序列,输出一样是分类。在 Le2i 这种小数据集上,我实测 TCN 往往比 LSTM 更容易收敛,而且不太容易出现 LSTM 那种“训练集很好、验证集崩掉”的过拟合情况。

3.4 损失函数与类别不平衡处理

Le2i 的正负样本比例不均衡。一个视频里可能只有几秒是跌倒,其余都是日常活动,切窗之后正样本数量通常只有负样本的 1/5 到 1/10。如果不做处理,模型会倾向于把所有样本都预测为“日常活动”,因为这样准确率也能很高,但跌倒检测里漏报是最危险的。

处理办法有三个,我建议组合使用:

  • 类别加权交叉熵。给正样本更高的权重,比如 class_weight = [1.0, 5.0]。
  • 负样本下采样。让每个 batch 里正负样本数量接近,比如正样本采样 50%,负样本采样 50%。
  • 使用 Focal Loss。它能让模型更关注难分类样本,对正负样本不平衡效果不错。

我用得最多的是“类别加权 + 下采样”组合,简单直接,也不容易引入额外超参数。Focal Loss 在小数据上反而容易因为超参数选择不当而抖动。如果你想知道自己的模型到底是不是被不平衡害了,可以打印每个 batch 的正负比例,一目了然。

4. 完整的训练流程实战

4.1 环境准备

动手之前先把环境装好。Le2i 数据量不大,CPU 也能跑,但如果你想快速迭代,建议还是用 GPU。下面是我常用的环境版本:

  • Python 3.9 或 3.10
  • PyTorch 2.x
  • opencv-python
  • mediapipe
  • scikit-learn
  • pandas
  • numpy

安装命令很简单:

pip install torch opencv-python mediapipe scikit-learn pandas numpy

如果你有 GPU,建议装对应 CUDA 版本的 PyTorch;没有 GPU 也无所谓,这个数据集的单次训练时间在 CPU 上也就十几分钟到半小时。

4.2 模型核心代码

前面已经给了 LSTM 模型定义,这里补充 Dataset 和 DataLoader 的关键部分,以及训练循环。先把窗口数据封装成 PyTorch 的 Dataset:

from torch.utils.data import Dataset, DataLoader class FallsDataset(Dataset): def __init__(self, windows, labels): # windows: (N, T, 33, 3) self.windows = torch.FloatTensor(windows) self.labels = torch.LongTensor(labels) def __len__(self): return len(self.labels) def __getitem__(self, idx): x = self.windows[idx] # 只取 x, y 坐标,拉平成 (T, 66) x = x[:, :, :2].reshape(x.size(0), -1) return x, self.labels[idx]

训练循环我习惯写得直白一点,方便改东西:

from torch.utils.data import DataLoader train_dataset = FallsDataset(train_windows, train_labels) val_dataset = FallsDataset(val_windows, val_labels) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False) model = FallClassifier(input_dim=66) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) criterion = nn.CrossEntropyLoss(weight=torch.tensor([1.0, 5.0])) for epoch in range(30): model.train() total_loss = 0.0 for x, y in train_loader: optimizer.zero_grad() logits = model(x) loss = criterion(logits, y) loss.backward() optimizer.step() total_loss += loss.item() * x.size(0) scheduler.step() # 验证集上算准确率,简单看趋势 model.eval() correct = 0 total = 0 with torch.no_grad(): for x, y in val_loader: logits = model(x) preds = logits.argmax(dim=1) correct += (preds == y).sum().item() total += y.size(0) val_acc = correct / total print(f"epoch {epoch+1:02d} | loss {total_loss/len(train_dataset):.4f} | val_acc {val_acc:.4f}")

这段代码是能直接跑的。唯一要注意的是,我没有在训练循环里做类别下采样,如果你想用下采样策略,需要单独实现一个 WeightedRandomSampler 或者自定义 DataLoader,让每个 batch 里正负样本比例更接近。我实际跑的时候,光靠类别权重也能得到不错的效果,如果你后面发现正样本召回率特别低,再考虑上采样。

4.3 训练参数与调优记录

下面是我在 Le2i 上跑过的一组比较稳的参数,你可以直接参考:

参数数值说明
关键点数33MediaPipe Pose 的输出
窗口长度 W24约 0.96 秒(25fps)
步长 S6窗口重叠 75%
输入维度66只使用 x、y 坐标
LSTM 隐藏单元128单层也行,两层更稳
Dropout0.5防止小数据过拟合
Batch Size32尽量别太大,小数据下容易崩
学习率1e-3配合 CosineAnnealing
Epochs30~60多跑几轮,早停更稳
类别权重[1.0, 5.0]根据正负样本比例调整

在这个配置下,如果数据预处理和划分没出问题,验证集 F1 通常能做到 0.85 到 0.95。不同版本数据集结果会有差异,所以不用太纠结具体数值,重点看训练曲线是否平稳、验证集有没有突然塌掉。

4.4 评估指标怎么看

很多初学者在训练完只看准确率,这在类别不平衡的数据集上会骗人。比如负样本占 90%,模型全预测负样本,准确率也有 90%,但一个跌倒都没检测出来,这个模型毫无意义。所以我建议至少看三个东西:

  • 混淆矩阵。能直观看到哪些正样本被漏掉了,哪些负样本被误报成跌倒。
  • 召回率(Recall)。跌倒检测里最关键的是“漏报少”,宁可误报也别漏报,所以正类的召回率很重要。
  • F1 Score。在召回率和精确率之间取平衡。

如果你做的是帧级评估,那每一帧都算一个判断;如果你做的是事件级评估,那只要在一次跌倒事件的时间范围内,模型至少输出一次“跌倒”,就算这个事件被检测到了。后者更贴近真实监控场景。

下面是一个简单的评估代码:

from sklearn.metrics import confusion_matrix, classification_report y_true, y_pred = [], [] model.eval() with torch.no_grad(): for x, y in val_loader: logits = model(x) preds = logits.argmax(dim=1) y_true.extend(y.tolist()) y_pred.extend(preds.tolist()) print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred, target_names=["ADL", "Fall"], digits=4))

打印出来的 classification_report 里,Fall 那一行的 recall 和 f1-score 是你最需要盯住的两个数字。

5. 常见坑与排查技巧实录

5.1 容易踩的坑速查表

我把操作中遇到的典型问题整理成了一张表,方便你对照排查:

问题现象常见原因解决办法
MediaPipe 漏检关键点数组出现大量全零帧人物太小、背对镜头或运动过快放大输入帧,降低 min_detection_confidence,对缺失帧做插值
标注错位可视化时发现标注区间和画面动作对不上视频压缩转格式导致帧数变化用可视化脚本逐帧核对,重写标注映射逻辑
模型不收敛loss 基本不变,准确率接近多数类比例输入特征未归一化,或学习率过大先做坐标标准化,用少量样本试跑一个 batch
过拟合严重训练集 F1 很高,验证集很低小数据 + 模型参数多提高 dropout,改用 TCN,增加窗口重叠做数据增强
数据泄漏验证集分数虚高,换新视频后暴跌同一视频的窗口同时出现在 train/val按视频文件划分数据集,而不是按窗口随机划分

5.2 详细排查思路

先说 MediaPipe 漏检。Le2i 视频分辨率偏低,有些帧人物比较小,MediaPipe 确实会检测不到。你可以试两个方向:一是把输入帧先放大一些再送进 pose 模型;二是调低 min_detection_confidence 到 0.3 左右。如果连续十几帧都是全零,那基本上不是小问题,建议直接人工检查原视频,看是不是画面里根本没有完整的人。

再说标注错位。这个问题通常出现在你用非原始版本视频的时候。比如别人把 avi 转成 mp4 时丢了几帧,或者做了裁剪,帧号自然就对不上了。我的处理方式是写一个简单的可视化脚本,把标注区间画在原视频上,比如在跌倒区间给画面加一个红色边框。人眼扫一遍,马上就能发现问题出在哪。

最后说模型不收敛。如果你发现 loss 一直在很高的位置震荡,先别急着调学习率,把输入数据拿出来打印一下,看看有没有 NaN 或者超大值。我遇到过一次是因为归一化分母除到了接近 0,导致某些坐标值爆炸。另外,建议训练前先拿一个 batch 的数据做一次 forward + backward,确认流程能走通,再跑完整训练。

5.3 如何判断一个模型是真的学到了“跌倒”而不是“背景”

这个坑我特别想说。Le2i 场景固定为室内,如果模型只记住了“这个房间”的样子,换到另一个房间效果就会崩。所以训练时我建议做两件事。第一,训练集和验证集尽量来自不同场景。比如训练用 Home、Coffee Room,验证用 Office、Lecture Room,这样能测出模型是否具备跨场景泛化能力。第二,训练完之后写一个可视化脚本,把模型对每个窗口的预测结果叠加到原视频上。很多时候你以为模型学到了“跌倒规律”,其实它学的是“画面闪动”或“运动模糊”,不可视化你是发现不了的。

可视化脚本并不复杂,就是把预测的标签按帧画到视频上,然后保存成新的视频文件,用播放器慢放看几遍。我当时就发现模型对“快速弯腰捡东西”特别敏感,经常误判为跌倒,原因就是关键点轨迹和跌倒太像了。后来我在后处理里加了一个“倒地后静止时间”的条件,误报率立刻降下来不少。

6. 一点个人体会与扩展建议

我自己跑这个数据集时,最大的感触是数据预处理比模型设计要费心得多。如果你正在入门跌倒检测,建议先把“拿到视频 -> 抽关键点 -> 切窗 -> 训练 -> 可视化结果”这条链路完整跑通,再考虑换更强的模型。Le2i 尺寸虽小,但正因为小,你才有机会把每个环节都弄明白,而不是盲目堆数据。

最后再分享一个小技巧:训练完之后,可以把手头的数据扩展一下,比如在关键点序列里加入相邻帧的差分特征,也就是速度和加速度信息。跌倒最明显的特征是垂直方向的重心加速度变化,你不需要让模型凭空学会这一点,直接把加速度作为额外输入特征,模型会学得更快。我就是这样把验证集 F1 从 0.87 提到了 0.92。这个思路后续也可以迁移到自己的监控视频上,把关键点模型换成你更熟悉的 YOLOv8-pose,时序分类部分保持不变,基本就能形成一套自己的跌倒检测原型了。

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

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

立即咨询