首先非常感谢聆思科技、极术社区提供了这次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
至此环境已经搭建成功!
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_THRES | float | 人脸检测框门限 | 0~1 |
FD_PARAM_FACE_DETECT_PIXESIZE | float | 检测框最小像素值门限 | 0~640 |
FD_PARAM_FACE_ALIGN_YAWTHRES | float | 头姿检测偏航角门限 | 0~180 |
FD_PARAM_FACE_ALIGN_PITCHTHRES | float | 头姿检测俯仰角门限 | 0~180 |
FD_PARAM_FACE_ALIGN_ROLLTHRES | float | 头姿检测翻滚角门限 | 0~180 |
FD_PARAM_ANTI_SPOOFING_THRES | float | 活体识别门限 | 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
摁键回调函数主要是负责完成人脸注册,特征比较,清楚注册等任务:
- 人脸注册:长按开发板上的“用户按钮”,直到日志信息处出现
face_recognize: success
即表示当前人脸注册成功; - 特征比较:短按开发板上的“用户按钮” ,设备会将当前人脸特征与已注册的人脸库进行逐一比较,如分值超过设定的阈值,则日志信息处会出现
face_calc_similar: success
,如低于设定的阈值,则显示face_calc_similar: fail
。 - 清除注册:信息快速双击开发板上的“用户按钮”,日志信息处出现
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
烧录文件系统中。。。
连上串口,复位后得到输出
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!
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