发 帖  
[经验]

【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制

2024-6-30 10:49:20  29063 Cortex-A
0
zealsoft 2024-7-2 21:40:08
回复

举报

zealsoft 2024-7-4 19:43:23
板凳
回复

举报

zealsoft 2024-7-5 19:59:45
3#

昨天提到要使模型运行的NPU上,必须先将其量化。如果对没有量化的模型使用vela工具进行转换,工具会给出警告,所生成的模型仍然是只能运行在CPU上,而无法运行在NPU上的。

下面就是用vela工具对simple_audio_model_numpy.tflite文件进行转换的结果。


root@atk-imx93:~/shell/simple# vela simple_audio_model_numpy.tflite
Warning: Unsupported TensorFlow Lite semantics for RESIZE_BILINEAR 'sequential_2/resizing_2/resize/ResizeBilinear;StatefulPartitionedCall/sequential_2/resizing_2/resize/ResizeBilinear'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: input_3, sequential_2/resizing_2/resize/ResizeBilinear;StatefulPartitionedCall/sequential_2/resizing_2/resize/ResizeBilinear
Warning: Unsupported TensorFlow Lite semantics for CONV_2D 'sequential_2/conv2d_4/Relu;StatefulPartitionedCall/sequential_2/conv2d_4/Relu;sequential_2/conv2d_4/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd;sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd/ReadVariableOp2'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: sequential_2/resizing_2/resize/ResizeBilinear;StatefulPartitionedCall/sequential_2/resizing_2/resize/ResizeBilinear, sequential_2/conv2d_4/Relu;StatefulPartitionedCall/sequential_2/conv2d_4/Relu;sequential_2/conv2d_4/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd;sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd/ReadVariableOp1_reshape, sequential_2/conv2d_4/Relu;StatefulPartitionedCall/sequential_2/conv2d_4/Relu;sequential_2/conv2d_4/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd;sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd/ReadVariableOp2
Warning: Unsupported TensorFlow Lite semantics for CONV_2D 'sequential_2/conv2d_5/Relu;StatefulPartitionedCall/sequential_2/conv2d_5/Relu;sequential_2/conv2d_5/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_5/BiasAdd;sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/BiasAdd/ReadVariableOp'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: sequential_2/conv2d_4/Relu;StatefulPartitionedCall/sequential_2/conv2d_4/Relu;sequential_2/conv2d_4/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd;sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_4/BiasAdd/ReadVariableOp2, sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/Conv2D_reshape, sequential_2/conv2d_5/Relu;StatefulPartitionedCall/sequential_2/conv2d_5/Relu;sequential_2/conv2d_5/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_5/BiasAdd;sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/BiasAdd/ReadVariableOp
Warning: Unsupported TensorFlow Lite semantics for MAX_POOL_2D 'sequential_2/max_pooling2d_2/MaxPool;StatefulPartitionedCall/sequential_2/max_pooling2d_2/MaxPool'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: sequential_2/conv2d_5/Relu;StatefulPartitionedCall/sequential_2/conv2d_5/Relu;sequential_2/conv2d_5/BiasAdd;StatefulPartitionedCall/sequential_2/conv2d_5/BiasAdd;sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/Conv2D;StatefulPartitionedCall/sequential_2/conv2d_5/BiasAdd/ReadVariableOp, sequential_2/max_pooling2d_2/MaxPool;StatefulPartitionedCall/sequential_2/max_pooling2d_2/MaxPool
Warning: Unsupported TensorFlow Lite semantics for RESHAPE 'sequential_2/flatten_2/Reshape;StatefulPartitionedCall/sequential_2/flatten_2/Reshape'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: sequential_2/max_pooling2d_2/MaxPool;StatefulPartitionedCall/sequential_2/max_pooling2d_2/MaxPool, sequential_2/flatten_2/Reshape;StatefulPartitionedCall/sequential_2/flatten_2/Reshape
Warning: Unsupported TensorFlow Lite semantics for FULLY_CONNECTED 'sequential_2/dense_4/Relu;StatefulPartitionedCall/sequential_2/dense_4/Relu;sequential_2/dense_4/BiasAdd;StatefulPartitionedCall/sequential_2/dense_4/BiasAdd'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: sequential_2/flatten_2/Reshape;StatefulPartitionedCall/sequential_2/flatten_2/Reshape, sequential_2/dense_4/MatMul;StatefulPartitionedCall/sequential_2/dense_4/MatMul_reshape, sequential_2/dense_4/Relu;StatefulPartitionedCall/sequential_2/dense_4/Relu;sequential_2/dense_4/BiasAdd;StatefulPartitionedCall/sequential_2/dense_4/BiasAdd
Warning: Unsupported TensorFlow Lite semantics for FULLY_CONNECTED 'Identity'. Placing on CPU instead
- Input(s), Output and Weight tensors must have quantization parameters
   Op has tensors with missing quantization parameters: sequential_2/dense_4/Relu;StatefulPartitionedCall/sequential_2/dense_4/Relu;sequential_2/dense_4/BiasAdd;StatefulPartitionedCall/sequential_2/dense_4/BiasAdd, sequential_2/dense_5/MatMul;StatefulPartitionedCall/sequential_2/dense_5/MatMul_reshape, Identity

