在 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
界面操作
- 启动:运行 run.sh,自动打开第一个可用摄像头并实时预览。
- 选择摄像头:CAMERA卡下拉框自动扫描所有 USB 设备,默认选中第一个,可切换。
- 开启检测:点击 Start Detection,预览叠加检测框与关键点,结果卡更新姿态/置信度。
- 停止检测:再次点击,清空结果恢复裸预览。
- 调整参数:点击 Advanced Settings,修改推理帧率/检测阈值/关键点阈值/单多目标,确认生效,支持恢复默认。
- 退出:点击 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贡献代码改进!
