可乐跑枸杞 · 2022年11月19日 · 浙江

【聆思CSK6 视觉AI开发套件试用】利用文件系统实现人脸识别数据掉电保存,上电自动加载

首先非常感谢聆思科技、极术社区提供了这次CSK6视觉AI开发套件的体验机会。本次体验包括以下几个部分:

  • 环境搭建
  • 体验人脸识别功能
  • 体验文件系统的功能
  • 针对重新上电后人脸数据丢失的问题,利用文件系统将特征数据存储在flash中,下次上电自动加载人脸数据。
  • 总结

1. 开发环境安装

1.1 win11搭建环境

https://docs.listenai.com/chips/600X/application/getting\\_start

官网有详细的文档,在此不在赘述。

1.2 验证环境

lisa zep create

在命令行中进入刚创建的 hello\\_world 项目目录,执行编译命令。

cd hello_worldlisa zep 

build -b csk6011a_nano

lisa zep flash

 title=

至此环境已经搭建成功!

2. 人脸识别应用源码概览

2.1 main文件的几个结构体

2.1.1 face\_register\_t

face_register_t 结构体贯穿整个main文件,主要是负责保存设备实例,消息队列结构体,一张人脸的特征数据,特征数据的有效标志等

typedef struct {
    float feature[FD_MAX_FEATURE_DIMS];
    float feature_nvs[FD_MAX_FEATURE_DIMS];
    bool get_new_feature;
    fd_t *fd;
    bool is_usb_cfg;
    const struct device *video;
    struct video_format fmt;
    char result_txt[512];
    struct k_msgq msg;
} face_register_t;

2.1.2 storage\_info\_t

其中人脸数据存放在storage结构体中。storage结构体里维护了一个FIFO 队列,队列最大长度为10。

#define STORAGE_DATA_NUM_MAX    (10)

typedef struct {
    struct k_fifo data_fifo; 
    uint32_t data_cnt;
} storage_info_t;

2.2 main文件的几个函数

2.2.1 设置算法参数 --- alago\_init

int alago_init(void)
{
    float value = 0.4;
    fd_set_params(fd, FD_PARAM_FACE_DETECT_THRES, &value);  

    value = 30.0;
    fd_set_params(fd, FD_PARAM_FACE_ALIGN_YAWTHRES, &value);

    value = 30.0;
    fd_set_params(fd, FD_PARAM_FACE_ALIGN_PITCHTHRES, &value);

    value = 30.0;
    fd_set_params(fd, FD_PARAM_FACE_ALIGN_ROLLTHRES, &value); 

    value = 0.0;
    fd_set_params(fd, FD_PARAM_ANTI_SPOOFING_THRES, &value);   
}

当前视觉SDK,针对人脸识别,算法层面支持以下参数参数的配置:

参数type功能说明取值范围
FD_PARAM_FACE_DETECT_THRESfloat人脸检测框门限0~1
FD_PARAM_FACE_DETECT_PIXESIZEfloat检测框最小像素值门限0~640
FD_PARAM_FACE_ALIGN_YAWTHRESfloat头姿检测偏航角门限0~180
FD_PARAM_FACE_ALIGN_PITCHTHRESfloat头姿检测俯仰角门限0~180
FD_PARAM_FACE_ALIGN_ROLLTHRESfloat头姿检测翻滚角门限0~180
FD_PARAM_ANTI_SPOOFING_THRESfloat活体识别门限0~1

2.2.2 人脸检测 + 特征提取 --- sample\_face\_detect

  • 此函数完成了人脸检测以及人脸特征的提取,然后将提取到的人脸数据发送到网页显示,人脸特征数据放在了face\_register\_t.feature中
int sample_face_detect(fd_t *fd, struct video_buffer *vbuf, struct video_format *fmt)
    {
        ...
        fd_exec(fd, &pic_buf, &result);  /* 人脸检测 */
        ...
        webusb_send_pic(vbuf, &result);  /* 网页显示 */
    }
  • 显然如果一副图像中有多个人脸时,其实只保存了得分最高的人脸的feature
if(result.detect_cnt != 0 && result.detect_data[0].feature_dim == FD_MAX_FEATURE_DIMS)
     {
         memcpy((void *)fr.feature, (void *)result.detect_data[0].feature, result.detect_data[0].feature_dim * sizeof(float));
         fr.get_new_feature = true;
     }

2.2.3 摁键回调函数实现人脸注册、比较等人机交互任务 --- button\_callback

摁键回调函数主要是负责完成人脸注册,特征比较,清楚注册等任务:

  1. 人脸注册:长按开发板上的“用户按钮”,直到日志信息处出现face_recognize: success即表示当前人脸注册成功;
  2. 特征比较:短按开发板上的“用户按钮” ,设备会将当前人脸特征与已注册的人脸库进行逐一比较,如分值超过设定的阈值,则日志信息处会出现face_calc_similar: success,如低于设定的阈值,则显示face_calc_similar: fail
  3. 清除注册:信息快速双击开发板上的“用户按钮”,日志信息处出现clear_face _data: success,则表示人脸库已清除。