Network summary for simple_audio_model_numpy
Accelerator configuration               Ethos_U65_256
System configuration                 internal-default
Memory mode                          internal-default
Accelerator clock                                1000 MHz


CPU operators = 7 (100.0%)
NPU operators = 0 (0.0%)

Neural network macs                                 0 MACs/batch
Network Tops/s                                    nan Tops/s

NPU cycles                                          0 cycles/batch
SRAM Access cycles                                  0 cycles/batch
DRAM Access cycles                                  0 cycles/batch
On-chip Flash Access cycles                         0 cycles/batch
Off-chip Flash Access cycles                        0 cycles/batch
Total cycles                                        0 cycles/batch

Batch Inference time                 0.00 ms,     nan inferences/s (batch size 1)

Warning: Could not write the following attributes to RESHAPE 'sequential_2/flatten_2/Reshape;StatefulPartitionedCall/sequential_2/flatten_2/Reshape' ReshapeOptions field: new_shape

这个错误信息明确指出Vela不支持 TensorFlow Lite 对特定操作的支持问题。具体来说,这个警告说明了:量化参数缺失 ,错误信息指出,涉及的输入、输出和权重张量必须具有量化参数,但在这个操作中,某些张量(如 input_3sequential_2/resizing_2/resize/ResizeBilinear)缺失了这些量化参数。由于不支持,相关的操作将被放置在 CPU 上执行,而不是利用可能存在的更高效的硬件加速(NPU)。

我们使用netron.app可以查看一下模型文件。


FireShot Capture 134 - simple_audio_model_numpy.tflite - netron.app.png

从中可以看到input_3是float32类型的。


而查看被vela支持的模型,可以看到其输入参数已经被量化,是int8类型的。
FireShot Capture 135 - kws_ref_model.tflite - netron.app.png


如果我们想利用i.MX 93的NPU能力就需要先对模型文件进行量化。当然如果觉得i.MX 93的CPU推理能力已经够用了,此步骤也可以省略。



回复

举报

zealsoft 2024-7-8 19:50:50
4#

接下来我就想把录音和关键词识别整合在一个程序里面。


Python中进行语音操作首先想到的是PyAudio。不过在板子上安装PyAudio遇到一点麻烦。Python仓库里面并没有现成的对应这个板子的软件包,需要在板子上编译生成软件包,而PyAudio又依赖PortAudio,而PortAudio在板子上没有移植,所以PyAudio暂时用不了,这个问题以后再想办法解决。


我采用的临时办法是修改前面提到的测试音频的shell脚本,由它录制1秒的语音,然后调用Python程序进行关键字识别,如果是YES就打开开发板上的LED灯,如果是NO就关闭开发板上的LED灯,开灯或关灯完成之后会播放相应的提示音。为了方便调试,在录音之后会自动播放录音结果,以确定录音是否正确。


完整的程序见压缩包*附件:yes-no-test.zip。核心的脚本代码如下:


#!/bin/bash

INIT_FLAG="/home/root/shell/audio/.initialized_audio_device"
RECORD_FILE="test.wav"
PLAY_FILE="/home/root/shell/audio/short.mp3"
RC_LOCAL_FILE="/etc/rc.local"
DELETE_COMMAND="rm -f $INIT_FLAG"

# 将开机自动删除音频初始化标志文件命令加入到开机自启中
add_command() {
    if ! grep -qFx "$DELETE_COMMAND" "$RC_LOCAL_FILE"; then
        echo "$DELETE_COMMAND" >> "$RC_LOCAL_FILE"
        sync
    fi
}
   
# 检查命令是否存在
check_command() {
    command -v "$1" > /dev/null 2>&1
}

