# 手势识别算法 ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- 在 **Quectel Pi M2 Debian 系统** 上,基于 **手势识别算法 SDK**,从零搭建一个带桌面图形界面的手势识别项目。 界面采用三栏布局: - **左栏** —— 算法模块列表(当前为「Gesture Recognition」); - **中栏** —— 标题 + 实时显示区 + 控制按钮; - **右栏** —— 设备状态面板( RAM / ROM / CPU )。 提供两种体验方式: - **USB 摄像头** —— 打开 USB 摄像头,实时识别画面中的手势; - **本地图片** —— 选择一张本地图片,离线识别其中的手势。 ```{image} images/image_HZtebOMjjo686cxlxVDcGUrpnuf.webp :width: 1280px :height: 800px :align: center ``` ## 算法能力 | **项目** | **说明** | | --- | --- | | 算法类型 | 手势检测 + 手势分类(级联) | | 检测模型 | board6_RK3576_Ubuntu_hand_det.engine(640²,手部框) | | 分类模型 | board6_RK3576_Ubuntu_gesture_cls.engine(224²,13 类) | | 输入 | BGR 连续内存(cv::Mat.data,rows×cols×3) | | 输出 | 每只手一个 DetRes(框 xywh + 分类标签 + 置信度) | **手势标签示例**:`like`(点赞)、`dislike`(倒赞)、`call`(打电话)、`fist`(握拳)、`four`(四指)、`mute`(静音)、`ok`、`one`(食指)、`palm`(手掌)、`three`(三指)、`yeah`(V 字)、`other`(其他)。 ## 部署前准备 ### 资源获取 | **名称** | **说明** | | --- | --- | | `gesture_RK3576`(算法 SDK 包) | 手势识别算法包,含公开头、动态库、第三方运行时、模型与样例图 | | [`gesture_project`]()[(Demo 工程)]() | 本教程提供的示例工程,含源码、Makefile 与三个脚本(复制依赖 / 编译 / 运行) | 算法 SDK 包 `gesture_RK3576` 的关键目录: ```plaintext gesture_RK3576/ ├── include/gesture.h # 算法公开头(集成时唯一需要包含的头) ├── lib/libgesture.so # 算法主库(检测+分类级联) ├── 3rd_party/ │ ├── modelinfer/ # 推理引擎 libinference.so + 授权 libauthorization.so │ ├── rknn/librknnrt.so # RKNN 硬件运行时 │ └── opencv/ # 随包 OpenCV world(头 + libopencv_world.so) ├── models/ # 检测/分类 .engine 模型 ├── images/hand_input.jpg # 验收样例图 └── example.cpp # 官方最小调用示例 ``` ### 硬件/系统要求 | **项目** | **要求** | | --- | --- | | 主控 | Quectel Pi M2 | | 系统 | Debian / Ubuntu aarch64(带桌面环境,Wayland 或 X11) | | 外设 | USB 摄像头(体验实时识别时需要) | | 连接 | PC 可通过 `adb shell` 进入设备 | ### 软件要求 Demo 在Quectel Pi M2上本地编译,需确认以下软件已安装: ```bash g++ --version # C++17 编译器 make --version pkg-config --exists Qt5Widgets Qt5Gui Qt5Core && echo "Qt5 OK" # 显示用 Qt5(LGPL) v4l2-ctl --version # 用于枚举和排查 USB 摄像头节点 ``` > **界面方案说明**:显示层采用系统自带的 **Qt5**,直接在桌面显示、支持全屏;采集/推理/绘制使用 **工程内随包的 OpenCV world** 单库,与系统 OpenCV 隔离,避免版本冲突。 ## 工程结构 ```bash gesture_project/ ├── src/ │ └── gesture_cam_demo.cpp # Demo 主程序(Qt 界面 + 工作线程推理) ├── include/ # setup 后:SDK 公开头 gesture.h ├── lib/ # setup 后:libgesture.so ├── third_party/ # setup 后:modelinfer / rknn / opencv 运行时 ├── models/ # setup 后:检测/分类 .engine 模型 ├── images/ # setup 后:样例图 hand_input.jpg ├── Makefile # 编译规则 ├── build.sh # 一键编译 ├── setup_from_sdk.sh # 从 SDK 包复制依赖到本工程 ├── run.sh # 一键运行(配置环境变量 + 启动) └── README.md # 说明文档 ``` `include/ lib/ third_party/ models/ images/` 由 `setup_from_sdk.sh` 从原始 SDK 包复制生成。 ## 核心代码讲解 Demo 基于 SDK 公开头 `gesture.h` 的单类 `Gesture`,调用生命周期为:**构造 →`set_param`****(设阈值)→`init`****(加载模型+授权)→`inference`****(逐帧推理)→`uinit`****(释放)**。 源码位于 `src/gesture_cam_demo.cpp`,下面按调用顺序讲解关键片段。 ### 初始化 ```cpp Gesture g; float conf = 0.25f, iou = 0.45f, det_thr = 0.0f, cls_thr = 0.20f; g.set_param(conf, iou, det_thr, cls_thr); // 引用 4 参:检测置信度/NMS/det 过滤/分类阈值 char det_model[] = "models/board6_RK3576_Ubuntu_hand_det.engine"; char cls_model[] = "models/board6_RK3576_Ubuntu_gesture_cls.engine"; char license_buf[512] = "authorization.lic"; if (const char* license_path = std::getenv("GESTURE_LICENSE_FILE")) { if (license_path[0]) std::snprintf(license_buf, sizeof(license_buf), "%s", license_path); } // p_auth 为算法授权码,经环境变量 GESTURE_AUTH_CODE 或命令行下发(SDK 不内置) char auth_buf[512] = {0}; const char* auth = std::getenv("GESTURE_AUTH_CODE"); char* p_auth = nullptr; if (auth && auth[0]) { std::snprintf(auth_buf, sizeof(auth_buf), "%s", auth); p_auth = auth_buf; } GESTURE_RES_CODE rc = g.init(det_model, cls_model, license_buf, 3, p_auth); if (rc != GESTURE_PROCESS_OK) { /* 初始化失败:检查授权码/模型路径 */ } ``` 参数说明: - **`set_param(conf, iou, det_thr, cls_thr)`**(四个参数均为引用传入): - `conf`:手部 **检测** 置信度阈值,默认 `0.25`。低于此分数的候选框在检测阶段被丢弃;调高减少误检,调低召回更多手。 - `iou`:检测框 **NMS(非极大值抑制)** 的 IoU 阈值,默认 `0.45`。两个重叠框 IoU 超过该值时只保留分数更高的,避免同一只手出多框。 - `det_thr`:检测结果 **输出过滤** 阈值,默认 `0.0`(不过滤)。对最终输出的 `det_score` 再做一次下限筛选。 - `cls_thr`:**分类** 阈值,默认 `0.20`。分类 top1 分数低于此值时,`label` 置为 `other`(不确定手势)。 - **`init(p_det_model, p_cls_model, p_license_file, module, p_auth_code)`** 加载两颗模型并完成授权,五个参数依次为: - `p_det_model`:手部 **检测** 模型路径(`hand_det.engine`,输入 640²)。 - `p_cls_model`:手势 **分类** 模型路径(`gesture_cls.engine`,输入 224²,13 类)。 - `p_license_file`:算法授权文件的可写路径。项目中的 `run.sh` 通过 `GESTURE_LICENSE_FILE` 传入工程内绝对路径。 - `module`:平台标识,RK3576 固定传 `3`。 - `p_auth_code`:算法授权码(SDK 不内置,经 `GESTURE_AUTH_CODE` 环境变量或参数下发)。 - **返回码`GESTURE_RES_CODE`**:`0`=`GESTURE_PROCESS_OK` 成功;`-1` 初始化失败(授权/模型路径);`-2` 未初始化;`-3` 推理失败;`-4` 图像/参数非法。`rc` 必须判等 `GESTURE_PROCESS_OK` 后才能推理。 - **`get_status(opt)`** 查询状态:`get_status(0)` 返回初始化状态(`0` 成功);`get_status(1)` 返回授权状态(`0` 正式授权)。 ### 推理 每一帧(摄像头帧或本地图片)都调用一次 `inference`,SDK 内部完成「检测 → 裁剪 → 分类」的完整级联,返回每只手的结果。 ```cpp std::vector results; // 入参:BGR 数据指针、行(高)、列(宽)、通道数(必须 3)、输出结果向量 GESTURE_RES_CODE rc = g.inference(frame.data, frame.rows, frame.cols, frame.channels(), results); if (rc != GESTURE_PROCESS_OK) { /* 推理失败 */ } for (const auto& r : results) { // r.x, r.y, r.width, r.height : 检测框(原图像素坐标,xywh) // r.det_score : 检测置信度 // r.class_id : 分类 ID(0~12) // r.label : 手势标签(如 "fist") // r.cls_score : 分类置信度 cv::rectangle(frame, cv::Rect(r.x, r.y, r.width, r.height), cv::Scalar(0,255,0), 2); } ``` **输入约定**: - 必须是 **BGR 连续内存**(`cv::Mat` 默认即连续;若 `isContinuous()` 为假需先 `clone()` 再传 `.data`)。 - `channels` 必须为 `3`;灰度图需先 `cv::cvtColor` 转 BGR。 - SDK 内部自动完成 BGR→RGB、letterbox 缩放到 640²、`/255` 归一化与 int8 量化,调用方 **无需做任何预处理**。 **级联流程(SDK 内部)**: 1. 整图过 `hand_det` 检测所有手,得到候选框并做 NMS; 2. 每只手按框 +10% 上下文裁剪,resize 到 224²,按 ImageNet 均值方差归一化; 3. 过 `gesture_cls` 得到 13 类 softmax,取 top1;分数低于 `cls_thr` 则标 `other`。 **输出结构`DetRes`**(每只手一个): | **字段** | **含义** | | --- | --- | | `x / y / width / height` | 检测框在 **原图** 的像素坐标(左上角 + 宽高) | | `det_score` | 检测置信度(手部框可信度) | | `class_id` | 手势分类 ID(0~12) | | `label` | 手势名字符串(如 `fist`、`ok`、`palm`) | | `cls_score` | 分类置信度(top1 softmax 分数) | > 结果坐标已换算回原图,可直接用于 `cv::rectangle` / `cv::putText` 叠加绘制;`results` 为空表示当前帧未检测到手。 ### 释放 ```cpp g.uinit(); // 释放内部引擎资源 ``` ## 部署与运行 ### 传输文件 以下分为 PC 端和设备端命令。PC 示例使用 Windows PowerShell;`adb push` 先把目录传到设备 `/tmp` adb传输示例: ```bash # adb push 传输文件夹先放到 Quectel Pi M2 的/tmp下 adb push gesture_RK3576 /tmp/ adb push gesture_project /tmp/ ``` 推送完成后,在Quectel Pi M2桌面会话中打开终端,以普通用户执行下列命令。 ```bash #$HOME 和 $USER 由设备 shell 展开;不要在 root adb shell 中执行,否则它们会指向 root 用户 sudo mv /tmp/gesture_RK3576 /tmp/gesture_project "$HOME"/ sudo chown -R "$USER:$USER" "$HOME/gesture_RK3576" "$HOME/gesture_project" cd "$HOME/gesture_project" && chmod +x *.sh ``` ### 安装编译环境和开发工具 在Quectel Pi M2终端中输入以下命令: ```bash # 安装 make,g++等开发工具 sudo apt update && sudo apt install -y build-essential pkg-config qtbase5-dev ``` ### 复制依赖 `setup_from_sdk.sh` 从 SDK 包把头文件、动态库、第三方运行时、模型、样例图复制到工程对应目录: ```bash cd $HOME/gesture_project sh setup_from_sdk.sh $HOME/gesture_RK3576 # 不传参数则默认 ~/gesture_RK3576 ``` ### 编译 ```bash sh build.sh #产物:$HOME/gesture_project/gesture_cam_demo ``` > `build.sh` 内部调用 `make`:用系统 Qt5 做界面,链接工程内 `libgesture.so` 与随包 OpenCV world生成可执行文件 ### 运行 在Quectel Pi M2 **桌面终端** 中运行: ```bash ./run.sh # 自动探测 USB 摄像头 ./run.sh /dev/video73 # 指定 USB 摄像头节点(跳过自动探测) ``` > 推荐从设备桌面会话的终端启动。若通过 `adb shell` 启动,需切换到桌面登录用户,并确保 `XDG_RUNTIME_DIR`、`WAYLAND_DISPLAY` 或 `DISPLAY`/`XAUTHORITY` 与桌面会话一致; ### 界面操作 窗口为三栏布局:左栏算法模块、中栏显示与控制、右栏设备状态。 | **操作** | **说明** | | --- | --- | | **[Open Camera]** | 打开/关闭 USB 摄像头,实时识别手势;关闭时自动清屏 | | **[Local Image]** | 弹出文件选择框,选一张图片离线识别 | | **[Full Screen]** | 切换全屏 / 窗口显示 | | **[Quit]** | 关闭程序 | | 快捷键 | Esc/Q 退出,F 全屏 | **设备状态面板(右栏)** 每秒刷新一次: | **指标** | **说明** | | --- | --- | | RAM | 已用百分比 + 已用/总量(MB) | | ROM | 根文件系统已用百分比 + 已用/总量(GB) | | CPU | CPU 占用百分比 | ## 授权说明 SDK 采用 **先引擎、后算法** 的两层授权,集成方只需提供 **算法授权码**(`p_auth_code`),其余随包固定: - 授权码通过环境变量 `GESTURE_AUTH_CODE` 下发(`run.sh` 已 `export`,集成方替换为自己的码即可); - 授权文件路径由 `GESTURE_LICENSE_FILE` 指定,`run.sh` 默认设为工程目录内的绝对路径 `authorization.lic`;引擎授权件 `ql_ans_license.txt` 也保存在工程目录。首次联网授权成功后文件会生成,后续启动复用。 - 正确算法码 → 正式授权(`get_status(1)=0`); - 授权校验返回 `100010` 且 SDK 探测确认授权服务器不可达时,进入算法 **30 分钟 Trial**(`get_status(1)=100010`,到期映射 `100002`); - 空码或在线错码会使 `init` 失败,不进入算法 Trial。 查询状态:`get_status(0)` 初始化状态(0 成功),`get_status(1)` 授权状态。 ## 应用演示
gesture-demo
## 常见问题排查 | **问题** | **可能原因** | **解决方案** | | --- | --- | --- | | 初始化失败 rc=-1 | 授权码为空/错误,或模型路径不对 | 设置 GESTURE_AUTH_CODE;确认 models/ 下两个 .engine 存在 | | 窗口不显示 | 非桌面会话 / QT_QPA_PLATFORM 未匹配 | 在设备桌面会话运行;或由 run.sh 自动探测 Wayland/xcb | | 无法打开摄像头 | USB 摄像头未插入 / 指定节点错误 | 默认 auto 会自动探测 USB 采集节点;可用 v4l2-ctl --list-devices 确认后 ./run.sh /dev/videoN | | 链接/运行报找不到 .so | LD_LIBRARY_PATH 未设置 | 用 run.sh 启动(已配置),或手动 export 工程内 lib 与 third_party/* 路径 | | 识别结果为空 | 画面无手势 / 置信度过低 | 让手部清晰入镜;可调 set_param 的检测置信度与分类阈值 | ## 技术支持与贡献 如在使用过程中遇到问题,欢迎在 [移远官方论坛]() 提交技术咨询,移远技术支持团队将及时为您解答。 项目开源地址: https://github.com/Quectel-Pi/demo-edge-algorithms 欢迎提交Issue反馈问题或Pull Request贡献代码改进!