坐姿检测算法

在 Quectel PI M2 Debian 系统上,基于 坐姿检测算法 SDK,从零搭建一个带桌面图形界面的坐姿检测项目。demo 通过 USB 摄像头实时采集画面,识别不良坐姿姿态。

界面布局:

  • 左侧摄像头预览
  • 右侧功能面板:
    CAMERA —— 摄像头选择
    DETECTION —— 检测开关
    LIVE RESULT —— 检测结果输出
    ACTION —— 参数设置与退出按钮

体验方式:

  • 接入USB 摄像头,实时识别画面中的坐姿姿态

算法能力

项目 说明
算法类型 人体关键点检测 + 姿态识别(单模型)
检测模型 PeopleKptDet(RKNN NPU,输入 640×640)
输入 BGR 连续内存
输出 每帧一个 PoseFrameResult(检测框 + 17 关键点 + 姿态类别 + 置信度)
识别姿态 未识别 UNKNOWN / 托腮 Cheek Support / 低头 Head Bowing / 高低肩 Unbalanced Shoulder
结果滤波 滚动窗口去抖(窗口 20 / 步长 10),抑制姿态频繁抖动

部署前准备

资源获取

名称 说明
PeoplePostureDet_RK3576_Ubuntu(算法 SDK 包) 坐姿检测算法包,含公开头、动态库、第三方运行时、模型与样例授权文件
posture_detection_project 本教程提供的示例工程,含 src 源码、CMakeLists 与三个脚本(安装工具链/编译/运行)

算法包目录

算法 SDK 包 PeoplePostureDet_RK3576_Ubuntu 的关键目录:

PeoplePostureDet_RK3576_Ubuntu/
├── include/                       # 公开头(PeopleKptDet.h)
├── lib/                           # 核心动态库 libPeopleKptDet.so
├── 3rd_party/                     # 第三方运行时(modelInfer / rknn / opencv)
├── models/                        # 模型资产(640×640 的 engine / rknn)
└── authorization.lic*             # 授权文件(安装时生成)

硬件/系统要求

项目 要求
主控 Quectel PI M2
系统 Debian 13(aarch64,带桌面环境,Wayland 或 X11)
外设 USB UVC 摄像头(实时识别时需要)、显示器、键鼠
连接 PC 可通过 SSH / adb shell 进入设备

软件要求

Demo 在 Quectel PI M2 上本地编译,需确认以下软件已安装:

g++ --version              # C++17 编译器
cmake --version            # 构建系统
ninja --version            # 构建工具
pkg-config --exists Qt6Widgets && echo "Qt6 OK"   # 显示用 Qt6
v4l2-ctl --version         # 用于枚举和排查 USB 摄像头节点

环境安装可一键执行:sudo bash scripts/setup_toolchain.sh

界面方案说明:显示层采用系统 Qt 6(Widgets),运行时可全屏;摄像头采集使用系统 OpenCV(V4L2)与算法包自带的 OpenCV world 单库相隔离,避免版本冲突。

工程结构

posture_demo/                      # 项目根目录(git 仓库,位于 home 下)
├── CMakeLists.txt                 # 构建配置
├── README.md
├── PeoplePostureDet_RK3576_Ubuntu/# 算法包(与 src、scripts 同级)
├── src/
│   ├── main.cpp                   # 入口
│   ├── camera.*                   # 摄像头采集线程(FrameHub 共享最新帧)
│   ├── posture_detector.*         # 算法封装(唯一接入算法包模块)
│   ├── settings_dialog.*          # 高级设置面板
│   └── main_window.*              # 界面
└── scripts/
    ├── setup_toolchain.sh         # 一键安装工具链
    ├── build.sh                   # 一键编译(CMake + ninja)
    └── run.sh                     # 一键运行(注入算法路径/授权并启动)

include/、lib/、3rd_party/、models/ 由算法包 PeoplePostureDet_RK3576_Ubuntu 提供,与工程解耦;工程仅通过 src/posture_detector.* 接入算法。

核心代码讲解

Demo 通过 src/posture_detector.h 对算法 SDK 做 PIMPL 封装,仅在该模块内 include PeopleKptDet.h,其余界面代码不与算法包耦合。算法对象 PeopleKptDet 的生命周期为:

