本人拟在树莓派中设计一个GUI,GUI包括基于OPCV的人脸识别、基于YOLOV3的目标检测、图灵聊天机器人等。前期已经完成了多个功能,在做语音聊天机器人的时候遇到了些困难,该博客用于记录聊天机器人开发过程。
基本配置:树莓派4B,屏幕,键鼠,带3.5mm音频线的USB摄像头,Raspbain系统,python3.7。
遗留问题:在写用麦克风采集声音的程序时参考多个文章也没有实现声音采集的功能,个人感觉可能是树莓派自带的3.5mm音频口只能用来播放声音,采集还是需要外界声卡。上述说法如有问题恳请大家评论区告知,或大家有什么想法也可以评论区交流。
参考多篇博客:
树莓派简易聊天机器人: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,等买的外接声卡到了再试试。
插上外置声卡后先录音
树莓派进行录音: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‘结合上文的百度语音识别功能可以成功将录音中的语音转换为文本。
针对之前用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工具包降采样+百度语音识别+图灵对话机器人+百度语音合成最终实现基于树莓派的对话系统。程序见下文。
使用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程序。