树莓派-语音聊天机器人实现

    最近开始复习考研了,所以博客更新频率要降下来了,只能是白天复习,晚上抽空写写代码。宿舍里有一块树莓派3B+,买了两年,一直没怎么用过,突然想玩玩,发现用来做智能家居还不错。智能家居最重要的功能是语音对话,我这篇博客就是分享一下怎么用树莓派实现一个简单的语音聊天机器人。

硬件配置:

pi.png

    我用到的模块:USB外置声卡,麦克风,扬声器,带外盒和散热器的树莓派。树莓派板子上没有音频输入接口,因此只能通过外置的声卡来解决这个问题,声卡最好是免驱的。散热器这个东西,我觉得是必要的,曾经因为高温烧坏了我一块SD卡。

    语音对话的流程很简单,就是:录音,语音识别,对话生成,语音合成,播放语音。下面逐个分析。


录音

    这看起来是一件非常简单是事情,但树莓派上实现并不容易。首先树莓派没有音频输入接口,需要自己淘宝一个外置声卡和麦克风。其次需要配置声卡:新建或修改~/.asoundrc文件,将文件内容修改如下:

pcm.!default {
    type asym
    playback.pcm {
        type plug
        slave.pcm "hw:0,0"
    }
    capture.pcm {
        type plug
        slave.pcm "hw:1,0"
    }
}

    配置内容的意思就是音频输入使用声卡1(也就是usb声卡),输出使用声卡0,即板载声卡。其中,hw:1,0表示card 1 device 0。配置完后在命令行输入arecord -d 10 test.wav测试录音功能是否可用。

    录音的程序我使用的是pyaudio模块实现。代码如下:

import pyaudio
import wave
import os
import sys
import numpy as np
import time