# 初始化音频设备
init_audio_device() {
    amixer cset name='PCM Volume' 192
    amixer cset name='Mono Mux' 'Stereo'
……
    amixer cset name='Left PGA Mux' 'DifferentialL'
    amixer cset name='Right PGA Mux' 'DifferentialR'
    # touch "$INIT_FLAG"
}

# 检查是否已初始化
check_initialized() {
    [ -e "$INIT_FLAG" ]
}

function init_board_mic() {
    # 初始化板载麦克风
    check_command amixer && {
        amixer -q cset name='Differential Mux' 'Line 2'
        amixer -q cset name='Left Line Mux' 'Line 2L'
        amixer -q cset name='Right Line Mux' 'Line 2R'
    }
}

function init_headphone_mic() {
    # 初始化耳机麦克风
    check_command amixer && {
        amixer cset name='Differential Mux' 'Line 1'
        amixer cset name='Left Line Mux' 'Line 1L'
        amixer cset name='Right Line Mux' 'NC'
    }
}

function cleanup() {
    # 清理并退出
    printf "\\\\n清理并退出...\\\\n"
    stty sane  # 还原终端状态
    exit 0
}

function switch_mode() {
    # 录音/播音模式切换
    case $1 in
        1)
            # 进入录音模式
            check_command amixer && {
                amixer -q cset name='Left Mixer Left Bypass Switch' 'on'
                amixer -q cset name='Right Mixer Right Bypass Switch' 'on'
                amixer -q cset name='Left Mixer Left Playback Switch' 'off'
                amixer -q cset name='Right Mixer Right Playback Switch' 'off'
            }
            ;;
        2)
            # 进入播音模式
            check_command amixer && {
                amixer -q cset name='Left Mixer Left Bypass Switch' 'off'
                amixer -q cset name='Right Mixer Right Bypass Switch' 'off'
                amixer -q cset name='Left Mixer Left Playback Switch' 'on'
                amixer -q cset name='Right Mixer Right Playback Switch' 'on'
            }
            ;;
        3)
            # 关闭录音和播音模式
            check_command amixer && {
                amixer -q cset name='Left Mixer Left Bypass Switch' 'off'
                amixer -q cset name='Right Mixer Right Bypass Switch' 'off'
                amixer -q cset name='Left Mixer Left Playback Switch' 'off'
                amixer -q cset name='Right Mixer Right Playback Switch' 'off'
                amixer -q cset name='Left Line Mux' 'NC'
                amixer -q cset name='Right Line Mux' 'NC'
            }
            ;;       
    esac
}

function apply_config() {
    # printf "\\\\n可选麦克风测试项目:\\\\n"
    # printf "1. 耳机麦克风\\\\n"
    # printf "2. 板载麦克风\\\\n"

    # while true; do
    #     read -r -p "请输入您的选择: " choice

    #     if [[ "$choice" == "1" || "$choice" == "2" ]]; then
    #         break
    #     else
    #         printf "无效输入。请输入1或2。\\\\n"
    #     fi
    # done
    choice=2
    printf "\\\\n应用麦克风配置项 %s\\\\n" "$choice"
    case $choice in
        1)
            switch_mode 1
            init_headphone_mic
            ;;
        2)
            switch_mode 1
            init_board_mic
            ;;
        *)
            printf "无效选项\\\\n"
            ;;
    esac
}

# 捕获Ctrl+C信号,并调用cleanup函数
trap cleanup SIGINT

# 检查是否已初始化,如果没有,则进行初始化
# if ! check_initialized; then
#     printf "第一次运行,执行音频设备初始化...\\\\n"
    init_audio_device
#     add_command
# fi

while true; do
    while true; do
        command=1
        case $command in
            1)
                apply_config
                printf "\\\\n开始录音...\\\\n"
                #sleep 1
                check_command arecord && arecord -f cd -d 1 -r 16000 "$RECORD_FILE"
                switch_mode 2
                printf "\\\\n播放录音...\\\\n"
                check_command aplay && aplay "$RECORD_FILE"
                        switch_mode 3
                # 调用Python程序并捕获其输出  
                output=$(python3 simple_audio.py --input=test.wav)  
                echo "$output"

                # 检查输出是否包含">>> YES"  
                if echo "$output" | grep -q ">>> YES"; then  
                    echo "Python程序输出YES,执行相应代码..."  
                        echo 1 > /sys/class/leds/sys-led/brightness
                        echo heartbeat > /sys/class/leds/sys-led/trigger
                    # 在这里添加当输出为YES时需要执行的代码  
                    switch_mode 2
                    #gst-play-1.0 haodeyiweinindakai.mp3
                        aplay haodeyiweinindakai.wav
                    switch_mode 3
                    
                elif echo "$output" | grep -q ">>> NO"; then  
                    echo "Python程序输出NO,执行其他代码..."  
                        echo none > /sys/class/leds/sys-led/trigger
                        echo 0 > /sys/class/leds/sys-led/brightness
                    # 在这里添加当输出为NO时需要执行的代码  
                    switch_mode 2
                    #gst-play-1.0 haodeyiweininguanbi.mp3      
                        aplay haodeyiweininguanbi.wav
                    switch_mode 3
                else  
                    echo "Python程序输出未知结果,或者没有输出结果。"  
                    # 可以选择添加处理未知输出的代码  
                fi
                #break
                ;;
            2)
                switch_mode 2
                printf "\\\\n开始播音,按 Ctrl+C 可退出播音\\\\n"
                gst-play-1.0 --audiosink="alsasink" "$PLAY_FILE"
                        switch_mode 3
                #break
                ;;
            *)
                cleanup
                ;;
        esac
    done

    break
