树莓派语音聊天机器人

前期工作

本人拟在树莓派中设计一个GUI,GUI包括基于OPCV的人脸识别、基于YOLOV3的目标检测、图灵聊天机器人等。前期已经完成了多个功能,在做语音聊天机器人的时候遇到了些困难,该博客用于记录聊天机器人开发过程。

基本配置:树莓派4B,屏幕,键鼠,带3.5mm音频线的USB摄像头,Raspbain系统,python3.7。

遗留问题:在写用麦克风采集声音的程序时参考多个文章也没有实现声音采集的功能,个人感觉可能是树莓派自带的3.5mm音频口只能用来播放声音,采集还是需要外界声卡。上述说法如有问题恳请大家评论区告知,或大家有什么想法也可以评论区交流。

DAY1

参考多篇博客:
树莓派简易聊天机器人:https://blog.csdn.net/yonglisikao/article/details/82314512

树莓派进行录音:https://blog.csdn.net/qq_44391957/article/details/88867021

【小白教程】基于树莓派的智能语音助手-python:
https://blog.csdn.net/qq_43581335/article/details/96433550

python-读写Wave文件及分析:
https://blog.csdn.net/sinat_33588424/article/details/80239375

可能是因为没有声卡的原因,进行录音会提示‘no input device’,用‘arecord -l’显示录音设备也显示没有card,等买的外接声卡到了再试试。

DAY2

插上外置声卡后先录音
树莓派进行录音:https://blog.csdn.net/qq_44391957/article/details/88867021
但是后续参考树莓派简易聊天机器人处理时发现百度的语音识别需要采样率为16000,而用上述方法录音采样率设置为16000时发生错误’invalid sample rate‘。此外,由于使用的时python3.7,上文中’baidu-aip,requests,webrtcvad‘的安装需要用’pip3 install‘。接着先是尝试使用librosa库直接进行降采样,但是pip3 install librosa 一直出错。不过使用命令行代码’arecord -D “plughw:1” -f S16_LE -r 16000 -d 3 /home/pi/Desktop/voice.wav‘结合上文的百度语音识别功能可以成功将录音中的语音转换为文本。

DAY3

针对之前用pyaudio设置采样率为16000出错的问题,可以使用‘arecord’指令录制一定时长的语音

os.system('sudo arecord -D "plughw:1" -f S16_LE -r 16000 -d 3 /home/pi/recsound.wav')

也可以使用降采样的方法,该方法主要借鉴了以下文章。
librosa降采样https://blog.csdn.net/qq_45239614/article/details/105780341
sox工具包降采样https://blog.csdn.net/LCCFlccf/article/details/100546967
带有VAD的语音录取https://blog.csdn.net/EmithFla/article/details/105224330
本文采取了带有VAD语音录取+sox工具包降采样+百度语音识别+图灵对话机器人+百度语音合成最终实现基于树莓派的对话系统。程序见下文。

使用代码

带有VAD的录音程序

使用pyaudio录制采样率48000的语音‘record.wav’,再使用sox工具包降采样为16000

import webrtcvad   # 检测判断一组语音数据是否为空语音;
import collections
import sys
import os
import signal
import pyaudio    # 从设备节点读取原始音频流数据,音频编码是PCM格式

from array import array
from struct import pack
import wave
import time
import os


FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 48000
CHUNK_DURATION_MS = 10       # //检验音频帧长度,只支持10/20/30ms
PADDING_DURATION_MS = 1500   # 1 sec jugement
CHUNK_SIZE = int(RATE * CHUNK_DURATION_MS / 1000)  # chunk to read
CHUNK_BYTES = CHUNK_SIZE * 2  # 16bit = 2 bytes, PCM
NUM_PADDING_CHUNKS = int(PADDING_DURATION_MS / CHUNK_DURATION_MS)
NUM_WINDOW_CHUNKS = int(240 / CHUNK_DURATION_MS)
# NUM_WINDOW_CHUNKS = int(400 / CHUNK_DURATION_MS)  # 400 ms/ 30ms  ge
NUM_WINDOW_CHUNKS_END = NUM_WINDOW_CHUNKS * 2


def handle_int(sig, chunk):
    global leave, got_a_sentence
    leave = True
    got_a_sentence = True


