Funpack 第二季第一期--“Syntiant TinyML”的简单语音识别模型训练
Syntiant TinyML,可以使语音和传感器应用程序通过微型 USB 连接通过 Edge Impulse 轻松下载经过训练的模型,而无需任何专用硬件。本项目主要使用模块自带的麦克风进行语音识别,控制板载led的打开与关闭。
标签
MPU
Funpack
TinyML
sll
更新2022-07-06
474

内容介绍

FUNPACK2_1

  1. 平台硬件介绍
    1. 如图所示为Syntiant TinyML Board的基本结构框图,本项目使用到RGBLED,SAMD21,NDP101,Micro-phone,收集音频执行相应命令。FlLFUKzLDWKe7v8cdPLeozN7F4fw
    2. SAMD21
      1. 该 SAMD21 系列微控制器基于 ARM Cortex-M0+ 32 位处理器。其工作最高频率可达 48MHz,并且带有最高 256K 字节的可编程闪存。
    3. NDP101
      1. Syntiant ® NDP101 神经决策处理器™,可以使语音和传感器应用程序分别在 140 和 100 微瓦以下运行。其主要功能是加速处理神经网络的决策功能,连接了两个传感器:BMI160 6 轴运动传感器・SPH0641LM4H 麦克风。NDP101 支持多达 560k 个参数和 64 种输出分类
  2. 网络模型训练以及部署
    1. edge impuse平台
      1. 而 Edge impulse 是一个免费的模型训练平台,非常便捷的使用方法,致力于实现嵌入式系统的边缘计算开发,可以上传被训练的数据、打标、NN 模型训练、优化、部署。
    2. 收集数据
      1. 可以使用电脑麦克风、手机麦克风、TinyML板载麦克风收集数据;或者直接上传WAV音频文件。在收集数据使,可选择相对安静的环境进行多次采集,时音频数据集中在1s时间内。在收集完需要的指令数据后,还需采集一定的环境噪声进行训练。 
      2. 采集熟虑的方式FtTZJNIV2kFuVnEFehjjKnisKs5a
      3. 本项目中共采集5种指令标签,每种标签各50条音频,另外有噪声和官方提供的环境音。FgYOxeNkpf0Ocd50CQBATdnSf_XP
  3. 处理数据FksMOsOf3as8HcN4aG3XvYJat56cFgM1qPDrykPPnXzDZ4oaO4pHTAmmFkdKf7VgPHx0WdTaAAcBkUAs3WTx
  4. 模型测试
    1. 创建和下载测试模型文件。FlASOsSmoJz6Xtud_h_jBvTwZyp9
    2.  环境部署
      1. 需要安装Arduino-cil(1.81版本)高版本不可用,之后根据网站提示,烧录麦克风固件去录制声音。arduino-ide和官方的例程需要下载并根据提示使用。
  5. 小结
    1. 我认为edge impuse是一个非常方便的平台。在多人合作,大量样本支持的情况之下,可玩性非常的高,训练出来的模型非常的惊艳。但是我仅仅录制了5条双音节的指令,每条指令50个左右,而且录制时间很集中,没考虑到人声音的不用时间段的差异!后续希望能有一个多人合作的平台。
  6.  部分代码讲解
    1. NDP中断任务
      1. 利用外部中断,告知主控制器信息匹配完成。将定时器4重置,标志位置1。
      2. int ints = 0;
        int old_int = 0;
        // INT pin interrupt from NDP. Simply flag form main() routine to process
        void ndpInt()
        {
            SCB->SCR &= !SCB_SCR_SLEEPDEEP_Msk; // Don't Allow Deep Sleep
            doInt = 1;
            ledTimerCount = 1 * (1000000 / timer_in_uS); // flash LED for 1 second
        
            if (!doingMgmtCmd) {
                timer4.enableInterrupt(true);
            }
        
            ints++;
            // Serial.print(ints);
        }
    2. 定时器4中断任务
      1. Timer 4 interrupt. Handles ALL touches of NDP. Also services USB Audio,当进入timer中断后,会获取匹配信息,并打印出相应的标签。
      2. // Timer 4 interrupt. Handles ALL touches of NDP. Also services USB Audio
        void isrTimer4(struct tc_module *const module_inst)
        {
            digitalWrite(0, LOW);
            int s;
            unsigned int len;
        
            SCB->SCR &= !SCB_SCR_SLEEPDEEP_Msk; // Don't Allow Deep Sleep
            if ((ledTimerCount < 0xffff) && (ledTimerCount > 0)) {
                ledTimerCount--;
                if (ledTimerCount == 0) {
                    timer4TimedOut = 1;
                }
            }
        
        #ifdef WITH_AUDIO
            if (runningFromFlash) {
        
                uint32_t tankRead;
                int i;
        
                for (i = 0; i < 32; i += 4) {
                    tankRead = indirectRead(tankAddress + ((currentPointer - 32 + i) % tankSize));
                    audioBuf[i / 2] = tankRead & 0xffff;
                    audioBuf[(i / 2) + 1] = (tankRead >> 16) & 0xffff;
                }
                currentPointer += 32;
                currentPointer %= tankSize;
            }
            AudioUSB.write(audioBuf, 32); // write samples to AudioUSB
        #else
        
            if (runningFromFlash) {
                currentPointer = indirectRead(startingFWAddress);
        
                int32_t diffPointer = ((int32_t)currentPointer - prevPointer);
        
                if (diffPointer < 0) {
                    diffPointer = (64000 - prevPointer) + currentPointer;
                }
                if (diffPointer >= dataLengthToBeSaved) {
                    len = sizeof(dataBuf);
                    int ret = NDP.extractData((uint8_t *)dataBuf, &len);
        
                    if (ret != SYNTIANT_NDP_ERROR_NONE) {
                        ei_printf("Extracting data failed with error : %d\r\n", ret);
                    }
                    else {
                        if (imu_active == false) {
                            imu_active = true;
                            for (int i = 0; i < (dataLengthToBeSaved / 2); i++) {
                                imu[i] = dataBuf[i];
                            }
        
                            imu_active = false;
                        }
                    }
        
                    prevPointer = currentPointer;
                }
            }
        #endif
        
            if (doInt) {
                doInt = 0;
                // Poll NDP for cause of interrupt (if running from flash)
                if (runningFromFlash) {
                    match = NDP.poll(); //从ndp101获取匹配到的数据
        
                    if (match) {
                        // Light Arduino LED
                        // digitalWrite(LED_BUILTIN, HIGH);
        
                        ei_classification_output(match - 1); //打印出匹配到的标签。
        
                        printBattery(); // Print current battery level
                    }
                }
                else {
                    match = 1;
                }
            }
            digitalWrite(0, HIGH);
        }
    3. 匹配成功
      1. 调用ei_classification_output(match - 1);函数,打印出匹配到的信息。
      2. void ei_classification_output(int matched_feature)
        {
            if (ei_run_impulse_active()) {
        
                ei_printf("\nPredictions:\r\n");
        
                for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
                    ei_printf("    %s: \t%d\r\n", ei_classifier_inferencing_categories[ix],
                        (matched_feature == ix) ? 1 : 0);
                }
        
                on_classification_changed(ei_classifier_inferencing_categories[matched_feature], 0, 0);
            }
        }
        
    4. 处理任务字节
      1. 打印出匹配信息后调用on_classification_changed(ei_classifier_inferencing_categories[matched_feature], 0, 0);函数。此函数是用户可自定义的标签。这里我使用的是抛出标志位。

        void on_classification_changed(const char *event, float confidence, float anomaly_score)
        {
        
            // here you can write application code, e.g. to toggle LEDs based on keywords
            if (strcmp(event, "blink") == 0) {
                Flag.sta = 2;
            }
            if (strcmp(event, "open") == 0) {
                Flag.sta = 1;
            }
            if (strcmp(event, "close") == 0) {
                Flag.sta = 0;
                Serial.print(Flag.sta);
            }
            if (strcmp(event, "red") == 0) {
                Flag.red = 1;
            }
            if (strcmp(event, "green") == 0) {
                Flag.green = 1;
            }
        }
      2. 在得到标志位之后,会在loop中循环检测标志位,循环执行任务。
                static uint16_t count, co_steep;
                if (Flag.sta != 2) {
                    if (Flag.red == 1) {
                        switch (Flag.sta) {
                        case 0:
                            digitalWrite(LED_RED, LOW);
                            Flag.red = 0;
                            Flag.sta = 4;
                            break;
                        case 1:
                            digitalWrite(LED_RED, HIGH);
                            Flag.red = 0;
                            Flag.sta = 4;
                            break;
                        case 2:
                            digitalWrite(LED_RED, HIGH);
                            break;
                        default:
                            break;
                        }
                    }
                    if (Flag.green == 1) {
                        switch (Flag.sta) {
                        case 0:
                            digitalWrite(LED_GREEN, LOW);
                            Flag.green = 0;
                            Flag.sta = 4;
                            break;
                        case 1:
                            digitalWrite(LED_GREEN, HIGH);
                            Flag.green = 0;
                            Flag.sta = 4;
                            break;
                        default:
                            break;
                        }
                    }
                    digitalWrite(LED_BLUE, LOW);
                }
                else {
                    // count++;
                    if (count == 1000) {
                        static uint8_t co, flag = 1;
                        Flag.red = 0;
                        Flag.green = 0;
        
                        if (flag == 1) {
                            co++;
                            if (co == 255) {
                                flag = 0;
                            }
                        }
                        else {
                            co--;
                            if (co == 1) {
                                flag = 1;
                            }
                        }
                        analogWrite(LED_RED, co);
                        analogWrite(LED_GREEN, 255 - co);
                        analogWrite(LED_BLUE, co);
                        co_steep = 50;
                        count = 0;
                    }
                }
                if ((Flag.green == 0) && (Flag.red == 0) && (Flag.sta == 0)) {
                    digitalWrite(LED_RED, LOW);
                    analogWrite(LED_BLUE, 0);
                    digitalWrite(LED_GREEN, LOW);
                }
                if ((Flag.green == 0) && (Flag.red == 0) && (Flag.sta == 1)) {
                    digitalWrite(LED_RED, HIGH);
                    digitalWrite(LED_GREEN, HIGH);
                }
                if (count == 1000) {
                    int a = Flag.red;
                    int b = Flag.sta;
                    int c = Flag.green;
                    char str[50];
                    sprintf(str, "GREEN:%d,RED:%d,FLAG:%d\r\n", c, a, b);
                    Serial.print(str);
                    co_steep = 1;
                    count = 0;
                }
                count += co_steep;
  7. 功能展示
    1. 指令:红灯+打开:红灯亮;红灯+关闭:红灯灭;闪烁:灯光变换;打开:所有灯光打开;关闭:所有灯光关闭。FuXH8SmmfPVHBd9QYH9IMnJudEkcFtrqlon6BPLdtr-0hErW_jcb5EmGFtE5xN2C7iIQr_GW_URdbJ4d4BLb
    2. ps:打开指令虽然模型识别度准确度98以上,但是语音识别不灵敏,需要用到训练模型时的声音。
  8. 心得体会
    1. 这是我第一次接触到机器学习,训练模型。在此之前,仅仅使用过opencv的人脸调捡器,和一些简单的基础滤波。但是在此项目中的声音采集有很大的难度,第一次采集,没有考虑到周边噪声导致训练的模型准确率不高,而且在不说话的情况加会一直匹配到声音。在群友的提示下,我找到安静的环境下去录制声音,并且加上一个噪声的标签来录制环境音。关于对数据的处理edge impuse给出了很好的数据筛选和滤波器,让训练的模型的准确度更加的高。我还有一些不明白的事情,关于我的设备在不同的时间段对声音识别的准确度不一致,例如,"open"我在早上刚训练完测试的时候,是很准确的,但是晚上录制视频的时候就不行了。我认为可能的原因有三种:1.环境温度的改变。2.人声音因为过度使用嗓子,造成音色有偏差。3.板载麦克风采集声音的角度不同。总而言之,在训练出来的模型是可以使用的,在调整各种训练参数后,每一次都会有所提高。这款模块有很好的前景,可以个人很方便的部署离线语音识别,对比之前使用的语音识别块只能找厂家定制声音,我认为DIY一些家庭小设备上非常的好用。

附件下载

funpack2_1_syntiant-syntiant-ndp101-v70.zip
模型验证固件
firmware-syntiant-tinyml.zip
arduino程序

团队介绍

个人
团队成员
sll

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号