构造 → init(加载模型 + 授权)→ inference(逐帧推理)→ get_status(查询状态)→ uninit_model(释放)

初始化

源码位置:src/posture_detector.cpp

/* src/posture_detector.cpp —— doInitialize():一次性连续方法,加载模型并完成授权校验 */
void PostureDetector::doInitialize() {
    lastError_.clear();
    if (impl_->ok) { emit initializedChanged(true, QStringLiteral("ready")); return; }
    cv::redirectError([](int status, const char* func_name, const char* err_msg,const char* file_name, int line, void* )
        -> int {throw cv::Exception(status, err_msg, func_name, file_name, line);});
        
        const QByteArray m = QFile::encodeName(initModel_);
        const QByteArray l = initLicense_.isEmpty() ? QByteArray() : QFile::encodeName(initLicense_);
        const QByteArray a = initAuth_.toUtf8();
        
        const PEOPLE_POSTURE_RES_CODE rc = impl_->kpt.init(const_cast<char*>(m.constData()),
        
        l.isEmpty() ? nullptr : const_cast<char*>(l.constData()),initModule_,
        a.isEmpty() ? nullptr : const_cast<char*>(a.constData()));
        
    const int statusInit = impl_->kpt.get_status(0);
    const int statusAuth = impl_->kpt.get_status(1);
    
    std::fprintf(stderr, "[det] init rc=%d status(init=%d, auth=%d)\n",static_cast<int>(rc), statusInit, statusAuth);
    
    auto statusWord = [](int i, int a) -> QString {
        return (i == 0 && a == 0) ? QStringLiteral("Success") : QStringLiteral("Fail");};
    if (rc == PEOPLE_POSTURE_PROCESS_OK) {
        impl_->ok = true;
        emit initializedChanged(true, QStringLiteral("algorithm initialized: %1")
                                    .arg(statusWord(statusInit, statusAuth)));} 
    else {
        lastError_ = QStringLiteral("algorithm init failed ret=%1: %2")
                                    .arg(static_cast<int>(rc))
                                    .arg(statusWord(statusInit, statusAuth));
        emit initializedChanged(false, lastError_);}}

init 加载模型并完成授权校验;get_status(0) 查询初始化状态,get_status(1) 查询授权状态,两者都为 0 视为成功。

参数说明

  • init(p_model, p_license_file, module, p_auth_code)
  • p_model:人体关键点模型路径(640×640)。
  • p_license_file:算法授权文件的可写路径。
  • module:平台标识,RK3576 固定传 3。
  • p_auth_code:算法授权码(SDK 不内置,由环境变量 PEOPLEKPT_AUTH_CODE 注入)。
  • 返回码 :
    • PEOPLE_POSTURE_PROCESS_OK = 0 :成功;
    • < 0 :初始化/推理/参数非法等错误(日志打印具体错误码)。
  • get_status(opt):get_status(0) 返回初始化状态;get_status(1) 返回授权状态。

推理

推理前将采集帧按 letterbox 保形方式缩放并补边到 640×640,避免直接拉伸导致形变、降低识别率;坐标在校正偏移后映射回原始帧空间,便于界面叠加标注。

/* letterbox:保持人体长宽比缩放到 640×640,缺失区域补黑边 */
const double scale = std::min(640.0 / origW, 640.0 / origH);
cv::resize(bgr, content, cv::Size(cw, ch), 0, 0, cv::INTER_LINEAR);
cv::copyMakeBorder(content, square, yoff, 640-ch-yoff, xoff, 640-cw-xoff,cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));

/* 读取当前参数:单/多目标、两个置信度阈值均可由"高级设置"调节 */
const bool onlyMaxBox = cfg->onlyMaxBox;   /* 单目标(true)/多目标(false) */
const float detTh = cfg->detConfTh;        /* 检测置信度阈值,默认 0.50 */
const float kptTh = cfg->kptConfTh;        /* 关键点置信度阈值,默认 0.30 */

PoseFrameResult sdk;

const PEOPLE_POSTURE_RES_CODE rc = impl_->kpt.inference(src->data, src->rows, src->cols, src->channels(), sdk, onlyMaxBox);

