跳到主要内容

视觉 · 行为识别

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_vision wheel: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!

可视化结果

STGCN 行为识别结果示例

图中展示了视频帧中的人体姿态骨架和识别出的动作类别。

4. 应用开发

本章面向应用开发者,说明如何在自己的 C++ 或 Python 应用中集成行为识别组件。完整接口定义以 include/vision_service.hsrc/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:输出 VisionServiceResponseVisionServiceStatus(== VISION_SERVICE_OK 表示成功)
Infer(序列)对 30 帧关键点序列进行动作分类request:VisionServiceRequest(填 sequence_pts/count/width/height);response:输出 VisionServiceResponseVisionServiceStatus
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_msmodel_infer_msinfer_ms 字段
  • fall_down_index 在 fall detection 应用配置(fall_detection.yaml)中配置,不在 STGCN 模型配置中
  • 跌倒误报:调整 stgcn_smooth_window(增大可减少误报)和 kp_threshold
  • 确认姿态检测正常工作后再排查 STGCN 问题

6. 常见问题

现象可能原因处理
Model file not foundSTGCN 或 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]int861.188.9
yolov8s-pose[1,3,640,640]int835.452.9
yolov8m-pose[1,3,640,640]int819.329.3

含前后处理的端到端流水线数据见 README 附录「包含前后处理」。

复现方法(STGCN 模型,CPU):

onnxruntime_perf_test ~/.cache/models/vision/stgcn/stgcn.fp32.onnx -r 20 -x 1 -S 1 -s -I -c 1