void button_callback(uint32_t event)
    {
        switch(event){
            case BUTTON_SINGLE_CLICK:特征比较
            case BUTTON_LONG_PRESS:人脸注册
            case BUTTON_DOUBLE_CLICK:清除注册信息
         }    
    }

通过长按摁键可以实现人脸注册,但是重新上电后,注册的信息就丢失了,所以我们可以把注册的人脸信息存储到非易失的flash中去

3. littlefs文件系统的使用

获取sample

csk6 sdk提供了littlefs的使用示例,可以通过Lisa命令获取示例项目:

通过Lisa命令创建项目:

lisa zep create

按以下目录选择完成sample创建:

sample → subsys → fs → littlefs

开发者也可以通过该连接下载已经打包好的空系统文件:littlefs\_image.bin。文件系统bin文件制作完成后,在下文烧录固件时烧录到flash对应的偏移地址上。

https://docs.listenai.com/assets/files/littlefs\_imge-9c176f220342bc509ef31d5d9c06bb94.bin

编译

在app根目录下通过以下指令完成编译:

lisa zep build -b csk6011a_nano

烧录

  • 烧录应用项目固件

csk6011a_nano开发板通过USB连接PC,通过烧录指令开始烧录:

lisa zep flash
  • 烧录文件系统bin文件

这里提供串口烧录的指令示例,开发可根据实际的硬件环境选择对应的烧录方式,需要注意偏移地址是正确的。

lisa zep exec cskburn -s \\.\COMx -C 6 0x700000 xxx\littlefs_imge.bin -b 748800

 title=

烧录文件系统中。。。

 title=

连上串口,复位后得到输出

 title=

4. 注册人脸保存到flash

4.1 主要逻辑

实现人脸数据掉电恢复的逻辑很简单,主要包括两个阶段:

  • 写已注册人脸数据到flash
  • 重新上电后读人脸数据到运行时结构体

4.2 最终效果

如图所示,在重新上电后,

  • 首先从flash读取了上次存储的两张人脸特征数据填充到了运行时结构体中
  • 短摁进行人脸识别,人脸识别成功,证明已经成功读取。(本次上电并未手动录入)
  • 长摁进行人脸注册,显示新增人脸成功,将三张结构体都保存到了flash中

    日志信息:
    2022-11-19 21:08:53 read 2 face features from flash!
    2022-11-19 21:09:01 face_calc_similar: success
    2022-11-19 21:09:15 face_recognize: success, count:3
    2022-11-19 21:09:15 save 3 face features to flash!

 title=

4.3 详细实现过程

4.3.1 组件配置修改

在prj.conf修改组件配置,添加如下的字段。

# 启用flash
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y
# 启动文件系统
CONFIG_FILE_SYSTEM=y
CONFIG_FILE_SYSTEM_LITTLEFS=y

4.3.2 设备树配置修改

修改设备树配置文件,配置挂载点并指定文件系统在flash的偏移地址。

/ {
    fstab {
        compatible = "zephyr,fstab";
        lfs1: lfs1 {
            compatible = "zephyr,fstab,littlefs";
            mount-point = "/lfs1";
            partition = <&storage_partition>;
            automount ;
            no-format;
            read-size = <16>;
            prog-size = <16>;
            cache-size = <64>;
            lookahead-size = <32>;
            block-cycles = <512>;
        };
    };

};

&flash0 {
  partitions {
    algo_cp_partition: partition@400000 {
      label = "algo_cp";
      reg = <0x400000 0x100000>;
    };
    algo_res_partition: partition@500000 {
      label = "algo_res";
      reg = <0x500000 0x800000>;
    };

    /* storage: 1MB for storage */
    storage_partition: partition@0xD00000 {
    label = "storage";
    reg = <0xD00000 0x100000>;
        };
  };
};

