pyttsx的中文语音识别问题及探究之路
最近在学习pyttsx时,发现中文阅读一直都识别错误,从发音来看应该是字符编码问题,但搜索之后并未发现解决方案。自己一路摸索解决,虽说最终的原因非常可笑,大牛们可能也是一眼就能洞穿,但也值得记录一下。嗯,主要并不在于解决之道,而是探究之旅。
1、版本(python2中谈编码解码问题不说版本都是耍流氓)
python:2.7
pyttsx:1.2
OS:windows10中文版
2、系统的各种字符编码
sys.getdefaultencoding() ascii
sys.getfilesystemencoding() mbcs
locale.getdefaultlocale() ('zh_CN', 'cp936')
locale.getpreferredencoding() cp936
sys.stdin.encoding UTF-8
sys.stdout.encoding UTF-8
3、探究之路
(1)初体验:
按照http://pyttsx.readthedocs.io/en/latest/engine.html 的说明,传入中文,使用unicode类型,utf-8编码,结果发音并不是输入的内容。
#-*- coding: UTF-8 -*-
import sys
import pyttsx reload(sys)
sys.setdefaultencoding("utf-8") text = u'你好,中文测试'
engine = pyttsx.init()
engine.say(text)
engine.runAndWait()
(2)再试探:
或许是pyttsx内部转换的问题?将传入类型str类型,依然为utf-8编码,但发音依旧不对,和之前一样。
#-*- coding: UTF-8 -*-
import sys
import pyttsx reload(sys)
sys.setdefaultencoding("utf-8") text = '你好,中文测试'
engine = pyttsx.init()
engine.say(text)
engine.runAndWait()
(3)困惑:
google、百度轮番上阵,并未发现有类似问题,难道是默认语音的问题?获取属性看看!
voice = engine.getProperty('voice')
通过上述语句,获取到的voice是 TTS_MS_ZH-CN_HUIHUI_11.0,在控制面板-语音识别中,可以看到huihui是中文语音,原因不在于此。
(4)深入深入,迷茫:
既然系统没有问题,那看pyttsx的源码怎么写的吧,开源就这点好,有问题可以直接撸代码。
在pyttsx\driver.py中的__init__函数中,可以看到windows平台,默认使用的是sapi5
def __init__(self, engine, driverName, debug):
'''
Constructor. @param engine: Reference to the engine that owns the driver
@type engine: L{engine.Engine}
@param driverName: Name of the driver module to use under drivers/ or
None to select the default for the platform
@type driverName: str
@param debug: Debugging output enabled or not
@type debug: bool
'''
if driverName is None:
# pick default driver for common platforms
if sys.platform == 'darwin':
driverName = 'nsss'
elif sys.platform == 'win32':
driverName = 'sapi5'
else:
driverName = 'espeak'
# import driver module
name = 'pyttsx.drivers.%s' % driverName
self._module = importlib.import_module(name)
# build driver instance
self._driver = self._module.buildDriver(weakref.proxy(self))
# initialize refs
self._engine = engine
self._queue = []
self._busy = True
self._name = None
self._iterator = None
self._debug = debug
在pyttsx\driver\sapi5.py中可以看到say函数,在调用speak时,有一个toUtf8的转换,难道最终传入的是utf8编码格式的?
def say(self, text):
self._proxy.setBusy(True)
self._proxy.notify('started-utterance')
self._speaking = True
self._tts.Speak(toUtf8(text), 19)
继续向下探,在toUtf8的定义在pyttsx\driver\__init__.py中,只有1行
def toUtf8(value):
'''
Takes in a value and converts it to a text (unicode) type. Then decodes that
type to a byte array encoded in utf-8. In 2.X the resulting object will be a
str and in 3.X the resulting object will be bytes. In both 2.X and 3.X any
object can be passed in and the object's __str__ will be used (or __repr__ if
__str__ is not defined) if the object is not already a text type.
'''
return six.text_type(value).encode('utf-8')
继续深入,pyttsx\six.py中,对text_type有定义
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3 if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
可以看到,PY2中是unicode,至此到底了。根据代码,果然最终是转成utf-8编码的,可我传入的就是utf-8编码啊!
问题还是没有解决,陷入更深的迷茫...
(5)峰回路转
既然pyttsx没有问题,那难道是sapi5的问题?转而搜索sapi5。
sapi5(The Microsoft Speech API)是微软提供的语音API接口,win10系统提供的是最新的5.4版本,pyttsx中say最后调用的就是其中的ISpVoice::Speak接口,MSDN上有详细的介绍。 (https://msdn.microsoft.com/en-us/library/ee125024(v=vs.85).aspx)
从MSDN的介绍中,可以看出输入可以是字符串,也可以是文件名,也可以是XML格式。输入格式为LPCWSTR,指向unicode串。
ISpVoice:: Speak speaks the contents of a text string or file. HRESULT Speak(
LPCWSTR *pwcs,
DWORD dwFlags,
ULONG *pulStreamNumber
);
Parameters pwcs
[in, string] Pointer to the null-terminated text string (possibly containing XML markup) to be synthesized. This value can be NULL when dwFlags is set to SPF_PURGEBEFORESPEAK indicating that any remaining data to be synthesized should be discarded. If dwFlags is set to SPF_IS_FILENAME, this value should point to a null-terminated, fully qualified path to a file.
dwFlags
[in] Flags used to control the rendering process for this call. The flag values are contained in the SPEAKFLAGS enumeration.
pulStreamNumber
[out] Pointer to a ULONG which receives the current input stream number associated with this Speak request. Each time a string is spoken, an associated stream number is returned. Events queued back to the application related to this string will contain this number. If NULL, no value is passed back.
似乎看不出什么,但VS针对LPCWSTR,有两种解析方式,一种是按照unicode体系,另外一种是mbcs体系了。现在utf-8编码明显不正确,证明实际COM组件并不是按照unicode体系来解析的,那似乎应该换mbcs来看看。windows中文系统在mbcs编码体系下,字符集基本使用的就是GBK了。嗯,或许应该试试GBK?
先用其他方式验证一下,参考网上的代码用js写了一段tts转换的,核心读取很简单。
function j2()
{
var fso=new ActiveXObject("SAPI.SpVoice");
fso.Speak(arr[i]);
i=i+1;
setTimeout('j1()',100);
return i;
}
结果,当txt文件为utf-8格式时,读取的结果和python实现的一样;当text文件为简体中文格式时,能够正确朗读。而一般文本编辑器在选择简体中文时,使用的就是GBK编码。
(6)黎明到来
再次修改代码,将文件编码指定为gb18030,执行,结果还是不对...
#-*- coding: gb18030 -*-
import sys
import pyttsx
import chardet reload(sys)
sys.setdefaultencoding("gb18030") text = '你好,中文测试'
engine = pyttsx.init()
engine.say(text)
engine.runAndWait()
嗯,细心的同学已经猜到了,好吧,我承认我记性不好!
之前探究pyttsx时,最终实际是按照下方的链条进行了编码转化:
输入的str(gb18030)--> unicode(系统默认coding,我指定的是"gb18030") --> str(utf8)
看来,如果要使用GBK编码,只能改pyttsx的代码了。难道是pyttsx的源码错了?去github上看看,结果... 好吧,我只能捂脸,大家看代码吧。
sapi5.py中的say函数
def say(self, text):
self._proxy.setBusy(True)
self._proxy.notify('started-utterance')
self._speaking = True
self._tts.Speak(str(text), 19)
第5行已经被改成str了,不再是toUtf8了,而且这个修改时16/5发生的,到底我下了一个什么样的版本? 从github上重新下载版本,安装执行最后一个版本,成功。
原来是我自己的错,反思一下。
慢着慢着,第一次我好像也是从github,打开chrome的历史下载记录:
第一次:https://codeload.github.com/westonpace/pyttsx/zip/master
第二次:https://codeload.github.com/RapidWareTech/pyttsx/zip/master
李逵碰到李鬼? 重新打开第一次的下载,在https://github.com/westonpace/pyttsx 上豁然发现
westonpace/pyttsx
forked from RapidWareTech/pyttsx
哦,还是我疏忽了,大家一定要用正版啊!另外,吐个槽,https://pypi.python.org/pypi/pyttsx 也算是指定的下载点,但还是1.1的版本。
(7)复盘
问题虽然解决了,但还是有疑惑,中文只支持gbk(或者说mbcs体系)么?从结果反推是显然的,但还是要探究一下。
我们知道,python不能直接调用com组件,需要先安装pywin32。pywin32在安装后,会在 /Lib/site-packages/下生成pythonwin、pywin32_system32(我用的是32位)、win32、win32com、win32comext、adodbapi等库,其中和com组件关联的主要是win32com。
在/win32com/client下,有一个makepy.py,它会根据要求生成所需com组件的py文件,生成的文件在 /win32com/gen_py/,我们在/win32com/gen_py/下果然看到
C866CA3A-32F7-11D2-9602-00C04F8EE628x0x5x4.py的文件,C866CA3A-32F7-11D2-9602-00C04F8EE628是CLSID,x0x5x4是版本。
(注:这个可以在 genpy.py中看到, self.base_mod_name = "win32com.gen_py." + str(clsid)[1:-1] + "x%sx%sx%s" % (lcid, major, minor))
# -*- coding: mbcs -*-
# Created by makepy.py version 0.5.01
# By python version 2.7.10 (default, May 23 2015, 09:40:32) [MSC v.1500 32 bit (Intel)]
# From type library '{C866CA3A-32F7-11D2-9602-00C04F8EE628}'
# On Thu May 11 15:31:19 2017
'Microsoft Speech Object Library'
makepy_version = '0.5.01'
python_version = 0x2070af0 import win32com.client.CLSIDToClass, pythoncom, pywintypes
import win32com.client.util
from pywintypes import IID
from win32com.client import Dispatch # The following 3 lines may need tweaking for the particular server
# Candidates are pythoncom.Missing, .Empty and .ArgNotFound
defaultNamedOptArg=pythoncom.Empty
defaultNamedNotOptArg=pythoncom.Empty
defaultUnnamedArg=pythoncom.Empty CLSID = IID('{C866CA3A-32F7-11D2-9602-00C04F8EE628}')
第一行codeing是mbcs,那这个是从哪里来的呢?
从genpy.py中,文件的实际编码格式,是通过file参数指定的encoding来的。
def do_gen_file_header(self):
la = self.typelib.GetLibAttr()
moduleDoc = self.typelib.GetDocumentation(-1)
docDesc = ""
if moduleDoc[1]:
docDesc = moduleDoc[1] # Reset all the 'per file' state
self.bHaveWrittenDispatchBaseClass = 0
self.bHaveWrittenCoClassBaseClass = 0
self.bHaveWrittenEventBaseClass = 0
# You must provide a file correctly configured for writing unicode.
# We assert this is it may indicate somewhere in pywin32 that needs
# upgrading.
assert self.file.encoding, self.file
encoding = self.file.encoding # or "mbcs" print >> self.file, '# -*- coding: %s -*-' % (encoding,)
print >> self.file, '# Created by makepy.py version %s' % (makepy_version,)
print >> self.file, '# By python version %s' % \
(sys.version.replace("\n", "-"),)
回溯代码,发现这个file来源于makepy.py的main函数
def main():
import getopt
hiddenSpec = 1
outputName = None
verboseLevel = 1
doit = 1
bForDemand = bForDemandDefault
try:
opts, args = getopt.getopt(sys.argv[1:], 'vo:huiqd')
for o,v in opts:
if o=='-h':
hiddenSpec = 0
elif o=='-o':
outputName = v
elif o=='-v':
verboseLevel = verboseLevel + 1
elif o=='-q':
verboseLevel = verboseLevel - 1
elif o=='-i':
if len(args)==0:
ShowInfo(None)
else:
for arg in args:
ShowInfo(arg)
doit = 0
elif o=='-d':
bForDemand = not bForDemand except (getopt.error, error), msg:
sys.stderr.write (str(msg) + "\n")
usage() if bForDemand and outputName is not None:
sys.stderr.write("Can not use -d and -o together\n")
usage() if not doit:
return 0
if len(args)==0:
rc = selecttlb.SelectTlb()
if rc is None:
sys.exit(1)
args = [ rc ] if outputName is not None:
path = os.path.dirname(outputName)
if path is not '' and not os.path.exists(path):
os.makedirs(path)
if sys.version_info > (3,0):
f = open(outputName, "wt", encoding="mbcs")
else:
import codecs # not available in py3k.
f = codecs.open(outputName, "w", "mbcs")
else:
f = None for arg in args:
GenerateFromTypeLibSpec(arg, f, verboseLevel = verboseLevel, bForDemand = bForDemand, bBuildHidden = hiddenSpec) if f:
f.close()
可以看到,在文件指定时,打开的时候指定了mbcs的方式;在文件不指定时,后边默认也会通过mbcs来编码。因此从这里可以看到,win32com目前的版本最终都会转成mbcs编码格式。
OK,至此可以看出,win32com使用的是mbcs的编码格式,问题终于搞定。
4、总结
问题终于圆满解决了,总结一下。
(1)使用前检查是否为最新版,一定去github上。
(2)不光是最新版,还得正本清源,去源头看看,不要犯我的错误,搜索后没细看就下载了。
(3)开源时代,出现问题,多研究源码。
(4)python2的问题,编码转换太复杂,如果出现问题, 是需要具体问题具体分析的。
pyttsx的中文语音识别问题及探究之路的更多相关文章
- 基于深度学习的中文语音识别系统框架(pluse)
目录 声学模型 GRU-CTC DFCNN DFSMN 语言模型 n-gram CBHG 数据集 本文搭建一个完整的中文语音识别系统,包括声学模型和语言模型,能够将输入的音频信号识别为汉字. 声学模型 ...
- GRU-CTC中文语音识别
目录 基于keras的中文语音识别 音频文件特征提取 文本数据处理 数据格式处理 构建模型 模型训练及解码 aishell数据转化 该项目github地址 基于keras的中文语音识别 该项目实现了G ...
- python使用vosk进行中文语音识别
操作系统:Windows10 Python版本:3.9.2 vosk是一个离线开源语音识别工具,它可以识别16种语言,包括中文. 这里记录下使用vosk进行中文识别的过程,以便后续查阅. vosk地址 ...
- Android Camera探究之路——起步
Android Camera探究之路--起步 Camera在手机中有着举足轻重的地位,无论是二维码还是照片.识别.都离不开摄像头,本文将对Android中的Camera进行全面解析. 权限镇楼: &l ...
- Unity中使用百度中文语音识别功能
下面是API类 Asr.cs using System; using System.Collections; using System.Collections.Generic; using Unity ...
- Spring Security探究之路之开始
前言 在Spring Security介绍中,我们分析到了根据请求获取匹配的SecurityFilterChain,这个类中包含了一组Filter 接下来我们从这些Filter开始探究之旅 Sprin ...
- Kafka探究之路-命令小结
操作kafka之前,要先启动安装好的zk ,因为kafka的数据都保存在zk中,zk相当于是kafka的数据库吧. 安装的zk kafka 一定要按照书上,网上的教程,将相应的配置文件全部改成自己的, ...
- Kafak探究之路- 内部结构小结
1.框架与工作流 2 内部结构 kafka的每个主题分区的数据在 first-0(主题名-分区号)文件夹下,保存 n组xxx.log文件与xxx.index文件.log文件存发送消息的元数据,每个大小 ...
- Python学习实践 | speech智能语音模块
最近的生活.学习节奏很是容易被打断,终于,在今天,既实习结束之后,夏令营也结束了. 前几天,一个人在复习地很累的时候,又重新将Python捡了起来,看了挺多的知识点. 真是太有意(wu)思(liao) ...
随机推荐
- Python全栈工程师(递归函数、闭包)
ParisGabriel 每天坚持手写 一天一篇 决定坚持几年 全栈工程师 Python人工智能从入门到精通 函数式编程: 是指用一系列函数解决问题 每一个函数完成细 ...
- corosync.conf
##totem定义集群内各节点间是如何通信的,totem本是一种协议,专用于corosync专用于各节点间的协议,协议是有版本的 totem { ##版本号 version: ##安全认证on|off ...
- Where can I find the IPA logs
Retrieving the IPA logs will differ depending on which base image was used. Operating system that do ...
- HDU 4031 Attack (线段树)
成功袭击次数=所有袭击次数-成功防守次数 需要一个辅助pre来记录上一次袭击成功什么时候,对于每个查询,从上一次袭击成功开始,每隔t更新一次. 感觉这样做最坏时间复杂度是O(n^2),这里 说是O(q ...
- 团队项目-第三次scrum 会议
时间:10.25 时长:30分钟 地点:线上 工作情况 团队成员 已完成任务 待完成任务 解小锐 根据初步讨论结果编写初步的api文档 编写project和projectGenerator类 陈鑫 采 ...
- vue vscode 开始
E:\Html\4 Vue版>npm config set registry https://registry.npm.taobao.org E:\Html\4 Vue版>npm i &g ...
- 详解Linux运维工程师应具备的十大技能
Linux系统如果是学习可以选用Redhat或CentOS,特别是CentOS在企业中用得最多,当然还会有其它版本的,但学习者还是以这2个版本学习就行,因为这两个版本都是兄弟,没区别的,有空可以再研究 ...
- P1494 [国家集训队]小Z的袜子/莫队学习笔记(误
P1494 [国家集训队]小Z的袜子 题目描述 作为一个生活散漫的人,小\(Z\)每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小\(Z\)再也无法忍受这恼人的找袜子过程,于是他 ...
- Android四大组件:Service
前言 Service作为Android四大组件之一,应用非常广泛 本文将介绍对Service进行全面介绍(基础认识.生命周期.使用和应用场景) 目录 目录 1. 基础知识 定义:服务,属于Androi ...
- 重量WeightFormatUtil辅助类
package com.jlb.scan.util; import java.text.DecimalFormat; public class WeightFormatUtil { public st ...