bobo_ckH817 · 3月21日 · 四川

【聆思大模型 AI 开发套件】- 基于头肩手势 demo 的手势控制风扇

起源

聆思官方提供了基于内带 NPU 的 MCU 芯片 CSK6011A 的 头肩 与 手势识别的 demo

头肩检测可以检测图像中所有人体的头肩位置,返回每个头肩的唯一id、位置坐标、检测得分等。

手势识别方面,通过头肩检测识别用户的手势,返回当前目标的手势、得分等。
支持 5 种手势,分别为 LIKE(👍)、OK(👌)、STOP(🤚)、YES(✌️)、SIX(🤙)。

手势识别 可以做到 3 米 ,识别率 91%,帧率 15 FPS。此指标是相当的优秀。

如果能够通过手势来悄无声息的控制电器设备将是很酷的事情。

功能

  • 通过聆思大模型 AI 开发套件 上的 RGB 指示灯显示 手势识别状态。当识别到 OK(👌) 手势时 RGB 灯亮绿色,同时控制风扇转动。当识别到 STOP(🤚)手势时 RGB 灯亮红色,同时风扇停止转动。当识别到其他手势时 RGB 灯不亮。

 title=

  • 通过 IO 扩展的 PWM 控制引脚 CH\\\_PD2\\\_PWM 来控制 风扇的转动。因为此引脚 被开发套件中的 LCD 占用,所以需要将开发板上的 LCD 拔下。  title=

连接图如下,这里使用了 DFRobot 的  Gravity: 风扇模块
其中红色线连接主板的 3.3V,黑色线连接 GND ,蓝色线连接 PD2 引脚。
 title=
 title=

代码开发

  • 环境搭建

根据官方的 环境搭建教程 进行操作。对于 Windows 系统下安装需要注意,安装 CSK6 开发环境可能会遇到杀毒软件阻止的情况,一定要把杀毒软件,包括Windows 自带的 安全中心 功能都禁止掉才能可靠安装。

  • 获取开发环境与 SDK

根据官方的 获取开发环境与 SDK 教程 进行操作。

  • 本地 SDK 更新

我们要基于 ListenAI\duomotai\\\_ap\apps\hsd  这个头肩与手势识别的 app , 有可能你获取的SDK并不包含此最新的 app。
可以根据官方的 本地 SDK 更新教程 进行下周最新的 SDK 。

我们可以看到 SDK 代码中 包含了 hsd 这个文件夹,为了不影响原有的 app,你可以拷贝这个文件夹,另存为 hsd\\\_test 作为一个新的 app,修改这个 app 中的代码。 
 title=

  • 编译示例工程,测试是否能够编译通过

在 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 编译、下载和功能都没有问题了。

 title=

  • 修改代码。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 灯显示 绿色,风扇转动。 
 title=

通过 STOP(🤚)手势,RGB 灯显示 红色,风扇停止转动。  title=

推荐阅读
关注数
0
文章数
1
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息