ChatBot 是一个基于 Python 的智能语音交互机器人项目。
该项目是我在中南大学(CSU)人机交互(HCI)课程中的实验之一。
本学期结束后我会对该项目进行更新,敬请期待!相信它会对你大有帮助!(因为你在网上几乎找不到类似的轻量级资源/项目)
ChatBot is an Intelligent Voice Interactive Robot based on python. This project is one of the experiments in the HCI course at CSU
I will update this project soon after the end of this term. Please wait and see.l'm sure it will give you a great hand!(because you can't find related light-weight source/projects on the internet)
通过本实验课程的实践,使学生了解《人机交互》与计算机图形、 程序设计、认知心理学以及计算机硬件的发展等领域密切相关,并加深学生对人机交互知识的理解,增强学生的实际运用能力和开发高可用性的具有自然交互特性界面的能力。
通过实验学习,让学生了解不同的人机交互模型设计类型,以及成功与失败案例所带来的启示。通过原型设计使学生了解原型的作用,并了解用户需求对设计一个良好人机交互界面的重要性。通过原型和界面评估,使学生掌握针对交互系统的评估方法。
针对该项目(带有语音处理功能的界面设计):
1. 借助一个应用场景,通过开发一个简单式“能听“或”会说“、”懂你”的智能人机交互系统,使同学了解多通道交互系统的优点和交互模型的构建方法。
2.理解语音合成技术的基本原理。
3.了解和掌握阿里智能语音交互平台//百度智能语音开发平台/讯飞智能语音开发平台/Microsoft speech sdk的使用方法。
腾讯云语音识别控制台是腾讯云提供的用于管理和配置语音识别服务的在线平台。该控制台允许用户轻松地创建、配置和管理语音识别应用,以便将语音转换为文本。本实验调用的主要功能api为:
ASR(Automatic Speech Recognition) - 语音识别:
- 语音转文本: ASR的主要任务是将输入的语音信号转换为相应的文本。
- 多语言支持: 支持多种语言的语音识别,以满足不同地区和用户的需求。
- 实时和离线识别: 支持实时语音识别,适用于需要即时响应的场景,也支持离线语音识别,方便处理录音文件。
TTS(Text-to-Speech) - 文本转语音:
- 文本转语音: TTS的主要任务是将输入的文本信息转换为相应的语音信号。
- 多音色和语音风格: 可以通过调整参数实现不同音色和语音风格,使得生成的语音更具自然感。
- 合成实时性: TTS系统能够实时合成语音,根据用户需求即时生成语音输出。
在腾讯云平台上申请创建了密钥,从而调用上述功能接口api/sdk,图12 为私钥申请记录:
青云客(Qingyunke)是一个开源的中文智能聊天机器人平台,可以用于构建自己的聊天机器人。以下是青云客的一些特点和功能:
- 开源平台: 青云客是一个开源项目,用户可以访问其GitHub仓库,查看源代码、贡献代码或进行定制化开发。
- 中文语境: 青云客专注于中文语境,支持中文自然语言处理,适用于中文用户和应用场景。
- RESTful API支持: 青云客提供RESTful API,方便开发者将其集成到自己的应用中,实现聊天机器人功能。
- 用户交互: 青云客能够处理用户的文本输入,并生成相应的文本输出,实现基本的聊天交互。
- 轻量级部署: 由于其基于规则和模板的设计,青云客具有轻量级的特点,易于部署和运行。
我们使用青云客的智能聊天(NLP)功能接口api,从而实现智能语音问答的功能 如图13 为青云客平台api调用规范
- 功能: 用于录制音频并保存为.wav文件。该代码使用了
sounddevice
库进行录音,以及keyboard
库检测是否按下 'q' 键来停止录音,具体如图15所示。 - 实现原理:使用了
sounddevice
库来进行音频录制,同时使用keyboard
库来检测键盘输入。录音持续进行,直到按下 'q' 键,然后将录制的音频数据保存为.wav文件。通过调用record_and_save
函数并传递文件名和采样率参数来进行录音。
以下是提供的模块具体代码(附注释),代码实现具体细节参考如下:
def record_and_save(filename, sample_rate=44100):
# 录音和保存的函数,与你提供的代码一致
# ...
print("Recording... (Press 'q' to stop)")
# 开始录音
audio_data = []
with sd.InputStream(channels=2, samplerate=sample_rate, dtype=np.int16) as stream:
stream.start()
while True:
# 读取音频数据
block, overflowed = stream.read(1024) # 修改此行
audio_data.append(block.copy())
# 检查是否按下 'q' 键,如果是则停止录音
if keyboard.is_pressed('q'):
break
# 合并音频数据
audio_data = np.concatenate(audio_data, axis=0)
print("Recording complete.")
# 保存为.wav文件
with wave.open(filename, 'wb') as wf:
wf.setnchannels(2)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
wf.writeframes(audio_data.tobytes())
print(f"Audio saved as {filename}")
使用腾讯云的语音识别(ASR)服务进行音频文件的识别。主要步骤包括:
-
设置腾讯云的 APPID、SECRET_ID、SECRET_KEY 以及 ENGINE_TYPE。
APPID = "13***84" SECRET_ID = "AKID****NWl9To4L" SECRET_KEY = "c3Tks***tyIXhzVwc"# 已做脱敏处理 ENGINE_TYPE = "16k_zh"
-
创建
FlashRecognizer
实例,该实例可执行多次识别请求。 -
创建识别请求
FlashRecognitionRequest
,设置相关参数如语音格式、过滤模式等。 -
通过读取音频文件的数据,执行识别请求并获取结果。
with open(audio, 'rb') as f: #读取音频数据 data = f.read() #执行识别 resultData = recognizer.recognize(req, data) resp = json.loads(resultData) request_id = resp["request_id"] code = resp["code"] if code != 0: print("recognize faild! request_id: ", request_id, " code: ", code, ", message: ", resp["message"]) exit(0) for channl_result in resp["flash_result"]: return channl_result["text"]
-
解析识别结果,输出识别的文本。
以下是提供的模块具体代码(附注释),代码实现具体细节参考如下:
import sys
import json
sys.path.append("../..")
from common import credential
from asr import flash_recognizer
def asr(output_filename):
# 注意:使用前务必先填写APPID、SECRET_ID、SECRET_KEY,否则会无法运行!!!
APPID = "13***84"
SECRET_ID = "AKID****NWl9To4L"
SECRET_KEY = "c3Tks***tyIXhzVwc"# 已做脱敏处理
ENGINE_TYPE = "16k_zh"
credential_var = credential.Credential(SECRET_ID, SECRET_KEY)
# 新建FlashRecognizer,一个recognizer可以执行N次识别请求
recognizer = flash_recognizer.FlashRecognizer(APPID, credential_var)
# 新建识别请求
req = flash_recognizer.FlashRecognitionRequest(ENGINE_TYPE)
req.set_filter_modal(0)
req.set_filter_punc(0)
req.set_filter_dirty(0)
req.set_voice_format("wav")
req.set_word_info(0)
req.set_convert_num_mode(1)
# 音频路径
audio = output_filename
with open(audio, 'rb') as f:
#读取音频数据
data = f.read()
#执行识别
resultData = recognizer.recognize(req, data)
resp = json.loads(resultData)
request_id = resp["request_id"]
code = resp["code"]
if code != 0:
print("recognize faild! request_id: ", request_id, " code: ", code, ", message: ", resp["message"])
exit(0)
for channl_result in resp["flash_result"]:
return channl_result["text"]
使用了 Python 的 requests
库来发起 HTTP 请求,并通过 GET 请求向青云客的 API 发送用户输入的消息,通过调用该模块api,用于与青云客(Qingyunke)的智能聊天机器人进行对话。以下是代码的主要步骤:
- 定义青云客的 API 地址
base_url
,密钥key
和 AppIDappid
。 - 对用户输入的消息
msg
进行 URL 编码,以确保传递给 API 的参数不含特殊字符。 - 构建请求参数
params
,包括密钥、AppID 和经过编码的消息。 - 发送 GET 请求到青云客的 API,携带构建好的参数。
- 解析 API 返回的 JSON 数据,获取其中的聊天结果。
- 返回聊天结果。
以下是提供的模块具体代码(附注释),代码实现具体细节参考如下:
def chat_with_qingyunke(msg):
# 与青云客进行对话的函数,与你提供的代码一致
# ...
base_url = "http://api.qingyunke.com/api.php"
key = "free"
appid = 0
# 对消息进行 urlencode 处理
encoded_msg = requests.utils.quote(msg)
# 构建请求参数
params = {
"key": key,
"appid": appid,
"msg": encoded_msg
}
# 发送 GET 请求
response = requests.get(base_url, params=params)
# 解析返回的 JSON 数据
result = response.json()
# 返回聊天结果
return result["content"]
使用腾讯云的语音合成(TTS)服务,将指定的文本转换为语音,参见图18。以下是代码的主要功能和步骤:
- 导入相关库和模块,包括
wave
用于处理音频文件,time
用于计时,threading
用于多线程处理,以及腾讯云 TTS SDK 相关的模块。 - 定义了一个
tts
函数,用于执行语音合成。在函数内部设置了一些参数,包括腾讯云的 APPID、SECRET_ID、SECRET_KEY,以及指定的文本、音色类型、音频格式等。 - 使用
MySpeechSynthesisListener
类继承自speech_synthesizer_ws.SpeechSynthesisListener
,重写了一些回调函数,用于处理合成过程中的事件。例如,当合成开始、合成结束、接收到音频数据、接收到文本结果、合成失败等情况。 - 在
process
函数中,创建了一个MySpeechSynthesisListener
实例,设置相关参数,然后创建SpeechSynthesizer
实例并启动语音合成。在多线程情况下,可以调用process_multithread
函数创建多个线程同时执行合成任务。 - 在
process_multithread
函数中,创建并启动多个线程,每个线程执行process
函数,实现多个合成任务的并行处理。
以下是提供的模块具体代码(附注释),代码实现具体细节参考如下:
# -*- coding: utf-8 -*-
# 引用 SDK
import sys
sys.path.append("../..")
import wave
import time
import threading
from common import credential
from tts import speech_synthesizer_ws
from common.log import logger
from common.utils import is_python3
def tts(text):
APPID = "13***084"
SECRET_ID = "AKI***9To4L"
SECRET_KEY = "c3***wc"#(私人信息已做脱敏处理)
TEXT = text
VOICETYPE = 101001 # 音色类型
CODEC = "pcm" # 音频格式:pcm/mp3
SAMPLE_RATE = 16000 # 音频采样率:8000/16000
ENABLE_SUBTITLE = True
EMOTION_CATEGORY = "" # 仅支持多情感音色
EMOTION_INTENSITY = 100
class MySpeechSynthesisListener(speech_synthesizer_ws.SpeechSynthesisListener):
def __init__(self, id, codec, sample_rate):
self.start_time = time.time()
self.id = id
self.codec = codec.lower()
self.sample_rate = sample_rate
self.audio_file = ""
self.audio_data = bytes()
def set_audio_file(self, filename):
self.audio_file = filename
def on_synthesis_start(self, session_id):
'''
session_id: 请求session id,类型字符串
'''
super().on_synthesis_start(session_id)
# TODO 合成开始,添加业务逻辑
if not self.audio_file:
self.audio_file = "response." + self.codec
self.audio_data = bytes()
def on_synthesis_end(self):
super().on_synthesis_end()
# TODO 合成结束,添加业务逻辑
logger.info("write audio file, path={}, size={}".format(
self.audio_file, len(self.audio_data)
))
if self.codec == "pcm":
wav_fp = wave.open(self.audio_file + ".wav", "wb")
wav_fp.setnchannels(1)
wav_fp.setsampwidth(2)
wav_fp.setframerate(self.sample_rate)
wav_fp.writeframes(self.audio_data)
wav_fp.close()
elif self.codec == "mp3":
fp = open(self.audio_file, "wb")
fp.write(self.audio_data)
fp.close()
else:
logger.info("codec {}: sdk NOT implemented, please save the file yourself".format(
self.codec
))
def on_audio_result(self, audio_bytes):
'''
audio_bytes: 二进制音频,类型 bytes
'''
super().on_audio_result(audio_bytes)
# TODO 接收到二进制音频数据,添加实时播放或保存逻辑
self.audio_data += audio_bytes
def on_text_result(self, response):
super().on_text_result(response)
# TODO 接收到文本数据,添加业务逻辑
result = response["result"]
subtitles = []
if "subtitles" in result and len(result["subtitles"]) > 0:
subtitles = result["subtitles"]
def on_synthesis_fail(self, response):
super().on_synthesis_fail(response)
# TODO 合成失败,添加错误处理逻辑
err_code = response["code"]
err_msg = response["message"]
def process(id):
listener = MySpeechSynthesisListener(id, CODEC, SAMPLE_RATE)
credential_var = credential.Credential(SECRET_ID, SECRET_KEY)
synthesizer = speech_synthesizer_ws.SpeechSynthesizer(
APPID, credential_var, listener)
synthesizer.set_text(TEXT)
synthesizer.set_voice_type(VOICETYPE)
synthesizer.set_codec(CODEC)
synthesizer.set_sample_rate(SAMPLE_RATE)
synthesizer.set_enable_subtitle(ENABLE_SUBTITLE)
if EMOTION_CATEGORY != "":
synthesizer.set_emotion_category(EMOTION_CATEGORY)
if EMOTION_INTENSITY != 0:
synthesizer.set_emotion_intensity(EMOTION_INTENSITY)
synthesizer.start()
# wait for processing complete
synthesizer.wait()
logger.info("process done")
def process_multithread(number):
thread_list = []
for i in range(0, number):
thread = threading.Thread(target=process, args=(i,))
thread_list.append(thread)
thread.start()
print(i)
for thread in thread_list:
thread.join()
process_multithread(1)
该模块用于使用 pygame
播放指定路径的音频文件,参见图19。以下是代码的主要功能和步骤:
- 导入
pygame
库,pygame
是一个用于游戏开发的库,其中包含音频播放功能。 - 定义了一个
play_audio
函数,接受音频文件的路径作为参数。 - 在函数内部,初始化
pygame
和音频混合器(pygame.mixer
)。 - 使用
pygame.mixer.music.load(file_path)
加载指定路径的音频文件。 - 使用
pygame.mixer.music.play()
播放加载的音频文件。 - 使用
pygame.mixer.music.get_busy()
判断音频是否仍在播放,通过pygame.time.Clock().tick(10)
控制每秒的刷新率,使得程序不会占用过多 CPU 资源。 - 在
except
块中捕获异常并输出错误信息。 - 在
finally
块中确保调用pygame.mixer.quit()
,以释放音频混合器资源。
以下是提供的模块具体代码(附注释),代码实现具体细节参考如下:
def play_audio(file_path):
# 播放音频的函数,与你提供的代码一致
# ...
pygame.init()
pygame.mixer.init()
try:
pygame.mixer.music.load(file_path)
pygame.mixer.music.play()
# 等待音频播放完毕
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
except Exception as e:
print(f"Error: {e}")
finally:
pygame.mixer.quit()
定义设计图形用户界面(GUI)类 ChatGUI
,使用了 tkinter
库。这个界面通过调用上述模块,允许用户开始语音对话,与机器人进行聊天。
具体的显示效果会在下一节展示,以下是代码的主要功能和步骤:
- 界面初始化: 在
__init__
方法中,设置了窗口标题为"人机交互页面",创建了一个文本框 (scrolledtext.ScrolledText
) 用于显示聊天内容,以及开始和停止对话的按钮。 - 开始语音对话:
start_chat_thread
方法在用户点击“开始语音对话”按钮时被调用。在该方法中,按钮状态被更新,文本框显示用户输入的提示,然后启动了一个线程 (chat_thread
)。chat_thread
方法中,使用record_and_save
函数录制用户的语音,然后使用asr
函数将语音转换为文本。接着,调用chat_with_qingyunke
函数与青云客机器人进行聊天,获取机器人的回复。最后,使用tts
函数将机器人的回复转换为语音,再使用play_audio
函数播放生成的音频文件。- 这个对话线程会一直运行,直到用户点击“停止语音对话”按钮。
- 停止语音对话:
stop_chat_thread
方法在用户点击“停止语音对话”按钮时被调用。在该方法中,按钮状态被更新,文本框显示“对话结束”,然后等待对话线程结束。 - 多线程处理: 对话线程 (
chat_thread
) 是在用户点击“开始语音对话”按钮时创建并启动的,允许在后台进行语音录制、转换和聊天,而不影响主界面的响应。
以下是提供的模块具体代码(附注释),代码实现具体细节参考如下:
class ChatGUI:
def __init__(self, master):
self.master = master
master.title("人机交互页面")
self.text_area = scrolledtext.ScrolledText(master, wrap=tk.WORD, width=40, height=10)
self.text_area.pack(expand=True, fill='both')
self.text_area.insert(tk.END, "欢迎来到人机交互页面!点击 '开始语音对话' 开始与机器人聊天。\n")
self.record_button = tk.Button(master, text="开始语音对话", command=self.start_chat_thread)
self.record_button.pack()
self.stop_button = tk.Button(master, text="停止语音对话", command=self.stop_chat_thread, state=tk.DISABLED)
self.stop_button.pack()
self.user_input = ""
def start_chat_thread(self):
self.record_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.text_area.insert(tk.END, "你:")
self.user_input = ""
def chat_thread():
while True:
output_filename = "recorded_audio.wav"
record_and_save(output_filename)
self.user_input = asr(output_filename)
self.text_area.insert(tk.END, f"{self.user_input}\n")
response = chat_with_qingyunke(self.user_input)
self.text_area.insert(tk.END, f"chatbot:{response}\n")
tts(response)
play_audio('response.pcm.wav')
time.sleep(1) # 为了避免过快刷新
self.chat_thread = Thread(target=chat_thread)
self.chat_thread.start()
def stop_chat_thread(self):
self.record_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.text_area.insert(tk.END, "对话结束\n")
self.chat_thread.join()
登录页面:
如图20所示
附带欢迎语句,以及相应操作引导。 交互界面功能简洁,入口一目了然。
智能的将与机器人的语音对话转化为文字显示在界面上,一方面助于追溯,另一方面便于用户使用
同时对话内容标注语音发起者 我
和chatbot
,更加清晰易懂
控制台输出解读
- 可以看到,点击
开始语音对话
后,录音模块开始运行,输出Recording...(press 'q' to stop)
来对用户进行引导,告知正在听取语音输入,并且指导如何结束语音输入。最终Audio saved as recorded_audio.wav
-
subtitles=[{'Text': '今', 'BeginTime': 250, 'EndTime': 480, 'BeginIndex': 0, 'EndIndex': 1, 'Phoneme': 'jin1'}, {'Text': '天', 'BeginTime': 480, 'EndTime': 730, 'BeginIndex': 1, 'EndIndex': 2, 'Phoneme': 'tian1'}, {'Text': '星', 'BeginTime': 730, 'EndTime': 930, 'BeginIndex': 2, 'EndIndex': 3, 'Phoneme': 'xing1'}, {'Text': '期', 'BeginTime': 930, 'EndTime': 1180, 'BeginIndex': 3, 'EndIndex': 4, 'Phoneme': 'qi1'}, {'Text': '日', 'BeginTime': 1180, 'EndTime': 1380, 'BeginIndex': 4, 'EndIndex': 5, 'Phoneme': 'ri4'}, {'Text': '', 'BeginTime': 1380, 'EndTime': 1580, 'BeginIndex': 5, 'EndIndex': 6, 'Phoneme': 'SIL'}]
特色设计内容:
- 语音对话交互: 提供了语音对话的功能,用户可以通过语音与机器人进行交流,增强了用户体验。
- 实时展示对话记录: 文本框实时展示用户和机器人的对话记录,使用户能够清晰地看到之前的交流内容。
- 多线程处理: 采用多线程处理语音录制、转换和聊天,保证了界面的响应性,不会在语音处理过程中阻塞用户界面。
- 按钮状态切换: 在语音对话开始和结束时,按钮状态切换,使用户清楚地知道当前是否在进行语音对话。
- 对话结束提示: 在用户点击停止对话按钮后,文本框显示“对话结束”提示,提供了明确的结束反馈。
遵循的人机交互网页设计原则:
- 可用性原则: 界面提供了直观的按钮,用户可以轻松开始和结束语音对话,使功能易于理解和使用。
- 一致性原则: 界面的布局和按钮设计保持一致,用户在不同操作中能够找到相似的元素,提高了用户的学习和使用效率。
- 反馈原则: 文本框实时展示对话记录,为用户提供了明确的反馈,使用户能够了解系统的状态。
- 灵活性原则: 提供了开始和停止语音对话的按钮,允许用户在需要时启动或结束对话,增加了系统的灵活性。
- 可控性原则: 用户通过点击按钮主动触发语音对话的开始和结束,具有较高的可控性。
- 用户反馈和提示: 在文本框中显示欢迎提示、用户输入提示、对话结束提示等,为用户提供了友好的界面反馈和提示。
- 效率原则: 使用多线程处理,确保语音对话的同时不阻塞用户界面,提高了交互效率。
本实验通过整合语音录制、转换、对话、合成和图形用户界面设计等多个模块,成功搭建了一个简单的语音机器人交互系统。系统在基本功能上表现出色,能够实现用户与机器人的自然语言对话。然而,为了提高系统的稳定性和适应性,可能需要进一步优化语音录制和转换模块,并在实际应用中进行更多的测试和用户反馈。总体而言,本实验为构建更复杂、更强大的语音交互系统奠定了基础。
在该语音机器人交互系统中,尽管实现了多个功能并遵循了人机交互网页设计原则,但仍然存在一些不足之处:
-
语音识别的准确性: 语音识别的准确性可能受到语音质量和环境因素的影响。在不同噪音水平或口音的情况下,识别准确性可能下降,需要更强大的语音识别引擎或者后处理技术来提高准确性。
-
机器人对话的局限性: 该系统依赖于外部的聊天机器人服务(青云客),其回复的质量和内容受限于该服务的性能。对话结果的质量可能受到对话模型的限制,无法适应更广泛的语境。
-
语音合成的自然性: 语音合成功能虽然实现了将文本转换为语音,但可能在语音的自然性和流畅性方面有改进的空间。优化语音合成引擎或选择更先进的语音合成技术可以提高生成语音的自然感。
-
界面美观性: 图形用户界面相对简单,可能缺乏一些美观性和用户友好性的设计。更精致的界面设计可以提升用户的整体体验。
-
用户反馈机制: 在界面上缺少直观的用户反馈机制,例如进度条或状态指示器,告知用户当前语音录制和处理的进展情况。
-
系统稳定性: 对于语音录制和处理模块,可能需要更加健壮的异常处理机制,以应对可能的录音失败或识别错误。
-
对于不同语音输入的适应性: 系统可能需要更好地适应不同用户的语音输入,包括不同的语速、音调和口音。
这些不足之处可以作为进一步改进和优化系统的方向,通过技术的不断提升和用户反馈的积累,逐步完善语音机器人交互系统的性能和用户体验。