起源
聆思官方提供了基于内带 NPU 的 MCU 芯片 CSK6011A 的 头肩 与 手势识别的 demo。
头肩检测可以检测图像中所有人体的头肩位置,返回每个头肩的唯一id、位置坐标、检测得分等。
手势识别方面,通过头肩检测识别用户的手势,返回当前目标的手势、得分等。
支持 5 种手势,分别为 LIKE(👍)、OK(👌)、STOP(🤚)、YES(✌️)、SIX(🤙)。
手势识别 可以做到 3 米 ,识别率 91%,帧率 15 FPS。此指标是相当的优秀。
如果能够通过手势来悄无声息的控制电器设备将是很酷的事情。
功能
- 通过聆思大模型 AI 开发套件 上的 RGB 指示灯显示 手势识别状态。当识别到 OK(👌) 手势时 RGB 灯亮绿色,同时控制风扇转动。当识别到 STOP(🤚)手势时 RGB 灯亮红色,同时风扇停止转动。当识别到其他手势时 RGB 灯不亮。
- 通过 IO 扩展的 PWM 控制引脚 CH\\\_PD2\\\_PWM 来控制 风扇的转动。因为此引脚 被开发套件中的 LCD 占用,所以需要将开发板上的 LCD 拔下。
连接图如下,这里使用了 DFRobot 的 Gravity: 风扇模块 。
其中红色线连接主板的 3.3V,黑色线连接 GND ,蓝色线连接 PD2 引脚。
代码开发
- 环境搭建
根据官方的 环境搭建教程 进行操作。对于 Windows 系统下安装需要注意,安装 CSK6 开发环境可能会遇到杀毒软件阻止的情况,一定要把杀毒软件,包括Windows 自带的 安全中心 功能都禁止掉才能可靠安装。
- 获取开发环境与 SDK
根据官方的 获取开发环境与 SDK 教程 进行操作。
- 本地 SDK 更新
我们要基于 ListenAI\duomotai\\\_ap\apps\hsd 这个头肩与手势识别的 app , 有可能你获取的SDK并不包含此最新的 app。
可以根据官方的 本地 SDK 更新教程 进行下周最新的 SDK 。
我们可以看到 SDK 代码中 包含了 hsd 这个文件夹,为了不影响原有的 app,你可以拷贝这个文件夹,另存为 hsd\\\_test 作为一个新的 app,修改这个 app 中的代码。
- 编译示例工程,测试是否能够编译通过
在 duomotai\\\_ap 目录下,执行以下指令进行代码编译(以 Windows CMD 终端为例):
注意:一定要在 duomotai\\\_ap 这个根目录下执行 命令,否则不成功。
lisa zep build -b csk6_duomotai_devkit apps\hsd_test -p
编译完成后,编译产物二进制文件为 build\zephyr\zephyr.bin
烧录固件
lisa zep exec cskburn -s \.\COMxx -C 6 -b 1500000 0x000000 --verify-all .\build\zephyr\zephyr.bin
其中的 COMx 代表开发套件连接到 PC 上对应的串口号(可通过设备管理器查看)。例如:COM3。
- 运行
烧录完成后,程序将自动运行,你也可以通过按压开发板上的复位按键(RTS)进行复位运行。
- 通过 聆思串口显示终端 可以显示串口打印的结果。说明 官方的 demo 编译、下载和功能都没有问题了。
- 修改代码。RGB 灯用于指示手势识别结果,PWM 控制风扇。
RGB 灯的控制可参考 以下驱动代码
ListenAI\duomotai\\\_ap\.sdk\csk\samples\driver\exmcu\\\_gpio\\\_led
PWM 可参考 以下驱动代码
ListenAI\duomotai\\\_ap\.sdk\csk\samples\driver\exmcu\\\_pwm
首先修改 app 工程中的 prj.conf 代码,配置工程。
ListenAI\duomotai\\\_ap\apps\hsd\\\_test\prj.conf
增加 PWM 配置
# Copyright (c) 2023 Anhui Listenai Co., Ltd.
# SPDX-License-Identifier: Apache-2.0
CONFIG_LOG=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_NEWLIB_LIBC_FLOAT_SCANF=y
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_HEAP_MEM_POOL_SIZE=81920
CONFIG_CSK_HEAP=y
CONFIG_CSK_HEAP_MEM_POOL_SIZE=307200
CONFIG_GPIO=y
CONFIG_GPIO_CSK6_CH32V003=y
CONFIG_VIDEO=y
CONFIG_VIDEO_CSK6_DVP=y
CONFIG_VIDEO_GC0328=y
CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=614800
CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=3
CONFIG_VIDEO_CUSTOM_SECTION=y
CONFIG_VIDEO_CUSTOM_SECTION_NAME=".psram_section"
CONFIG_SPI=y
CONFIG_I2C=y
CONFIG_ST7789V=n
CONFIG_ADC=y
CONFIG_ADC_CSK6_CH32V003=y
CONFIG_KSCAN=y
CONFIG_KSCAN_ADC=y
CONFIG_LV_COLOR_DEPTH_16=y
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_Z_DRIVER_CSK6_16BIT=y
CONFIG_LV_Z_DRIVER_CSK6_16BIT_ROTATE_90=n
CONFIG_LV_Z_DRIVER_CSK6_16BIT_THREAD_PRIORITY=1
CONFIG_LV_Z_DRIVER_CSK6_KSAN_FILTER_TIME_MS=50
CONFIG_LV_MEM_CUSTOM=y
CONFIG_LV_Z_MEM_CUSTOM_SECTION=y
CONFIG_LV_Z_MEM_CUSTOM_SECTION_NAME=".psram_section"
CONFIG_LV_Z_MEM_POOL_MAX_SIZE=1024
CONFIG_LV_Z_MEM_POOL_NUMBER_BLOCKS=500
CONFIG_LV_Z_DOUBLE_VDB=y
CONFIG_LV_Z_VDB_SIZE=100
CONFIG_LV_Z_BUFFER_ALLOC_DYNAMIC=y
CONFIG_DISK_MEMORY=n
CONFIG_LSF=y
CONFIG_LSF_CLIENT=y
CONFIG_LSF_OS_ZEPHYR=y
CONFIG_LSF_IC_MESSAGE_EP_ID=5
CONFIG_CAPABILITY_HSD=y
CONFIG_DISPLAY_DEBUG=n
CONFIG_PWM=y
CONFIG_PWM_CSK6_CH32V003=y
CONFIG_PWM_CSK6=n
配置中,关闭了 LCD 的显示,这样我们的风扇才能接在 PWM 接口上;使能了 PWM。
修改 ListenAI\duomotai\\\_ap\apps\hsd\\\_test\boards\csk6\\\_duomotai\\\_devkit.overlay 文件,增加 PWM 配置
/*
* Copyright (c) 2023 Anhui Listenai Co., Ltd.
* SPDX-License-Identifier: Apache-2.0
*/
/delete-node/ &storage_partition;
/delete-node/ &psram_ap;
/delete-node/ &psram_cp;
/delete-node/ &psram_share;
/delete-node/ &wifi_driver_storage;
/delete-node/ &wifi_nvs_storage;
/ {
chosen {
zephyr,code-partition = &ap_code_partition;
resource,cp = &cp_code_partition;
resource,hsd_head_shoulder = &res_hsd_head_shoulder_partition;
resource,hsd_gesture_recognition = &res_hsd_gesture_recognition_partition;
resource,hsd_head_track = &res_hsd_head_track_partition;
};
};
&flash0 {
reg = <0x18000000 DT_SIZE_M(16)>;
write-block-size = <4>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
ap_code_partition: partition@0 {
label = "ap_code";
reg = <0x0 DT_SIZE_M(2)>;
};
cp_code_partition: partition@200000 {
label = "cp_code";
reg = <0x200000 DT_SIZE_M(1)>;
};
res_hsd_head_shoulder_partition: partition@300000 {
reg = <0x300000 1466832>;
};
res_hsd_gesture_recognition_partition: partition@480000 {
reg = <0x480000 1272048>;
};
res_hsd_head_track_partition: partition@600000 {
reg = <0x600000 442432>;
};
};
};
&psram0 {
compatible = "listenai,csk6-psram";
reg = <0x30000000 DT_SIZE_M(8)>;
#address-cells = <0x1>;
#size-cells = <0x1>;
psram_cp: psram_cp@30000000 {
compatible = "listenai,csk6-psram-partition";
reg = <0x30000000 0x510000>;
status = "okay";
};
psram_ap: psram_ap@30510000 {
compatible = "zephyr,memory-region",
"listenai,csk6-psram-partition";
reg = <0x30510000 0x2f0000>;
status = "okay";
zephyr,memory-region = "PSRAMAP";
};
};
&expwm{
clock-prescaler = <4800>;
clock-frequency = <48000000>;
};
/ {
motor {
motor_pwm: motor_pwm {
compatible = "vnd,phandle-holder";
status = "okay";
pwms = <&expwm 0 1000 0x0 100>;
};
};
};
修改 ListenAI\duomotai\\\_ap\apps\hsd\\\_test\src\comp\\\_hsd.c 代码,如下
/*
* Copyright (c) 2023 Anhui Listenai Co., Ltd.
* SPDX-License-Identifier: Apache-2.0
*/
#include "comp_hsd.h"
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/kernel.h>
#if defined(CONFIG_DISPLAY_DEBUG)
#include "screen.h"
#endif /* CONFIG_DISPLAY_DEBUG */
#include "gcs_hsd_service.h"
#if defined(CONFIG_DISPLAY_DEBUG)
static struct hsd_gcs_info screen_info;
static void update_screen_info_cb(struct k_work *work)
{
screen_update_info(&screen_info);
}
K_WORK_DEFINE(update_screen_info, update_screen_info_cb);
#endif /* CONFIG_DISPLAY_DEBUG */
// 扩展IO的定义
/* The devicetree node identifier for the "led_rgb" alias. */
#define LED_R_NODE DT_ALIAS(led_rgb_red)
#define LED_G_NODE DT_ALIAS(led_rgb_green)
#define LED_B_NODE DT_ALIAS(led_rgb_blue)
/*
* A build error on this line means your board is unsupported.
* See the sample documentation for information on how to fix this.
*/
static const struct gpio_dt_spec led_r = GPIO_DT_SPEC_GET(LED_R_NODE, gpios);
static const struct gpio_dt_spec led_g = GPIO_DT_SPEC_GET(LED_G_NODE, gpios);
static const struct gpio_dt_spec led_b = GPIO_DT_SPEC_GET(LED_B_NODE, gpios);
// 扩展IO的PWM初始化
const struct pwm_dt_spec spec = PWM_DT_SPEC_GET(DT_NODELABEL(motor_pwm));
int _pwm_set(uint32_t period_ms,
uint32_t pulse_ms)
{
int err;
uint64_t pulse_cycles;
uint64_t period_cycles;
uint64_t cycles_per_sec;
err = pwm_get_cycles_per_sec(spec.dev, spec.channel, &cycles_per_sec);
if (err < 0) {
return err;
}
period_cycles = (period_ms * cycles_per_sec) / MSEC_PER_SEC;
if (period_cycles > UINT16_MAX) {
return -ENOTSUP;
}
pulse_cycles = (pulse_ms * cycles_per_sec) / MSEC_PER_SEC;
if (pulse_cycles > UINT16_MAX) {
return -ENOTSUP;
}
return pwm_set_cycles(spec.dev, spec.channel, (uint32_t)period_cycles,
(uint32_t)pulse_cycles, spec.flags);
}
static void comp_hsd_info_callback(const struct hsd_gcs_info *info)
{
int ret;
#if defined(CONFIG_DISPLAY_DEBUG)
memcpy(&screen_info, info, sizeof(screen_info));
k_work_submit(&update_screen_info);
#endif /* CONFIG_DISPLAY_DEBUG */
if (info->results_cnt > 0) {
printk("hsd results: %d [\n", info->results_cnt);
for (uint32_t i = 0; i < info->results_cnt; i++) {
const head_shoulder_detect *result = &info->results[i];
printk(" [%d] "
"id: %d, "
"rect: [x:%d, y:%d, w:%d, h:%d], "
"score: %f, "
"gesture: [%d \"%s\" %f]\n",
i, result->id, result->rect.x, result->rect.y, result->rect.w,
result->rect.h, result->score, result->gesture_state,
comp_hsd_gesture_name(result->gesture_state),
result->gesture_scores[result->gesture_state]);
// 如果手势识别是STOP,就亮红灯
if (result->gesture_state == GESTURE_STOP) {
ret = gpio_pin_set_dt(&led_r,1);
if (ret < 0) {
return 0;
}
ret = gpio_pin_set_dt(&led_g,0);
if (ret < 0) {
return 0;
}
ret = gpio_pin_set_dt(&led_b,0);
if (ret < 0) {
return 0;
}
// 通过pwm关闭风扇
ret = _pwm_set(spec.period, 0);
if (ret) {
printk("pwm set failed, r:%d\n", ret);
return 0;
}
}
// 如果手势识别是OK,就亮绿灯
else if(result->gesture_state == GESTURE_OK){
ret = gpio_pin_set_dt(&led_r,0);
if (ret < 0) {
return 0;
}
ret = gpio_pin_set_dt(&led_g,1);
if (ret < 0) {
return 0;
}
ret = gpio_pin_set_dt(&led_b,0);
if (ret < 0) {
return 0;
}
// 通过pwm开启风扇转动
ret = _pwm_set(spec.period, spec.period / 2);
if (ret) {
printk("pwm set failed, r:%d\n", ret);
return 0;
}
}
// 其他手势识别都熄灭
else {
ret = gpio_pin_set_dt(&led_r,0);
if (ret < 0) {
return 0;
}
ret = gpio_pin_set_dt(&led_g,0);
if (ret < 0) {
return 0;
}
ret = gpio_pin_set_dt(&led_b,0);
if (ret < 0) {
return 0;
}
}
}
printk("]\n");
}
}
int comp_hsd_init(void)
{
/* 初始化服务 */
hsd_service_gcs_init();
#if defined(CONFIG_WEBUSB_DEBUG)
/* 开启 PC 调试工具 */
hsd_service_gcs_work_mode_set(HSD_GCS_WORK_MODE_DEBUG, NULL);
#endif /* CONFIG_WEBUSB_DEBUG */
/* 注册信息回调 */
hsd_service_gcs_callback(comp_hsd_info_callback);
/* 启动服务 */
hsd_service_gcs_start();
/* 配置参数 (请参考手册) */
struct hsd_param params[] = {
{PARAM_HEAD_SHOULDER_DETECT_THRES, 0.60f},
{PARAM_HEAD_SHOULDER_DETECT_LOSS_CNT, 5.0f},
{PARAM_HEAD_SHOULDER_DETECT_PIXESIZE, 10.0f},
{PARAM_HEAD_SHOULDER_DETECT_TIMEOUT, 80.0f},
};
hsd_service_gcs_params_set(params, ARRAY_SIZE(params));
// 初始化扩展LED_RGB
int ret;
if (!gpio_is_ready_dt(&led_r)) {
return 0;
}
if (!gpio_is_ready_dt(&led_g)) {
return 0;
}
if (!gpio_is_ready_dt(&led_b)) {
return 0;
}
ret = gpio_pin_configure_dt(&led_r, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
ret = gpio_pin_configure_dt(&led_g, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
ret = gpio_pin_configure_dt(&led_b, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
// PWM初始化
if (!device_is_ready(spec.dev)) {
printk("device: %s is not ready\n", spec.dev->name);
return 0;
}
printk("init board OK!!!!!!!!!!!!!!\n");
return 0;
}
static const char *gesture_names[GESTRUE_COUNT] = {
[GESTURE_OTHER] = "OTHER", [GESTURE_LIKE] = "LIKE", [GESTURE_OK] = "OK",
[GESTURE_STOP] = "STOP", [GESTURE_YES] = "YES", [GESTURE_SIX] = "SIX",
[GESTURE_PHOTO] = "PHOTO",
};
const char *comp_hsd_gesture_name(GESTURE_STAT gesture)
{
return gesture_names[gesture];
}
对代码重新进行编译
lisa zep build -b csk6\_duomotai\_devkit apps\hsd\_test -p
烧录固件
lisa zep exec cskburn -s \.\COMxx -C 6 -b 1500000 0x000000 --verify-all .\build\zephyr\zephyr.bin
验证功能
重新对主板进行上电后。
通过 OK(👌) 手势,RGB 灯显示 绿色,风扇转动。
通过 STOP(🤚)手势,RGB 灯显示 红色,风扇停止转动。