def record_to_file(path, data, sample_width):
    "Records from the microphone and outputs the resulting data to 'path'"
    # sample_width, data = record()
    data = pack('<' + ('h' * len(data)), *data)
    wf = wave.open(path, 'wb')
    wf.setnchannels(1)
    wf.setsampwidth(sample_width)
    wf.setframerate(RATE)
    wf.writeframes(data)
    wf.close()


def normalize(snd_data):
    "Average the volume out"
    MAXIMUM = 32767  # 16384
    times = float(MAXIMUM) / max(abs(i) for i in snd_data)
    r = array('h')
    for i in snd_data:
        r.append(int(i * times))
    return r

signal.signal(signal.SIGINT, handle_int)

"""
当检测到持续时间长度 T1 vad检测都有语音活动,可以判定为语音起始。

当检测到持续时间长度 T2 vad检测都没有有语音活动,可以判定为语音结束。
""" 

def record_sound(file_path='test_recsound_vad.wav'):
    # 录音,有声音自动写入文件,默认为'record.wav',声音结束后录音也停止,调用一次,录制一个片段
    vad = webrtcvad.Vad(1) # 这个参数可为1,2,3,可改变灵敏度,越大越粗犷
    pa = pyaudio.PyAudio()
    stream = pa.open(format=FORMAT,
                     channels=CHANNELS,
                     rate=RATE,
                     input=True,
                     start=False,
                     # input_device_index=2,
                     frames_per_buffer=CHUNK_SIZE)

    got_a_sentence = False
    leave = False
    no_time = 0

    while not leave:
        ring_buffer = collections.deque(maxlen=NUM_PADDING_CHUNKS)
        triggered = False
        voiced_frames = []
        ring_buffer_flags = [0] * NUM_WINDOW_CHUNKS
        ring_buffer_index = 0

        ring_buffer_flags_end = [0] * NUM_WINDOW_CHUNKS_END
        ring_buffer_index_end = 0
        buffer_in = ''
        # WangS(原作者的名字)
        raw_data = array('h')
        index = 0
        start_point = 0
        StartTime = time.time()
        print("* recording: ")
        stream.start_stream()

        while not got_a_sentence and not leave:
            chunk = stream.read(CHUNK_SIZE)
            # add WangS
            raw_data.extend(array('h', chunk))
            index += CHUNK_SIZE
            TimeUse = time.time() - StartTime

            active = vad.is_speech(chunk, RATE)

            sys.stdout.write('~' if active else '_')
            ring_buffer_flags[ring_buffer_index] = 1 if active else 0
            ring_buffer_index += 1
            ring_buffer_index %= NUM_WINDOW_CHUNKS

            ring_buffer_flags_end[ring_buffer_index_end] = 1 if active else 0
            ring_buffer_index_end += 1
            ring_buffer_index_end %= NUM_WINDOW_CHUNKS_END

            # 开始端点检测
            if not triggered:
                ring_buffer.append(chunk)
                num_voiced = sum(ring_buffer_flags)
                if num_voiced > 0.8 * NUM_WINDOW_CHUNKS:      # 声音起始
                    sys.stdout.write(' Open ')
                    triggered = True
                    start_point = index - CHUNK_SIZE * 20  # start point
                    # voiced_frames.extend(ring_buffer)
                    ring_buffer.clear()
            # 结束端点检测
            else:
                # voiced_frames.append(chunk)
                ring_buffer.append(chunk)
                num_unvoiced = NUM_WINDOW_CHUNKS_END - sum(ring_buffer_flags_end)
                if num_unvoiced > 0.90 * NUM_WINDOW_CHUNKS_END or TimeUse > 10:   # 声音结束
                    sys.stdout.write(' Close ')
                    triggered = False
                    got_a_sentence = True

            sys.stdout.flush()

        sys.stdout.write('\n')
        # data = b''.join(voiced_frames)

        stream.stop_stream()
        print("* done recording")
        got_a_sentence = False

        # write to file
        raw_data.reverse()
        for index in range(start_point):
            raw_data.pop()
        raw_data.reverse()
        raw_data = normalize(raw_data)
        record_to_file(file_path, raw_data, 2)
        leave = True

    stream.close()

    return True

CHUNK = 512  # 512是树莓派能使用的最大的CHUNK

