• 回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

【 DigiKey DIY原创大赛】在嵌入式终端上基于本地大模型实现的离线语音聊天机器人(1)

引言

今年上半年,我曾在基于RK3566的嵌入式开发板上本地部署并运行大模型,当时仅能在终端界面使用文字进行交互,而我的进一步目标是实现本地的大模型语音交互。但限于我手里的开发板内存不足,无法加载本地的语音识别模型,同时经费有限,只能暂时搁置。下半年,遇到电源网和得捷举办DIY活动,提供600元的经费报销,使得该项目得以继续。

本文将介绍在嵌入式终端上,基于本地大模型实现的多语言离线语音聊天机器人。其中,语音识别、大模型推理、文字转语音(TTS)都是在嵌入式终端基于本地模型实现的,无需接入互联网环境。

特点

-基于嵌入式终端实现

-无需联网,完全离线运行

-多语言支持(中、英)

-代码开源,模型易部署及更换

硬件环境

-Raspberry Pi 5(quad-core Arm Cortex A76 processor @ 2.4GHz, 8GB RAM)

-WM8960 音频模块

-2寸LCD主屏+ 0.96寸OLED双副屏

软件及模型

-Python: 3.11.2

-推理框架:llama-cpp-python

-语音识别:SenseVoice大模型

-推理模型:千问大模型

-文本转语音:Piper TTS

整体框架

(按要求使用Scheme-it绘制)

环境准备

安装llama推理框架

直接用pip安装:

pip install llama-cpp-python

安装SenseVoice依赖

在SenseVoice的Github仓库,提供了requirements.txt。如果是直接使用我提供的源码,无需拉取SenseVoice仓库,requirements.txt存放于\models\SenseVoiceSmall目录。使用pip安装必要的依赖:

pip install -r requirements.txt

安装Piper TTS

Piper TTS是我目前找到的较优离线TTS,语音接近人声,加载速度快,完全离线运行。它无需特别安装,只需要下载编译好的二进制可执行文件,即可使用,我提供的源码已经直接包含,存放于\piper目录。

特别说明:上述安装截图中pip安装含有--break-system-packages选项,这是我的系统Python结构的原因,在其它系统Python环境下,可能是不需要的。

安装音频及显示驱动

本文使用的音频及显示模块,均来自微雪电子,可直接参考对应模块的教程,进行驱动安装即可,如有问题,可联系其技术支持。

-WM8960音频模块驱动安装

-LCD+OLED三联屏显示驱动安装

获取本项目源码

-获取项目源码:

   本项目完整源码,通过gitee开源,可通过git拉取

-放置模型文件:

   受限于git仓库对单个文件的大小的限制,两个较大的模型文件单独提供网盘下载

注:源码网址见文末

代码说明

线程结构

本项目代码主要由多个线程组成,包含按键线程、录音线程、语音识别线程、模型推理线程、文字转语音线程、显示线程等,各线程通过事件进行触发并流转运作。

主要线程代码

Key线程

Device.pin_factory = LGPIOFactory()
# key init
key2 = Button(17)
def key2_pressed():
    start_record_event.set()
    stop_tts_event.set()
    show_record_event.set()
def key2_released():
    stop_record_event.set()
    model_doing_event.set()
    stop_tts_event.clear()
    show_record_event.clear()
# Bind key press event
key2.when_pressed = key2_pressed
key2.when_released = key2_released

该线程主要监听按键的按下和释放事件,以触发语音录制及识别等相关动作。

录音线程

def recording_thread():
    while True:
        start_record_event.wait()
        start_record_event.clear()
        device = "default"
        wavfile = wave.open(f"{current_dir}/record.wav", "wb")
        mic = alsaaudio.PCM(
            alsaaudio.PCM_CAPTURE,
            alsaaudio.PCM_NONBLOCK,
            channels=1,
            rate=44100,
            format=alsaaudio.PCM_FORMAT_S16_LE,
            periodsize=160,
            device=device,
        )
        wavfile.setnchannels(1)
        wavfile.setsampwidth(2)  # PCM_FORMAT_S16_LE
        wavfile.setframerate(44100)
        print("Start speaking...")
        time_start = datetime.now()
        while True:
            if stop_record_event.is_set():
                stop_record_event.clear()
                time_stop = datetime.now()
                print("Stop speaking...")
                wavfile.close()
                if time_stop.timestamp() - time_start.timestamp() >= 1:
                    trig_sensevoice_event.set()
                else:
                    print("The speaking time is too short")
                    model_doing_event.clear()
                break
                # Read data from device
            l, data = mic.read()
            if l:
                wavfile.writeframes(data)
                time.sleep(0.001)

录音线程,在KEY按下后被触发执行循环录制,KEY释放后退出录制。此处还做了简单的录音时长的判断,因为当录音时长过短时,后续的语音识别可能会报错。

语音识别线程

def sensevoice_thread():
    from model import SenseVoiceSmall
    from funasr.utils.postprocess_utils import rich_transcription_postprocess

    model_dir = f"{current_dir}/models/SenseVoiceSmall"
    m, kwargs = SenseVoiceSmall.from_pretrained(model=model_dir, device="cuda:0")
    m.eval()
    senvc_load_done.set()
    print("Load sensevoice model done")

    while True:
        trig_sensevoice_event.wait()
        trig_sensevoice_event.clear()
        res = m.inference(
            data_in=f"{current_dir}/record.wav",
            language="auto",  # "zh", "en", "yue", "ja", "ko", "nospeech"
            use_itn=False,
            ban_emo_unk=False,
            **kwargs,
        )

        text = rich_transcription_postprocess(res[0][0]["text"])
        ask_text_q.put(text)
        trig_llama_event.set()

在录音线程正常执行完成后,触发执行语音识别线程。SenseVoice语音识别使用较为简单,可自动识别多种语言,生成的文本直接放置到消息队列中,供下一步模型推理使用。模块的初始import是较费时间的,为了不影响程序的整体加载时间,所以关于SenseVoice模块的import处理也放置在了线程中,而不是统一放在文件的开头

------无奈的结束分割线(受论坛帖子字数限制,后续见下一帖子)-----

https://www.dianyuan.com/bbs/2778145.html

全部回复(0)
正序查看
倒序查看
现在还没有回复呢,说说你的想法