done

从下面的视频看,基本实现了所需要的效果。原来担心板子的麦克风录音效果会影响识别,目前看问题不大。由于采用的是先录音成文件的方法,而且时间仅有1秒,所以使用时还是比较麻烦的,有的时候说话稍微慢了点就没有录制完全。这个需要后续优化一下Python的语音处理。

视频:

另外,目前使用的模型是预训练好的,后面计划会训练中文的提示词,以方便使用。


1 回复

举报

zealsoft 2024-7-10 20:31:01
5#
本帖最后由 zealsoft 于 2024-7-14 17:25 编辑

接下来就是要尝试训练中文提示词。首先要进行语料采集,这是一个比较耗费人力的事情,通常大公司会有有专人进行语料收集,我只好自己亲自做。这里参考了AliOS Things里面提供的一个录音工具,方便快速录音。对这个工具做了一点修改,原来的代码只能在Linux下运行,现在改成在Windows下也能运行。

  1. import pyaudio
  2. import wave
  3. import random
  4. import time
  5. import os
  6. from IPython import display
  7. #from pydub import AudioSebment
  8. #from pydub.playback import play
  9. #from playsound import playsound
  10. CHUNK = 2
  11. FORMAT = pyaudio.paInt16
  12. CHANNELS = 1
  13. RATE = 16000
  14. SAMPLEWIDTH = 2
  15. RECORD_SECONDS = 1
  16. FILE_FORMAT = '.wav'
  17. RECODER_NAME = 'lk'
  18. #play stream
  19. def play_wav(name, pyaudio):
  20.     f = wave.open(name,"rb")  
  21.     #open stream  
  22.     play_stream = pyaudio.open(format = p.get_format_from_width(f.getsampwidth()),  
  23.                     channels = f.getnchannels(),  
  24.                     rate = f.getframerate(),  
  25.                     output = True)  
  26.     #read data  
  27.     data = f.readframes(CHUNK)
  28.     while data:  
  29.         play_stream.write(data)
  30.         data = f.readframes(CHUNK)
  31.     #stop stream
  32.     play_stream.stop_stream()  
  33.     play_stream.close()  
  34.     #close PyAudio  
  35. #    pyaudio.terminate()
  36.     f.close()
  37. def save_wav(name, frames):
  38.     wf = wave.open(name, 'wb')
  39.     wf.setnchannels(CHANNELS)
  40.     wf.setsampwidth(p.get_sample_size(FORMAT))
  41.     wf.setframerate(RATE)
  42.     wf.writeframes(b''.join(frames))
  43.     wf.close()  
  44. def record_wav(duration):
  45.     time.sleep(0.2) # 1sec, 0.1sec
  46.     print("开始录音,请说话......")
  47. #    count = 3
  48. #    for i in range(3):
  49. #        time.sleep(0.2) # 1sec, 0.1sec
  50. #        count -= 1
  51. #        print(count)
  52.     frames = []
  53.     stream = p.open(format=FORMAT,
  54.             channels=CHANNELS,
  55.             rate=RATE,
  56.             input=True,
  57.             frames_per_buffer=CHUNK)
  58.     for i in range(0, int(RATE * duration / SAMPLEWIDTH)):
  59.         data = stream.read(CHUNK, exception_on_overflow = False)
  60.         frames.append(data)
  61.     #count = 0
  62.     #while count < int(RECORD_SECONDS * RATE):
  63.     #    data = stream.read(CHUNK)
  64.     #    frames.append(data)
  65.     #     count += CHUNK
  66.     stream.stop_stream()
  67.     stream.close()
  68.     print("录音结束!")
  69.     return frames
  70. # main function
  71. if __name__ == '__main__':
  72.     p = pyaudio.PyAudio()
  73.     # input('请按回车键开始录制!\n')
  74.     # record files
  75.     count = 0
  76.     for i in range(250):
  77.         input('请按回车键开始录制!\n')
