视觉 · 行为识别
1. 模块概述
- 主要功能:基于 STGCN(Spatial-Temporal Graph Convolutional Network)的骨架行为识别,输入连续 30 帧的人体关键点序列,输出动作类别概率。支持 7 类动作识别,包含跌倒检测(Fall Down)。通常与 YOLOv8-Pose 配合使用:先检测人体关键点,再将关键点序列送入 STGCN 进行动作分类。
- 规格或特性:
- 支持模型:STGCN(TSSTG)
- 输入:30 帧骨架序列(17 个 COCO 关键点 × 30 帧)
- 数据类型:fp32
- 输出:7 类动作概率
- 推理后端:ONNX Runtime + CPUExecutionProvider
- 接口形态:C++(
vision_service.h)、Python(spacemit_visionwheel:VisionServiceNative)
- 相关目录结构:
src/deploy/stgcn/
├── cpp/stgcn_action_recognizer.h # C++ 头文件
├── cpp/stgcn_action_recognizer.cpp # C++ 实现
└── python/stgcn_action_recognizer.py # Python 实现
applications/fall_detection/
├── config/fall_detection.yaml # 跌倒检测应用配置
├── config/stgcn.yaml # STGCN 模型配置
├── cpp/example_fall_detection.cpp # C++ 跌倒检测示例
├── python/example_fall_detection.py # Python 跌倒检测示例
└── scripts/download_models.sh # 模型下载脚本
2. 环境准备
前置条件
SDK 源码获取和基础编译环境配置统一参考 2.3-构建编译。完成 SDK 初始化后,回到本文继续执行"构建编译"。
后续命令默认在 spacemit_robot SDK 根目录执行。
构建编译
系统缺少依赖时先安装:
sudo apt install python3-spacemit-ort opencv-spacemit libeigen3-dev spacemit-onnxruntime libyaml-cpp-dev
在 SDK 根目录加载构建环境后编译视觉组件:
source build/envsetup.sh
cd components/model_zoo/vision
mm
SDK 集成构建会把跌倒检测应用 example_fall_detection 等示例程序安装到 output/staging/bin,加载 build/envsetup.sh 后可直接运行。
运行 Python 示例前,需先安装 spacemit_vision wheel:
cd components/model_zoo/vision
python3 -m pip install -U pybind11 build setuptools wheel
cmake -S . -B build && cmake --build build -j
python3 -m pip install --force-reinstall src/python/dist/*.whl
python3 -c "from spacemit_vision import VisionServiceNative; print('ok')"
模型权重默认存放路径为 ~/.cache/models/vision/stgcn/ 与 ~/.cache/models/vision/yolov8_pose/。须先手动执行下载脚本;缺失时程序会直接报错(Model file not found)。
3. 示例使用(从 0 跑通)
STGCN 行为识别通常通过跌倒检测应用(applications/fall_detection/)来演示,该应用组合了 YOLOv8-Pose 姿态估计与 STGCN 动作分类。
3.1 跌倒检测应用(Python)
前置:见 §2。
步骤 1:下载模型
cd components/model_zoo/vision
bash applications/fall_detection/scripts/download_models.sh
bash examples/yolov8_pose/scripts/download_models.sh
预期现象:STGCN 模型下载至 ~/.cache/models/vision/stgcn/stgcn.fp32.onnx,YOLOv8-Pose 模型下载至 ~/.cache/models/vision/yolov8_pose/yolov8n-pose.q.onnx。
步骤 2:下载测试素材
bash scripts/download_assets.sh
预期现象:测试视频下载至 ~/.cache/assets/video/002_fall.mp4。
步骤 3:运行跌倒检测
python3 applications/fall_detection/python/example_fall_detection.py
3.2 跌倒检测应用(C++)
前置:见 §2,C++ 编译完成。
步骤 1:下载模型(同 §3.1 步骤 1)
步骤 2:运行跌倒检测
example_fall_detection applications/fall_detection/config/fall_detection.yaml
3.3 运行结果示例
终端输出示例:
Using test_video from "/home/user/spacemit_robot/components/model_zoo/vision/applications/fall_detection/config/fall_detection.yaml": /home/user/.cache/assets/video/002_fall.mp4
Start processing... press 'q' to quit.
Action/fall by STGCN (30-frame sequence), draw highest-score person only.
SpaceMIT EP initialized successfully!
可视化结果:

图中展示了视频帧中的人体姿态骨架和识别出的动作类别。
4. 应用开发
本章面向应用开发者,说明如何在自己的 C++ 或 Python 应用中集成行为识别组件。完整接口定义以 include/vision_service.h 和 src/python/spacemit_vision/vision_service_native.py 为准;本节介绍常用公开接口和典型调用方式。
4.1 接口说明
行为识别组件基于 STGCN(时空图卷积网络)模型,需要输入连续 30 帧的人体关键点序列进行动作分类。应用侧通常先使用姿态估计模型提取关键点,再送入 STGCN 进行行为识别。
4.1.1 常用数据结构
| 类型 | 说明 |
|---|---|
| vision::Action | 行为识别结果(在 namespace vision),包含动作类别 ID(label)、置信度(score)、全部类别概率(class_scores)。 |
| vision::Pose | 姿态估计结果(在 namespace vision),包含边界框(bbox)、置信度(score)、关键点(keypoints)。 |
| VisionServiceRequest | 统一推理输入,图像模型填 image;序列模型(如 STGCN)填 sequence_pts、sequence_count、sequence_width、sequence_height。 |
| VisionServiceResponse | 统一推理输出,results 为 vision::Result 变体列表(如 vision::Pose / vision::Action)。 |
4.1.2 服务初始化
C++ 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| VisionService::Create | 从 YAML 配置文件创建 STGCN 服务实例 | config_path:YAML 配置文件路径 | VisionService 智能指针 |
| GetClassNames | 获取所有动作类别名称(由模型配置的 label_file_path 加载) | 无 | 类别名称向量 |
跌倒类别索引(fall_down_index)不再由服务接口提供,而是作为应用策略从 fall detection 应用配置(fall_detection.yaml)的 fall_down_index 字段读取。
Python 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| VisionServiceNative.create | 从 YAML 创建 STGCN 服务 | config_path:YAML 路径 | VisionServiceNative 实例 |
| get_class_names | 获取动作类别名称列表 | 无 | 字符串列表 |
| get_fall_down_class_index | 读取 yaml 中的 fall_down_index(若配置) | 无 | int(未配置为 -1) |
4.1.3 行为识别
C++ 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| Infer(图像) | 对单帧图像(如姿态估计)进行推理 | image:cv::Mat;response:输出 VisionServiceResponse | VisionServiceStatus(== VISION_SERVICE_OK 表示成功) |
| Infer(序列) | 对 30 帧关键点序列进行动作分类 | request:VisionServiceRequest(填 sequence_pts/count/width/height);response:输出 VisionServiceResponse | VisionServiceStatus |
| Draw | 将推理结果绘制到图像(无状态,需显式传入 response) | image:输入图像;response:推理结果;out_image:输出图像 | VisionServiceStatus |
Python 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| infer_sequence | 对关键点序列进行动作分类 | pts:(t, 13, 3) float32 ndarray,像素坐标;image_width/height:原图宽高 | (VisionServiceStatus, class_scores 列表) |
| infer_image | 对单帧图像推理(姿态估计等) | image:BGR numpy 数组 | (VisionServiceStatus, results 列表) |
| last_error | 获取最近一次推理错误 | 无 | 错误描述字符串 |
4.1.4 性能监控
C++ 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| SetTimingOptions | 启用/禁用性能计时 | options:VisionServiceTimingOptions(enabled 字段) | void |
| GetLastTiming | 获取最近一次推理的各阶段耗时 | 无 | VisionServiceTiming 结构体(含 sequence_ms、model_infer_ms、infer_ms 等字段) |
4.2 典型调用流程
4.2.1 C++ 跌倒检测应用
#include "vision_service.h"
#include <opencv2/opencv.hpp>
#include <yaml-cpp/yaml.h>
#include <deque>
#include <variant>
int main() {
// 1. 加载应用配置(fall_down_index 是应用策略,存在应用配置中,非模型属性)
YAML::Node app_cfg = YAML::LoadFile("applications/fall_detection/config/fall_detection.yaml");
const int fall_down_index = app_cfg["fall_down_index"].as<int>(); // 6
// 2. 创建姿态估计服务与 STGCN 服务
auto pose_service = VisionService::Create("examples/yolov8_pose/config/yolov8_pose.yaml");
auto stgcn_service = VisionService::Create("applications/fall_detection/config/stgcn.yaml");
// 3. 获取动作类别名称(模型属性,由 label_file_path 加载)
const auto class_names = stgcn_service->GetClassNames();
// 4. 关键点序列缓冲区(30 帧 × 13 点 × 3 通道)
std::deque<std::vector<float>> keypoint_buffer;
cv::VideoCapture cap("video.mp4");
cv::Mat frame;
while (cap.read(frame)) {
// 5. 姿态推理
VisionServiceResponse pose_response;
if (pose_service->Infer(frame, &pose_response) != VISION_SERVICE_OK) continue;
if (!pose_response.results.empty()) {
// 6. 选得分最高的人
int best_idx = 0;
for (size_t i = 1; i < pose_response.results.size(); ++i) {
if (vision::get_score(pose_response.results[i]) >
vision::get_score(pose_response.results[best_idx]))
best_idx = i;
}
const vision::Pose* cr = std::get_if<vision::Pose>(&pose_response.results[best_idx]);
if (cr && cr->keypoints.size() >= 17) {
// 映射 COCO-17 -> TSSTG-13,构造单帧 float 数组(13×3)
static constexpr int MAP13[] = {0,5,6,7,8,9,10,11,12,1,2,3,4};
std::vector<float> frame_pts(13 * 3);
for (int i = 0; i < 13; ++i) {
const auto& kp = cr->keypoints[MAP13[i]];
frame_pts[i*3+0] = kp.x;
frame_pts[i*3+1] = kp.y;
frame_pts[i*3+2] = kp.visibility;
}
keypoint_buffer.push_back(frame_pts);
if (keypoint_buffer.size() > 30) keypoint_buffer.pop_front();
}
}
// 7. 累积满 30 帧后进行 STGCN 序列推理
if (keypoint_buffer.size() == 30) {
// 展平为连续 float 数组
std::vector<float> pts;
pts.reserve(30 * 13 * 3);
for (const auto& f : keypoint_buffer)
pts.insert(pts.end(), f.begin(), f.end());
VisionServiceRequest seq_req;
seq_req.sequence_pts = pts.data();
seq_req.sequence_count = static_cast<int>(pts.size());
seq_req.sequence_width = frame.cols;
seq_req.sequence_height = frame.rows;
VisionServiceResponse seq_resp;
if (stgcn_service->Infer(seq_req, &seq_resp) == VISION_SERVICE_OK
&& !seq_resp.results.empty()) {
const vision::Action* act =
std::get_if<vision::Action>(&seq_resp.results[0]);
if (act && static_cast<int>(act->class_scores.size()) > fall_down_index) {
int pred = static_cast<int>(
std::max_element(act->class_scores.begin(),
act->class_scores.end())
- act->class_scores.begin());
float fall_prob = act->class_scores[fall_down_index];
std::string action = (pred < static_cast<int>(class_names.size()))
? class_names[pred] : std::to_string(pred);
if (pred == fall_down_index)
std::cout << "Fall detected! action=" << action
<< " score=" << fall_prob << "\n";
}
}
}
cv::imshow("Fall Detection", frame);
if (cv::waitKey(1) == 'q') break;
}
return 0;
}
4.2.2 Python 跌倒检测应用
import yaml
import cv2
import numpy as np
from collections import deque
from pathlib import Path
from spacemit_vision import VisionServiceNative, VisionServiceStatus
COCO17_TO_TSSTG13 = [0, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4]
STGCN_LEN = 30
with open("applications/fall_detection/config/fall_detection.yaml") as f:
app_cfg = yaml.safe_load(f)
fall_down_index = int(app_cfg["fall_down_index"])
config_dir = Path("applications/fall_detection/config")
pose_svc = VisionServiceNative.create(str(config_dir / app_cfg["pose_model"]))
stgcn_svc = VisionServiceNative.create(str(config_dir / app_cfg["stgcn_model"]))
class_names = stgcn_svc.get_class_names()
keypoint_buffer = deque(maxlen=STGCN_LEN)
cap = cv2.VideoCapture("video.mp4")
while True:
ret, frame = cap.read()
if not ret:
break
h, w = frame.shape[:2]
status, results = pose_svc.infer_image(frame)
if status == VisionServiceStatus.OK and results:
best = max(results, key=lambda r: r.score)
if len(best.keypoints) >= 17:
kps = [(kp.x, kp.y, kp.visibility) for kp in best.keypoints]
keypoint_buffer.append(kps)
if len(keypoint_buffer) == STGCN_LEN:
arr = np.array(keypoint_buffer, dtype=np.float32)
pts = arr[:, COCO17_TO_TSSTG13, :].copy()
if pts[:, :, :2].max() <= 1.0 + 1e-5:
pts[:, :, 0] *= w
pts[:, :, 1] *= h
st, scores = stgcn_svc.infer_sequence(pts, w, h)
if st == VisionServiceStatus.OK:
pred = int(np.argmax(scores))
if pred == fall_down_index:
print(f"Fall detected! {class_names[pred]} P={scores[pred]:.4f}")
cv2.imshow("Fall Detection", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
4.3 配置说明
4.3.1 STGCN 模型配置
# STGCN 模型文件路径(fp32 格式)
model_path: ~/.cache/models/vision/stgcn/stgcn.fp32.onnx
# 部署类名(C++ 模型工厂注册名,Python 通过 yaml 路径间接使用)
class: deploy.stgcn.StgcnActionRecognizer
# 推理参数
default_params:
# 推理线程数(STGCN 为 CPU fp32,示例默认 4)
num_threads: 4
# 是否延迟加载模型
lazy_load: false
# 推理后端(STGCN 使用 CPU,因为是 fp32 格式)
providers:
- CPUExecutionProvider
4.3.2 跌倒检测应用配置
# 姿态估计模型配置(相对于本配置文件目录)
pose_model: pose.yaml
# STGCN 模型配置(相对于本配置文件目录)
stgcn_model: stgcn.yaml
# 测试视频路径
test_video: ~/.cache/assets/video/002_fall.mp4
# 关键点可见度阈值
kp_threshold: 0.3
# 等待帧数(累积多少帧后开始 STGCN 推理)
stgcn_wait_frames: 10
# 预测平滑窗口(用于减少误报)
stgcn_smooth_window: 5
# 摄像头设备号(--use-camera 时生效,对应 /dev/videoN 中的 N)
camera_id: 0
# 应用策略:哪个 STGCN 输出类别算作"跌倒"事件
# 这是本应用的业务决策,不属于模型属性
fall_down_index: 6
支持的动作类别(来自 assets/labels/stgcn.txt,共 7 类):
- 0: Standing(站立)
- 1: Walking(行走)
- 2: Sitting(坐着)
- 3: Lying Down(躺下)
- 4: Stand up(站起)
- 5: Sit down(坐下)
- 6: Fall Down(跌倒)
参数调优建议:
- fall_down_index:应用策略字段,指定哪个 STGCN 输出类别为跌倒事件(默认 6,与
assets/labels/stgcn.txt的 "Fall Down" 行对应)。存在 fall detection 应用配置中,不是模型属性。 - stgcn_wait_frames:控制开始推理的延迟,增大可减少初始误报。
- stgcn_smooth_window:平滑窗口大小,增大可减少误报但会增加延迟。
- kp_threshold:关键点可见度阈值,过低会引入噪声关键点。
- providers:STGCN 使用 CPUExecutionProvider,因为模型为 fp32 格式。
- num_threads(STGCN):STGCN 示例 yaml 默认为 4(CPU 推理)。前端姿态估计(YOLOv8-Pose)使用 SpaceMIT EP,默认 8,intra-op 最大建议 8。
4.4 性能监控
通过启用性能计时,可以分析推理各阶段的耗时,用于性能优化和瓶颈定位。
C++ 示例:
VisionServiceTimingOptions timing_opts;
timing_opts.enabled = true;
stgcn_service->SetTimingOptions(timing_opts);
stgcn_service->Infer(seq_req, &seq_resp);
auto timing = stgcn_service->GetLastTiming();
std::cout << "Sequence inference: " << timing.sequence_ms << " ms" << std::endl;
std::cout << "Model infer: " << timing.model_infer_ms << " ms" << std::endl;
std::cout << "Total infer: " << timing.infer_ms << " ms" << std::endl;
性能优化建议:
- STGCN 推理耗时相对较低(单次推理 < 50ms),整体流水线性能主要受前端姿态检测影响。
- 姿态检测是瓶颈:优化姿态检测模型(使用 yolov8n-pose 而非 yolov8m-pose)。
- 减少推理频率:不必每帧都进行 STGCN 推理,可每隔几帧推理一次。
参考 demo 路径:applications/fall_detection/
5. 调试指南
- STGCN 使用
CPUExecutionProvider(非 SpaceMIT),因为模型为 fp32 格式 - 启用计时:构造
VisionServiceTimingOptions o; o.enabled=true; service->SetTimingOptions(o);,推理后调用GetLastTiming()查看sequence_ms、model_infer_ms、infer_ms字段 fall_down_index在 fall detection 应用配置(fall_detection.yaml)中配置,不在 STGCN 模型配置中- 跌倒误报:调整
stgcn_smooth_window(增大可减少误报)和kp_threshold - 确认姿态检测正常工作后再排查 STGCN 问题
6. 常见问题
| 现象 | 可能原因 | 处理 |
|---|---|---|
Model file not found | STGCN 或 Pose 模型未下载 | 分别执行两个模型的下载脚本 |
No module named 'spacemit_vision' | wheel 未安装 | 按 §2 编译并 pip install src/python/dist/*.whl |
| 动作分类不准确 | 关键点检测质量差 | 确认 YOLOv8-Pose 检测正常,调整 kp_threshold |
| 跌倒检测延迟高 | 需要累积 30 帧 | 正常现象,STGCN 需要 30 帧序列输入 |
| 频繁误报跌倒 | 平滑窗口过小 | 增大 stgcn_smooth_window(如 7 或 9) |
| qt.qpa.xcb: could not connect to display | 没有接入显示器 | 接入显示器 |
附录:性能与测试数据
STGCN 模型未在 README 性能表中单独列出(fp32、CPU 推理)。跌倒检测流水线的前端姿态估计性能见 README.md 附录「不包含前后处理」:
| 具体模型 | 输入大小 | 数据类型 | 帧率 (4核) | 帧率 (8核) |
|---|---|---|---|---|
| yolov8n-pose | [1,3,640,640] | int8 | 61.1 | 88.9 |
| yolov8s-pose | [1,3,640,640] | int8 | 35.4 | 52.9 |
| yolov8m-pose | [1,3,640,640] | int8 | 19.3 | 29.3 |
含前后处理的端到端流水线数据见 README 附录「包含前后处理」。
复现方法(STGCN 模型,CPU):
onnxruntime_perf_test ~/.cache/models/vision/stgcn/stgcn.fp32.onnx -r 20 -x 1 -S 1 -s -I -c 1