输入约定:

  • 必须为 BGR 连续内存(cv::Mat 默认;若 isContinuous() 为 false,须先 clone())。
  • 通道数必须为 3。
  • SDK 内部自动完成 BGR→RGB、letterbox 缩放到 640×640、归一化与 int8 量化。

输出结构 PoseFrameResult(每帧一个):

字段 含义
det_landmarks[i].x1 / y1 / x2 / y2 第 i 个检测框在 640×640 空间坐标(需映射回原图)
det_landmarks[i].score 检测置信度
kpt_landmarks[i][j].x / y / score 第 i 人的第 j 个关键点坐标与置信度
pose_result.pose 姿态类别枚举(CHEEK_SUPPORT / HEAD_BOWING / UNBALANCED_SHOULDER)
pose_result.is_side_face 是否侧脸

结果去抖

对算法原始结果做滚动窗口滤波:窗口 20、滑动步长 10、去抖保持 1 步,取窗口内出现次数最多且计数 > 1 的姿态作为显示结果;无满足条件动作时显示 unknown。镜头无人持续 1 秒后才清空窗口,1 秒内人重新出现则保留已有窗口数据。

释放

算法线程退出前释放引擎资源:

if (impl_ && impl_->ok) {
    impl_->kpt.uninit_model();   /* 释放 SDK/引擎资源 */
    impl_->ok = false;}

窗口关闭时按顺序清理:先 shutdown() 停止采集与算法线程并 join,再 uninit_model() 释放 SDK 资源,最后释放帧缓存,避免 use-after-free 或残留线程。

部署与运行

传输文件

在 M2 上直接从 GitHub 下载工程(需要联网)

cd ~                                   # 回到 home 目录
git clone https://github.com/Quectel-Pi/demo-edge-algorithms/tree/main/posture_detection_project
cd ~/posture_demo

安装编译环境和开发工具

sudo apt update
sudo bash scripts/setup_toolchain.sh        # 安装 build-essential/cmake/ninja/pkg-config/qt6-base-dev/libopencv-dev 等

编译

cd "$HOME/posture_demo"mkdir -p build
chmod +x scripts/*.sh
sudo bash scripts/build.sh        # CMake + ninja,产物 build/posture_demo

运行

在 Quectel PI M2 桌面终端执行:

bash scripts/run.sh

界面操作

  1. 启动:运行 run.sh,自动打开第一个可用摄像头并实时预览。
  2. 选择摄像头:CAMERA卡下拉框自动扫描所有 USB 设备,默认选中第一个,可切换。
  3. 开启检测:点击 Start Detection,预览叠加检测框与关键点,结果卡更新姿态/置信度。
  4. 停止检测:再次点击,清空结果恢复裸预览。
  5. 调整参数:点击 Advanced Settings,修改推理帧率/检测阈值/关键点阈值/单多目标,确认生效,支持恢复默认。
  6. 退出:点击 Exit。

授权说明

  • 运行前需通过环境变量注入授权码 PEOPLEKPT_AUTH_CODE。
  • 授权文件路径可通过环境变量覆盖(如 PPD_LICENSE_FILE),默认落点为算法包内。
  • 授权失败时,界面算法状态显示 Fail,日志打印 init/auth 状态码(get_status 数值),便于排查。

应用演示

常见问题排查

现象 排查方法
没有摄像头画面 确认摄像头连接与 ls /dev/video*;权限问题加入 video 组;扫描可能发现 Rockchip ISP 中间节点,用 CAMERA_DEVICE=N 或 --device N 指定实际设备
初始化 / 授权失败 检查 PEOPLEKPT_AUTH_CODE 是否正确、板端能否访问授权服务器;查看 get_status 返回的 init/auth 状态码
开检测后程序退出 多为推理阶段崩溃:优先排查授权状态、内存占用(有无 swap),以及单进程编译避免 OOM
缺共享库 核对 setup_toolchain.sh 依赖,用 ldd 检查 build/posture_demo 与算法包动态库

技术支持与贡献

如在使用过程中遇到问题,欢迎在移远官方论坛提交技术咨询,移远技术支持团队将及时为您解答。

项目开源地址: https://github.com/Quectel-Pi/demo-edge-algorithms/tree/main/posture_detection_project

欢迎提交Issue反馈问题或Pull Request贡献代码改进!