复制代码

回复

举报

zealsoft 2024-8-4 15:28:20
6#

好久没有更新了,今天再来更新一下。


我们用前面提到的录音工具录制了自己的中文语音,包括“打开”和“关闭”各100条,同时我们从谷歌的mini_speech_commands样本集里面随机挑选了100条作为"unknown”的样本,三个类别的数据个数要尽量相同,否则训练出来的结果会有倾向性。然后,开始自己的训练过程。


我所使用的是阿里云的PAI-DSW进行在线训练,平台的使用非常方便,避免了在本机上进行繁琐的设置工作。我采用的训练笔记本是TensorFlow的Simple audio recognition: Recognizing keywordssimple_audio_pi/simple_audio_train_numpy.ipynb



将脚本上传后,直接打开,就可以看到笔记本了。


笔记本的操作和其他平台差不多,就不详细介绍了。


我把准备的语音数据上传到data/speech目录下,共有3个子目录,分别是open、close和unknown。然后修改脚本中关于data_dir的设置。


`data_dir = pathlib.Path('data/speech')`

然后修改了训练集、验证集和测试集的数量设置。


train_files = filenames[:350]
val_files = filenames[250: 250 + 100]
test_files = filenames[-100:]

然后就按照笔记本里面的步骤执行就可以了。


数据量不大,训练只用了数秒就完成了。


使用一个样本进行测试,可以正确得到打开的结果。


1722756220971.png


最后可以得到tflite格式的文件,用于在开发板上的测试。


将tflite格式的文件拷贝到开发板上,并修改前面的测试程序中的模型文件路径和commands设置就可以使用中文的“打开”、“关闭”进行控制了。视频稍后将上传到B站,欢迎大家观看。


回复

举报

只有小组成员才能发言,加入小组>>

328个成员聚集在这个小组

加入小组

精选推荐

最新话题

热门话题

创建小组步骤

快速回复 返回顶部 返回列表
关注微信公众号

电子发烧友网

电子发烧友开云(中国)官方

社区合作
刘勇
联系电话:15994832713
邮箱地址:liuyong@huaqiu.com
社区管理
elecfans短短
微信:elecfans_666
邮箱:users@huaqiu.com
关闭

站长推荐 上一条 /6 下一条

快速回复 返回顶部 返回列表
-

技术社区

张飞电子技术社区

KaihongOS技术社区

FPGA开发者技术社区

RISC-V MCU技术社区

HarmonyOS技术社区

-

OpenHarmony开源社区

OpenHarmony开源社区

-

嵌入式开云(中国)官方

ARM技术开云(中国)官方

STM32/STM8技术开云(中国)官方

嵌入式技术开云(中国)官方

单片机/MCU开云(中国)官方

RISC-V技术开云(中国)官方

瑞芯微Rockchip开发者社区

FPGA|CPLD|ASIC开云(中国)官方

DSP开云(中国)官方

-

电路图及DIY

电路设计开云(中国)官方

DIY及创意

电子元器件开云(中国)官方

专家问答

-

电源技术开云(中国)官方

电源技术开云(中国)官方

无线充电技术

-

综合技术与应用

机器人开云(中国)官方

USB开云(中国)官方

电机控制

模拟技术

音视频技术

综合技术交流

上位机软件(C/Python/Java等)

-

无线通信开云(中国)官方

WIFI技术

蓝牙技术

天线|RF射频|微波|雷达技术

-

EDA设计开云(中国)官方

PCB设计开云(中国)官方

DigiPCBA开云(中国)官方

Protel|AD|DXP开云(中国)官方

PADS技术开云(中国)官方

Allegro开云(中国)官方

multisim开云(中国)官方

proteus开云(中国)官方|仿真开云(中国)官方

KiCad EDA 中文开云(中国)官方

DFM|可制造性设计开云(中国)官方

-

测试测量开云(中国)官方

LabVIEW开云(中国)官方

Matlab开云(中国)官方

测试测量技术

传感技术

-

招聘/交友/外包/交易/杂谈

项目外包

供需及二手交易

工程师杂谈|交友

招聘|求职|工程师职场

-

官方社区

发烧友官方/活动

华秋商城

华秋电路