def getMicroRecord(fileName):
    CHUNK = 1024
    FORMAT = pyaudio.paInt16
    CHANNELS = 1
    RATE = 16000
    MAX_RECORD_SECONDS = 30
    MAX_SILENCE_SECONDS=3.5
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)
    print("recording...")
    frames = []
    isSilence=False
    isSilenceLast=False
    startTime=time.time()
    for i in range(0, int((RATE / CHUNK) * MAX_RECORD_SECONDS)):
        data = stream.read(CHUNK)
        frames.append(data)
        audio_data=np.fromstring(data,dtype=np.short)
        max_audio_data=np.max(audio_data)
        print(max_audio_data,end=' ')
        if(max_audio_data<800):
            isSilence=True
            
        else:
            isSilence=False
        if(isSilence and not isSilenceLast):
            startTime=time.time()
        
        isSilenceLast=isSilence
        if(isSilence and time.time()-startTime>MAX_SILENCE_SECONDS):
            break
    print("done")
    stream.stop_stream()
    stream.close()
    p.terminate()
    wf = wave.open(fileName, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()

    代码实现了录音功能,当没有声音持续3.5s后录音结束,最长录音时间为30s。我上面用了很简单的方法判断说话是否结束,但其实语音端点检测是个复杂的问题,以后有空再研究吧。有时声音采样率设置为16000会有点问题,这时启动pulseaudio就可以了。


语音识别和合成

    语音识别和语音合成我都使用了百度的API。若想使用百度的API,需要在百度AI开放平台上面先注册一个账户,然后上面创建一个“应用”,就可以获取应用的APP ID,API KEY,SECRET KEY。之后用pip安装百度提供的API接口模块:baidu-aip,就可以愉快的使用百度的语音识别和语音合成接口了。Python程序如下:

from aip import AipSpeech
import wave
import json

APP_ID='**********'
API_KEY='***************'
SECRET_KEY='******************'

client=AipSpeech(APP_ID,API_KEY,SECRET_KEY)
client.setConnectionTimeoutInMillis(3000)#connection timeout /ms

#语音合成
def speechSynthesis(text,fileName):
    #speech syn
    #para:text,cuid,
    result=client.synthesis(text,'zh',1,{
        'vol':5,#vol:0-15
        'spd':5,#speed:0-9
        'pit':5,#tone:0-9
        'per':0 #persion,0:woman,1:man,3:duxiaoyao,4:yaya
        })
    if not isinstance(result,dict):#if syn fail,return a dict
        with open(fileName,'wb') as f:
            f.write(result)
        return True
    else:
        if(result['err_no']==500):
            print('不支持的输入')
        elif(result['err_no']==501):
            print('输入参数不正确')
        elif(result['err_no']==502):
            print('token验证失败')
        elif(result['err_no']==503):
            print('合成后端失败')
        return False

#语音识别
def speechRecognition(fileName):
    with open(fileName,'rb') as f:
        d=client.asr(f.read(),'wav',16000,{
            'dev_pid':1536,#putonghua
            })
        if(d['err_no']==0):
            return d['result'][0]
        else:
            return ""

    上面的代码使用时,只需把你的APP_ID,API_KEY,SECRET_KEY修改为你自己的即可。需要说明的是,百度语音识别只能识别PCM编码的、采样率为16000的音频。


声音播放

    我设置百度语音合成的音频格式为MP3,不能使用pyaudio模块直接播放。播放MP3的方式有很多,最经典的办法是使用pygame模块,代码如下:

from pygame import mixer
from mutagen.mp3 import MP3
import pygame
import time
import os

def playMp3(fileName):
    mp3=MP3(fileName)
    duration=mp3.info.length #get mp3 lenght
    print('mp3 len:'+str(duration))
    mixer.init()
    mixer.music.load(fileName)
    mixer.music.play()
    mixer.music.set_volume(0.99)#set volume
    time.sleep(duration)
    mixer.music.stop()

    上面的代码中,我首先使用mutagen.mp3模块获取MP3文件的长度,再使用pygame.mixer模块播放,同时设置延迟时间。但是我发现使用pygame播放的音效很怪,这可能是我没设置好pygame的原因。所以后面我又改用命令行调用播放,代码如下:

def playMp3ByShell(fileName):
    mp3=MP3(fileName)
    duration=mp3.info.length #get mp3 lenght
    print('mp3 len:'+str(duration))
    os.popen('omxplayer -p -o local '+fileName)
    for i in range(1000):
        time.sleep(duration/1000)

    上面代码通过命令行直接调用了omxplayer播放器播放音频。你会发现播放代码的后面跟着一个for循环延迟,我之所以这样写是因为直接写time.sleep(duration)似乎只能播放四五秒的时间,后面就没声了,我猜这跟Debian系统的进程调度有关,这样设置分段延迟之后就没有这个问题了。


对话生成

    这是语音聊天机器人最核心的模块,根据语音识别到的文本生成相应的回答。前面我有写过自己训练一个聊天机器人,但是说实话效果并不是很理想。这里我使用了开放的聊天机器人API:小I聊天机器人,代码如下:

# -*- coding:utf-8 -*- 
import json
import urllib.request
import re
import string

def chat(text):
    BOT_NAME='垃圾机器人'
    BOT_SELF_NAME='本垃圾'
    x = urllib.parse.quote(text)
    link = urllib.request.urlopen(
        "http://nlp.xiaoi.com/robot/webrobot?&callback=__webrobot_processMsg&data=%7B%22sessionId%22%3A%22ff725c236e5245a3ac825b2dd88a7501%22%2C%22robotId%22%3A%22webbot%22%2C%22userId%22%3A%227cd29df3450745fbbdcf1a462e6c58e6%22%2C%22body%22%3A%7B%22content%22%3A%22" + x + "%22%7D%2C%22type%22%3A%22txt%22%7D")
    html_doc = link.read().decode()
    reply_list = re.findall(r'\"content\":\"(.+?)\\r\\n\"', html_doc)
    reply=str(reply_list[-1])
    reply=reply.replace("小i机器人",BOT_NAME)
    reply=reply.replace("小i",BOT_SELF_NAME)
    return reply

    上面代码把小I替换成了自定义的名字,代码很简单不用过多解释。


主程序

    把上面各个模块综合起来,得到下面的主程序:

from speech import *
from voice import *
from chatbot import *
from microrecord import *
import time

mp3FileName='dialog.mp3'
wavFileName='dialog.wav'


def getResponse(text):
    output_text=''
    output_text=chat(text)
    return output_text
    
while True:
    getMicroRecord(wavFileName)
    input_text=speechRecognition(wavFileName)
    print('input:'+input_text)
    if(len(input_text)==0):
        break
    output_text=getResponse(input_text)
    if(speechSynthesis(output_text,mp3FileName)):
        playMp3ByShell(mp3FileName)
    print('output:'+output_text)
    
print('dialog done')




首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于