树莓派简易聊天机器人

前言

这篇文章属于此系列:

一个用树莓派做的会聊天,能人脸识别(支持云台追踪)和发送邮件的小玩具

https://blog.csdn.net/yonglisikao/article/details/82804318

原理简析

简易的聊天机器人包括以下几个部分:输入声音(录音),把录音中有声音的片段提取出来(VAD),将声音转换为文字(语音识别),对文字进行回复,将文字转换为语音(语音识别),输出语音(播放)。

1.硬件要求

树莓派及基本配件,声音输出设备(音箱),USB声卡以及声音输入设备(麦克风),USB声卡与麦克风可由内置麦克风的USB摄像头代替

2.系统要求

Raspbain系统(其它Linux系统应该也可以)

3.软件要求

Python及一些库——baidu-aip(接入百度语音),pyaudio(输入和输出声音),requests(接入图灵机器人),webrtcvad(语音活动检测即VAD)等

4.其他要求

因为语音识别和语音合成使用的是百度云语音,所以需要在百度的AI开放平台注册自己的应用,获取相应口令。同样地,负责聊天的机器人使用的是图灵机器人,也需要去官网注册,获取相应口令。以上三个接口都是在线使用的。

环境搭建

关于声卡的配置请参考下面这篇文章:

https://www.jianshu.com/p/2e8eed5070ed

1.安装pyaudio

在命令行输入:

sudo apt-get install portaudio19-dev
sudo apt-get install python-all-dev

为了与系统的安装包隔离,建议使用虚拟环境(virtualenv),虚拟环境安装完成后,进入虚拟环境,下文所有操作均在虚拟环境下进行。

在命令行输入:

pip install pyaudio

如果你没有安装pip,请前往官网

2.安装baidu-aip,requests,webrtcvad

在命令行输入:

pip install baidu-aip
pip install requests
pip install webrtcvad

至此,环境搭建完毕

代码实现

1.语音识别及语音合成

直接参考下面代码及注释。更多花样玩法,以及一些参数设置请参考官方文档

# coding=utf-8
from aip import AipSpeech

# 使用时请将下面的内容替换为你自己的
APP_ID = '11549141'
API_KEY = 'dNEEwltpoaUM086aHBAkxdjy'
SECRET_KEY = 'jRjaMTPz9QegCcxmZ10XBg9OMj94nU5Y'
# 初始化
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('test.wav', 'wb') as f:
            f.write(result)
        return True
    else:
        return False


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


# 调用示例
text2sound('你吃饭了吗?')
sound2text('test.wav')

2.语音输入,输出及VAD

以下代码改编自(非中文注释为原作者所加):

https://github.com/wangshub/python-vad

这里的录音直接融合了VAD。更改特定参数可改变灵敏度,参数的位置见注释。运行时会有报错,忽略即可,如果实在想解决,下面这篇文章有一些解决方案,但不一定能解决所有问题:

https://blog.csdn.net/u013372900/article/details/72784823

# coding=utf-8
import webrtcvad
import collections
import sys
import signal
import pyaudio

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


FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK_DURATION_MS = 30       # supports 10, 20 and 30 (ms)
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)


def record_sound(file_path='test.wav'):
    # 录音,有声音自动写入文件,默认为'test.wav',声音结束后录音也停止,调用一次,录制一个片段
    vad = webrtcvad.Vad(3) # 这个参数可为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('1' 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

            # start point detection
            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()
            # end point detection
            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

# 调用示例
record_sound('test.wav')
play_sound('test.wav')

如果你对声音文件不了解,不明白chunk,rate等术语是什么意思,相信下面这篇文章会帮到你:

http://www.cnblogs.com/lzxwalex/p/6922099.html

3.图灵机器人对话

代码如下:

# coding=utf-8
import requests
import json


def tuling(text='I said nothing'):
    # 与图灵机器人对话
    tuling_url = 'http://www.tuling123.com/openapi/api'
    tuling_date = {
        'key': '4527c40a4ee64b2bb4ca92cf020847be',
        'info': text
    }  # 使用时请将key更换为你自己的
    r = requests.post(tuling_url, data=tuling_date)
    return json.loads(r.text)['text']

# 调用示例
reply = tuling('你吃饭了吗?')
print(reply)

4.整合简单示例

代码如下:

while True:
    record_sound()
    text = sound2text()
    print('你:%s'.text)
    reply = tuling(text)
    print('机器人:%s'.reply)
    text2sound(reply)
    play_sound()

总结

本文教你制作一个简易的聊天机器人,虽然写的简单,但我相信,如果你之前没有接触过,你可能会在很多地方碰到麻烦,现实总是与理想有差距,但不要灰心,多看看文档,认真分析,多尝试,有时候或许只是时机不对。代码部分只是简单的应用,将聊天机器人做的更稳定,更精致需要靠自己的努力。

可能会遇到的问题

1.你使用声卡与麦克风的套餐,录音时,即使你把灵敏度调到最低,依然出现即使没有说话,环境噪音也不大,也开始了录音的情况。

可能原因:声卡与麦克风的接触不良。

解决方案:a.将声卡与麦克风接触牢固一下;b.换一下声卡或麦克风试试;c.换成内置麦克风的USB摄像头。

2.你使用声卡与麦克风的套餐,且将音箱插在3.5mm接口(耳机口),播放时,没有声音

可能原因:这时你有两个声卡,一个板载,一个外置,播放声音时,信号从外置声卡出去的,而板载声卡没信号。

解决方案:a.修改alsa的配置文件:

sudo nano ~/.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,即板载声卡。”

b.把音箱插到外置声卡上试试;c.将声卡和麦克风用内置麦克风的USB摄像头代替。

3.音箱插在3.5mm接口(耳机口),没有播放任何东西就有很大杂音。

可能原因:接头不匹配。

解决方案:把街头稍稍往外拔一点

4.带有麦克风的耳机直接插在耳机口不能录音

原因:板载声卡不支持

解决方案:做梦吧你

猜你喜欢

转载自blog.csdn.net/yonglisikao/article/details/82314512