4.3.3 在storage.c文件中实现以下函数:

  • 挂载文件系统
    int init_mount_flash()
    {
        int32_t ret = 0;
        snprintf(fname, sizeof(fname), "%s/face_features.bin", mp->mnt_point);
        struct fs_statvfs sbuf;
        ret = littlefs_mount(mp);
        if (ret < 0) {
            return;
        }
    
        ret = fs_statvfs(mp->mnt_point, &sbuf);
        if (ret < 0) {
            LOG_PRINTK("FAIL: statvfs: %d\n", ret);
        }
    
        LOG_PRINTK("%s: bsize = %lu ; frsize = %lu ;"
               " blocks = %lu ; bfree = %lu\n",
               mp->mnt_point,
               sbuf.f_bsize, sbuf.f_frsize,
               sbuf.f_blocks, sbuf.f_bfree);
        return ret;
    }
  • 卸载文件系统

    int unmount_flash()
    {
        int ret = 0;
        ret = fs_unmount(mp);
        LOG_PRINTK("%s unmount: %d\n", mp->mnt_point, ret);
    
        return ret;
    }
    
  • 写已注册的人脸特征数据到flash
    int write_flash_face_data()
    {
        int32_t ret     = 0;
        uint32_t cnt    = 0;
        struct fs_dirent dirent;
        struct fs_file_t file;
    
        fs_file_t_init(&file);
    
        ret = fs_open(&file, fname, FS_O_CREATE | FS_O_RDWR);
        if (ret < 0) {
            LOG_ERR("FAIL: open %s: %d", log_strdup(fname), ret);
            return ret;
        }
    
        ret = fs_stat(fname, &dirent);
        if (ret < 0) {
            LOG_ERR("FAIL: stat %s: %d", log_strdup(fname), ret);
            goto out;
        }
    
        // 获取已注册人脸个数
        ret = storage_get_data_cnt(&cnt);
        CHECK_ERROR(0 == ret, -1);
        LOG_PRINTK("storage_get_data_cnt: %d\n", cnt);
    
        // 先在文件中写已注册人脸个数
        ret = fs_write(&file, &cnt, sizeof(int32_t));
        if (ret < 0) {
            LOG_ERR("FAIL: write %s: %d", log_strdup(fname), ret);
            goto out;
        }
    
        // 接着依次写入人脸特征数据
        float features[384] = {0};
        for(uint32_t i = 0; i < cnt; i++)
        {
            storage_read_data(i, (void *)features, sizeof(features));
            ret = fs_write(&file, features, sizeof(features));
            if (ret < 0) 
            {
                LOG_ERR("FAIL: write %s: %d", log_strdup(fname), ret);
                goto out;
            }
        }
    
    out:
        ret = fs_close(&file);
        if (ret < 0) {
            LOG_ERR("FAIL: close %s: %d", log_strdup(fname), ret);
            return ret;
        }
        return ret;
    }
  • 读存储在flash中的人脸特征数据到运行时结构体

    int read_flash_face_data(int* count)
    {
        int32_t ret     = 0;
        uint32_t cnt    = 0;
        struct fs_dirent dirent;
        struct fs_file_t file;
    
        fs_file_t_init(&file);
    
        ret = fs_open(&file, fname, FS_O_CREATE | FS_O_RDWR);
        if (ret < 0) {
            LOG_ERR("FAIL: open %s: %d", log_strdup(fname), ret);
            return ret;
        }
    
        ret = fs_stat(fname, &dirent);
        if (ret < 0) {
            LOG_ERR("FAIL: stat %s: %d", log_strdup(fname), ret);
            goto out;
        }
    
        // 先读出已经注册的人脸个数
        ret = fs_read(&file, &cnt, sizeof(int32_t));
        if (ret < 0) {
            LOG_ERR("FAIL: read %s: %d", log_strdup(fname), ret);
            goto out;
        }
        LOG_PRINTK("fs_read: %d", cnt);
    
        // 依次读出flash中的人脸特征信息填充到runtime人脸信息结构体中
        float features[384] = {0};
        for(uint32_t i = 0; i < cnt; i++)
        {
            ret = fs_read(&file, features, sizeof(features));
            if (ret < 0) 
            {
                LOG_ERR("FAIL: write %s: %d", log_strdup(fname), ret);
                goto out;
            }
            storage_write((void *)features, sizeof(features));
        }
        *count = cnt;
    out:
        ret = fs_close(&file);
        if (ret < 0) {
            LOG_ERR("FAIL: close %s: %d", log_strdup(fname), ret);
            return ret;
        }
    }
    

    4.3.4 主函数逻辑

    int main()
    {
        init_mount_flash();
        read_flash_face_data();
        ......
        
        while(1)
        {
            ......
            
            if(新注册人脸)
            {
                write_flash_face_data();
            }
            
            ......
        }
        
        unmount_flash();
    }
    

    5. 总结

1、板子资料挺详细的,基础应用和高级应用都有例程。环境搭建方便,基本根据命令无脑操作,好评。

2、AI功能暂时没有开放模型编译器,不能部署自己的模型,可玩性较低,期待开放模型转bin工具链。

参考资料:

https://docs.listenai.com/chips/600X/ai\_usage/fd/intro
https://aijishu.com/a/1060000000365321
https://aijishu.com/a/1060000000361009
推荐阅读
关注数
5175
内容数
100
聆思科技官方专栏,专注AIOT芯片,持续分享有趣的解决方案。商务合作微信:listenai-csk 技术交流QQ群:825206462
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息