项目描述:
本次项目使用MAX78000板卡与角度传感器模块ATK-IMU901并结合DTW算法实现手势识别项目。
前期准备:
1.MAX78000:
先运行一下例程检查SDK是否安装成功和板卡是否有问题
/***** Includes *****/
#include <stdio.h>
#include <stdint.h>
#include "mxc_device.h"
#include "led.h"
#include "board.h"
#include "mxc_delay.h"
/***** Definitions *****/
/***** Globals *****/
/***** Functions *****/
// *****************************************************************************
int main(void)
{
int count = 0;
printf("Hello World!\n");
while (1) {
LED_On(LED1);
MXC_Delay(500000);
LED_Off(LED1);
MXC_Delay(500000);
printf("count : %d\n", count++);
}
}
结果:
可以看出SDK没有问题,板卡运行也没有问题,然后就需要继续熟悉MAX78000并将其运用在项目中。
2.ATK-IMU901
ATK-IMU901是新西兰威森公司(Hemisphere)生产的一款先进的惯导组合导航系统(strapdown INS)硬件平台。它集成了三轴陀螺仪、三轴加速度计和三轴磁力计,可以提供精密的角速度、加速度和磁场测量结果。该惯导组件广泛应用于各类导航定位和稳定控制系统中。
该模块是正点原子推出的一款十轴的高性能角度传感器模块。模块内部集 成多个高精度传感器:陀螺仪、加速度计、磁力计、气压计,并采用 32 位 72MHz 主频的高 性能微处理器,快速求解出模块当前的实时运动姿态。模块通过 UART 与外 部主控芯片进行通讯,直接通过 UART 输出姿态角数据,因此外部主控无需进行复杂的姿 态解算,降低了运动处理运算对应用端的负荷,同时也大大降低了开发难度。可用于倾角、姿态角、气压、磁场、海拔高度、温度等的测量,可应用与无人机、云台、 VR、测量仪表等场合。
模块的各项基本参数,如下表所示:
模块的各引脚功能描述,如下表所示:
串口显示数据如下:
通讯协议如下:
代码实现:
for(int count = 0; count < 1006; count++)
{
if(RxData[count] == 0x55 && RxData[count+1] == 0x55)
{
switch (RxData[count+2])
{
case 0x01:
Roll = ((short)(RxData[count+5]<<8 | RxData[count+4])) / 32768.0 * 180;
Pitch = ((short)(RxData[count+7]<<8 | RxData[count+6])) / 32768.0 * 180;
Yaw = ((short)(RxData[count+9]<<8 | RxData[count+8])) / 32768.0 * 180;
printf("Roll = %.3f, Pitch = %.3f, Yaw = %.3f\n", Roll, Pitch, Yaw);
//分析x、y、z角度的异常判断
if( fabs(Pitch)>40 || fabs(Roll)>40 || fabs(Yaw)>40 )
{
mpu_1_flag = true;
}
else
{
mpu_1_flag = false;
}
break;
case 0x02:
Q[0] = ((float)(RxData[count+5] << 8 | RxData[count+4])) / 32768.0;
Q[1] = ((float)(RxData[count+7] << 8 | RxData[count+6])) / 32768.0;
Q[2] = ((float)(RxData[count+9] << 8 | RxData[count+8])) / 32768.0;
Q[3] = ((float)(RxData[count+11] << 8 | RxData[count+10])) / 32768.0;
printf("Q[0] = %.3f, Q[1] = %.3f, Q[2] = %.3f, Q[3] = %.3f\n", Q[0], Q[1], Q[2], Q[3]);
break;
case 0x03:
acc[0] = ((float)(RxData[count+5] << 8 | RxData[count+4])) / 32768.0 * 2 * 9.8; // 2 为设置的1G单位刻度
acc[1] = ((float)(RxData[count+7] << 8 | RxData[count+6])) / 32768.0 * 2 * 9.8;
acc[2] = ((float)(RxData[count+9] << 8 | RxData[count+8])) / 32768.0 * 2 * 9.8;
GyroValue.X = ((float)(RxData[count+11] << 8 | RxData[count+10])) / 32768.0 * 90;
GyroValue.Y = ((float)(RxData[count+13] << 8 | RxData[count+12])) / 32768.0 * 90;
GyroValue.Z = ((float)(RxData[count+15] << 8 | RxData[count+14])) / 32768.0 * 90;
printf("acc[0] = %.3f, acc[1] = %.3f, acc[2] = %.3f, GyroValue.X = %.3f, GyroValue.Y = %.3f, GyroValue.Z = %.3f\n",
acc[0], acc[1], acc[2], GyroValue.X, GyroValue.Y, GyroValue.Z);
sum[0] += GyroValue.X;
sum[1] += GyroValue.Y;
sum[2] += GyroValue.Z;
success_num ++;
SVM = sqrt(pow(acc[0],2)+ pow(acc[1],2) + pow(acc[2],2));
//分析加速度SVM的异常判断
if( SVM>23000 || SVM<12000 )i = 0;
i++;
if( i<=10 )mpu_2_flag = true;
else
{
i = 10;
mpu_2_flag = false;
}
//综合欧拉角、SVM异常判断异常
if( mpu_2_flag || mpu_1_flag )
{
mpu_flag = true;
}
else
{
mpu_flag = false;
}
break;
}
}
}
结果展示:
由结果可知,已成功读取到三轴(Yaw,Pitch,Roll)角度,四元素,陀螺仪和加速度计数据。
动态时间规整( DTW ):
1.来源:
-假定一个孤立词识别系统,利用模板匹配法进行识别。训练阶段,用户将词汇表种每一个词都念一遍,将其特征矢量的时间序列作为模板(template)存入模板库;识别阶段,将输入语音的特征矢量时间序列与模板库中的每个模板进行相似度比较,将相似度最高的最为识别输出。
-实际上,这样做识别率很低,因为语音信号随机性太强了,同一个人在不同时刻讲同一句话,发同一个音,也不可能具有完全相同的时间长度。
-对此,日本学者板仓(Itakura)将动态规划算法(DP)应用于解决说话速度步不均匀的难题,提出DTW算法。
2.DTW算法:
按照距离最近原则,衡量两个长度不同的时间序列的相似度的方法,是一种非线性规整技术。
如下图所示,上下两条实线代表两个时间序列,时间序列之间的虚线代表两个时间序列之间的相似的点。DTW使用所有这些相似点之间的距离的和,称之为归整路径距离(Warp Path Distance)来衡量两个时间序列之间的相似性。
- 要求:
1.单向对应,不能回头,从前往后对齐。
2.一一对应,中间不能有空元素。
3.对应之后,距离最近。
如下图所示,第一个是对的,下面两个都是错的。下左是因为回头了,导致有交叉,所以不对;下右是有空缺。
下面根据一个具体例子:
有A和B两个序列,都是一维特征,长度10。图上两条数是特征值。当然也可以是不同长度的序列。若A长m,B长n,就得到m*n的矩阵。矩阵从(0,0)开始。此处用最简单的欧氏距离衡量dis=|x-y|。该矩阵叫累计距离矩阵。,因为其计算D[i,j]的值是前当前计算值dis(Ai,Bj)加上上一步的最小值,这样相当于每步都在不断累计。
分三种情况,左边第一列,下面第一行,和其他。
最后将整个网络计算完填满,根据最小距离原则找对A和B的对应关系,找到一条最优路径,起点必须(0,0),终点必须(m-1,n-1)。从右上方开始,在左下方的三个点里找最小的一个点,最后反转一下。
3.MAX78000代码实现DTW
float dtw(int seq1_len, int seq2_len, float (*seq1)[3], float (*seq2)[3]) {
float dp[M + 1][M + 1]; // 二维数组用于存储动态规划的结果
for (int i = 0; i <= seq1_len; i++) {
for (int j = 0; j <= seq2_len; j++) {
dp[i][j] = INF;
}
}
dp[0][0] = 0;
for (int i = 1; i <= seq1_len; i++) {
for (int j = 1; j <= seq2_len; j++) {
float cost = 0;
for (int k = 0; k < 3; k++) {
cost += pow(seq1[i-1][k] - seq2[j-1][k], 2);
}
cost = sqrt(cost);
dp[i][j] = cost + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
}
}
return dp[seq1_len][seq2_len];
}
函数 dtw 接受两个长度分别为 seq1_len 和 seq2_len 的时间序列作为输入,并计算它们之间的动态时间规整距离。在函数内部,首先定义了一个二维数组 dp 用于存储动态规划的结果。然后使用两层嵌套的循环遍历这个二维数组,初始化所有元素为一个较大的值(在代码中用 INF 表示)。接着将 dp[0][0] 初始化为0,作为初始状态。 随后的两层嵌套循环用于计算动态规划的结果。在内部的循环中,首先计算当前位置 (i, j) 的代价 cost,这里使用了欧氏距离的计算公式。然后根据动态规划的状态转移方程,更新 dp[i][j] 的值为当前代价 cost 加上从相邻位置转移过来的最小代价。这里使用了 min 函数来计算三个可能的转移路径中的最小值。 最后,函数返回 dp[seq1_len][seq2_len],即时间序列 seq1 和 seq2 之间的动态时间规整距离。 这段代码实现了动态时间规整算法的核心逻辑,通过动态规划的方式高效地计算了两个时间序列之间的相似度。
项目部分代码:
下面是模板数据的采集以及一些处理代码,在1.5s内收集信息,每50ms进行一次采集,采集30组数据作为数据模板,我在此项目中只收集了12个模板,每3个数据模板归为一类手势,第0~2组数据为前后手势,第3~5组数据为左右手势,第6~8组数据为上下手势,第9~11组数据为其他手势。
for(int i=0;i<30;i++)
{
mxc_uart_req_t read_req;
read_req.uart = MXC_UART_GET_UART(READING_UART);
read_req.rxData = RxData;
read_req.rxLen = BUFF_SIZE;
read_req.txLen = 0;
read_req.callback = readCallback;
MXC_UART_ClearRXFIFO(MXC_UART_GET_UART(READING_UART));
#ifdef DMA
if(!update_flag)
{
error = MXC_UART_TransactionDMA(&read_req);
update_flag = true;
}
#else
error = MXC_UART_TransactionAsync(&read_req);
#endif
if (error != E_NO_ERROR) {
printf("-->Error starting async read: %d\n", error);
printf("-->Example Failed\n");
return error;
}
Gyro_sample_update();
for(int j=0;j<3;j++)
{
move_acc[PB1_count-1][i][j] = acc[j];
}
MXC_Delay(1000*50);
}
测试数据的收集与模板数据收集步骤类似,下面是测试代码与模板代码的比较,若测试数据与0~2组数据中某一个的差距比其余的小,则该测试数据的手势检测为前后手势,其余以此类推。
// dtw 算法
printf("dtw algorithm begins\n");
for(int i=0;i<PB1_count;i++)
{
move_dis_avg[i] = dtw(M, M, move_acc[i], move_test);
if(move_dis_avg[i]<min_dis)
{
min_dis = move_dis_avg[i];
min_dis_num = i;
}
}
if(min_dis_num<3)
{
printf("The test result is front and back\n");
}
else if(min_dis_num>=3 && min_dis_num < 6)
{
printf("The test result is left and right\n");
}
else if(min_dis_num>=6 && min_dis_num < 9)
{
printf("The test results are up and down\n");
}
else if(min_dis_num>=9 && min_dis_num < 12)
{
printf("The test results are other\n");
}
printf("End of test\n");
出现的问题:
因为MAX78000是一款可以跑AI,机器学习的嵌入式板卡,所以上手后想跑一跑AI,机器学习相关的工程,并且自带有摄像头模块,所以一开始想做摄像头与AI,也就是视觉方向的项目,但是AI的环境配置一直有问题,我多次向交流群求助,但一直没有回应,以至于现在也没有解决,最终放弃AI方案,然后又想选择简单一些并且综合手上的模块,选择了用MAX78000结合ATK-IMU901来做手势识别。
出现问题如下:
问题1:
描述:WSL2下python版本为3.10.12,pytorch版本为2.1.0,cuda版本为11.8,torchaudio版本为2.1.0,torchmetrics版本为1.2.0,torchnet版本为0.0.4,torchvision版本为0.16.0,并且已经正确安装,环境中都能检查到,但是运行实例训练模型代码出现 ModuleNotFoundError: No module named 'torchmetrics.detection.map'此问题,但是一查发现torchmetrics是安装成功的,所以不知道是哪里出了问题,需要解决方法,但是多次在群里提出此问题都没有解决,希望可以在此次报告提出后能得到尽快解决,否则我就很难用MAX78000运行AI,机器学习等相关工程,也就不好去做相关AI项目,我现在有IMU传感器,做运动状态识别,用AI,机器学习来可能会更好做,如果用不了就只有用算法来实现,我希望可以及早将这个问题解决,让我可以更好的了解边缘AI,我也希望能更好的将AI运用于嵌入式中。
最终上述问题没有得到解决,在与ADI工程师多次沟通后,工程师认为是我设备的问题,所以我没有能够使用上嵌入式AI,没有用上它AI部分,只是用算法来实现了类似于AI的功能。
问题2:
我的屏幕是ILI9488
屏幕显示问题,屏幕会闪一下,然后又全白,参考了这位大佬
主程序修改 main.c:设置RESET、BLK控制引脚
#ifdef ENABLE_TFT
/* Initialize TFT display */
mxc_gpio_cfg_t tft_reset_pin = {MXC_GPIO0, MXC_GPIO_PIN_19, MXC_GPIO_FUNC_OUT, MXC_GPIO_PAD_NONE, MXC_GPIO_VSSEL_VDDIOH};
mxc_gpio_cfg_t tft_blen_pin = {MXC_GPIO0, MXC_GPIO_PIN_9, MXC_GPIO_FUNC_OUT, MXC_GPIO_PAD_NONE, MXC_GPIO_VSSEL_VDDIOH};
MXC_TFT_Init(MXC_SPI0, 1, &tft_reset_pin, &tft_blen_pin);
// MXC_TFT_Init(MXC_SPI0, 1, NULL, NULL);
TFT_Feather_test();
#endif
样式配置文件example_config.h添加开发板定义,方便编辑器解析
#ifndef EXAMPLES_MAX78000_TFT_DEMO_EXAMPLE_CONFIG_H_
#define EXAMPLES_MAX78000_TFT_DEMO_EXAMPLE_CONFIG_H_
#include "board.h"
#define BOARD_FTHR_REVA
#ifdef BOARD_EVKIT_V1
#include "tft_ssd2119.h"
#include "bitmap.h"
// Enable TFT display
#define ENABLE_TFT
// Enable Touchscreen
#define ENABLE_TS
#endif
#ifdef BOARD_FTHR_REVA
#include "tft_ili9341.h"
// 添加触摸板调用文件
#include "tsc2046.h"
// Enable TFT display:开启TFT显示
#define ENABLE_TFT
// Enable Touchscreen:开启触摸屏功能
#define ENABLE_TS
#endif
#endif
工程配置文件:project.mk中修改
# Add your config here!
IPATH += resources
BOARD=FTHR_RevA
# Place build files specific to EvKit_V1 here.
ifeq "$(BOARD)" "EvKit_V1"
VPATH += resources/tft_evkit
endif
# Place build files specific to FTHR_RevA here.
ifeq "$(BOARD)" "FTHR_RevA"
VPATH += resources/tft_fthr
endif
ifeq ($(BOARD),Aud01_RevA)
$(error ERR_NOTSUPPORTED: This project is not supported for the Audio board)
endif
参考大佬的改了上面三处,但屏幕显示还是不行,会闪一下然后又全屏白。买的屏幕到了,就想着给项目加一个新功能,或者也可以调一调图像等,但冒出此问题没有解决。
项目改进:
现在的话,数据量少识别的手势也比较少,可以录入一定量的数据,让本项目识别更多的手势,并且识别得更精确,该算法的应用场景很广,除了本项目中的手势识别,还可以用在语音识别等有一系列时间序列数据的场合。
后续如果屏幕问题解决,也可以将此项目结合屏幕做一些有意思的设计。
总结 :
可惜了,MAX78000明明是一块边缘计算设备,本应该可以接触了解嵌入式AI开发的全过程,但鉴于设备原因,无法将其用于更好的嵌入式AI方案,但与之相代替的我用了一个DTW算法来实现类似AI的效果,同时也更加熟悉了DTW算法和嵌入式方面的开发。 感谢硬禾举办这样一个活动,给了我一次探索嵌入式AI的机会,也要感谢美信和ADI工程师们提供的详细的文档,虽然有坑,但已经比较完善了,在很大程度上方便了我的开发。