如何在Tkinter的 Python GUI中实时显示Vosk的语音识别文本?
我在Python中使用Vosk语音识别库,实时从麦克风识别语音。
识别结果工作正常,我可以在终端打印输出。
然而,我想在Tkinter GUI的标签中实时显示识别文本,但当音频识别循环运行时,GUI会卡死。
下面是我的代码的一个简化版本。
import tkinter as tk
from vosk import Model, KaldiRecognizer
import pyaudio
import json
model = Model("vosk-model-small-cn-0.22")
rec = KaldiRecognizer(model, 16000)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True,
frames_per_buffer=8000)
root = tk.Tk()
label = tk.Label(root, text="Speech result")
label.pack()
while True:
data = stream.read(4000)
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
text = result["text"]
label.config(text=text)
root.mainloop()
运行这段代码时,Tkinter窗口会卡死且不会更新。
如何在不阻塞GUI线程的情况下,使用Vosk的识别结果来更新Tkinter标签?
解决方案
while True: 循环阻止tkinter的 mainloop 运行(也就是说,进程没有执行到 mainloop 调用),因为主代码是顺序执行的。
可以使用 threading Python模块将语音识别部分移到另一个线程,如下所示:
import tkinter as tk
from vosk import Model, KaldiRecognizer
import pyaudio
import json
import threading # Added import.
# Reads audio, recognizes text, and updates the label:
def listen(recognizer, stream, label):
while True:
data = stream.read(4000)
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
text = result["text"]
label.config(text=text)
model = Model("vosk-model-small-cn-0.22")
rec = KaldiRecognizer(model, 16000)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True,
frames_per_buffer=8000)
root = tk.Tk()
label = tk.Label(root, text="Speech result")
label.pack()
# Create and start the thread which performs voice recognition:
t = threading.Thread(
target=listen,
kwargs=dict(recognizer=rec, stream=stream, label=label)
)
t.daemon = True # When the user closes the tkinter's window then the thread will not prevent the end of the process.
t.start()
root.mainloop()
对tkinter的调用可以来自任意Python线程。内部来说,如果调用来自创建Tk对象的线程以外的线程,解释器的事件队列中会投放一个事件,当该事件被执行时,结果会返回给调用该Python线程。
...因此,可以安全地从另一个线程更新标签。
另一种选择是把 mainloop 本身也放到另一个线程中。若 mainloop 可以在创建 root 的线程之外运行,那么把 mainloop 放到另一个线程里就像把 root.mainloop() 替换为以下内容一样简单:
threading.Thread(target=root.mainloop).start()
但由于我目前找不到明确的说明,可以参考其他类似的帖子,例如 Multithreading: tkinter mainloop not in main thread,将 mainloop 移动到另一个线程。
注意,上面的代码没有正确处理资源释放。这是因为结束 mainloop 会结束进程,因此 stream 会被释放(因为进程终止)。不过,在更大型的应用中,当不再需要时,通常需要正确地释放 stream。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。