def play_sound(file_path='test.wav'):
    # 播放声音文件,默认为'test.wav'
    wf = wave.open(file_path, 'rb')
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                    channels=wf.getnchannels(),
                    rate=wf.getframerate(),
                    output=True)
    data = wf.readframes(CHUNK)

    while data != b'':
        stream.write(data)
        data = wf.readframes(CHUNK)
    stream.stop_stream()
    stream.close()
    p.terminate()
    return
   
def resample(file,rate=16000):
    os.system('sox ' + file + ' -r ' + str(rate) + ' resample_' + file)
    print(wave.open('resample_record.wav', 'rb').getframerate())

if __name__ == '__main__':
    record_sound('record.wav')
    play_sound('record.wav')
    resample('record.wav',16000)

语音转文字程序

其中4-5行的APP_ID、API_KEY、SECRET_KEY需要自己去百度官网申请语音识别相关技术获得。
申请连接:百度智能云

from aip import AipSpeech

# 使用时请将下面的内容替换为你自己的
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
# 初始化
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)

def sound2text(file_path='resample_record.wav'):
    # 语音识别函数,传入文件名(默认为'test.wav'),返回识别结果或错误代码
    with open(file_path, 'rb') as fp:
        recog = client.asr(fp.read(), 'wav', 16000, {'dev_pid': 1536})  # 参数设置见文档        print('a')
        print(recog)
        if recog['err_msg'] != 'success.':
            return recog['err_no']
        return recog['result'][0]
text = sound2text()
print(text)

图灵聊天机器人代码

图灵机器人可以根据输入的文本返回相应的回答。
代码中‘key’和’&info='需要写入密匙

import requests
text = sound2text()
url = "http://www.tuling123.com/openapi/api?key=&info="
html = url + text
response = requests.get(html, timeout=10)
res = response.text
res = res.split('"')[5]

其中,sound2text()函数就是上文的语音转文字函数,最后一行的res就是回答的文本信息

文字转语音程序

APP_ID、API_KEY、SECRET_KEY同上文

from aip import AipSpeech

# 使用时请将下面的内容替换为你自己的
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
# 初始化
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)

def text2sound(words='你好'):
    # 语音合成函数,传入欲合成的内容,默认为“你好”,返回成功与否,若成功默认将文件保存为'test.wav'
    result = client.synthesis(words, 'zh', 1, {
        'vol': 5, 'aue': 6, 'per': 4
    })  # 具体的参数设置请参考官方文档

    if not isinstance(result, dict):
        with open('text2sound.wav', 'wb') as f:
            f.write(result)
        return True
    else:
        return False
    
text2sound(text)

本文只给出了每个函数单独的程序,因本人接下来要将这些程序集成到一个GUI中,所以就不会将这些程序单独集成了,后续如果大家有需要可以在评论区留言,我会给出这些程序集成好的GUI程序。

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
树莓派4B的串口调试助手是minicom。为了验证树莓派串口是否正常工作,可以通过安装minicom来进行验证。安装minicom的步骤如下: 1. 打开终端,输入命令:sudo apt-get install minicom,回车等待下载和安装。 2. 安装完成后,将USB转TTL连接到树莓派,并打开电脑的串口调试助手,连接到CH340,波特率设置为9600,数据位为8位,停止位为1。 3. 同时打开树莓派的串口助手,在终端输入命令:minicom -D /dev/ttyAMA0 -b 9600,回车进入串口助手。 4. 在电脑端输入abcd,点击发送,可以在树莓派上看到相应的显示,表示串口已成功开启。 5. 退出minicom的步骤为:首先按下Ctrl+A,然后按下Z,再按下X,选择是即可退出。 请注意,如果需要使用串口登录树莓派功能,可以选择不启用串口登录树莓派。当被提示是否打开串口时,选择是,并选择Finish->是来重启树莓派。在终端中再次输入命令ls -l /dev,可以看到serial0映射到了mini串口上,表示mini串口已成功开启。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [树莓派4B学习笔记(11)—— 串口配置与基础用法](https://blog.csdn.net/weixin_44415639/article/details/115004935)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [树莓派4B学习笔记——IO通信篇(UART)](https://blog.csdn.net/qq_41954556/article/details/122945322)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值