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编码,结果发音并不是输入的内容。

 1 #-*- coding: UTF-8 -*-
 2 import sys
 3 import pyttsx
 4
 5 reload(sys)
 6 sys.setdefaultencoding("utf-8")
 7
 8 text = u‘你好,中文测试‘
 9 engine = pyttsx.init()
10 engine.say(text)
11 engine.runAndWait()

   (2)再试探:

  或许是pyttsx内部转换的问题?将传入类型str类型,依然为utf-8编码,但发音依旧不对,和之前一样。

 1 #-*- coding: UTF-8 -*-
 2 import sys
 3 import pyttsx
 4
 5 reload(sys)
 6 sys.setdefaultencoding("utf-8")
 7
 8 text = ‘你好,中文测试‘
 9 engine = pyttsx.init()
10 engine.say(text)
11 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

 1  def __init__(self, engine, driverName, debug):
 2         ‘‘‘
 3         Constructor.
 4
 5         @param engine: Reference to the engine that owns the driver
 6         @type engine: L{engine.Engine}
 7         @param driverName: Name of the driver module to use under drivers/ or
 8             None to select the default for the platform
 9         @type driverName: str
10         @param debug: Debugging output enabled or not
11         @type debug: bool
12         ‘‘‘
13         if driverName is None:
14             # pick default driver for common platforms
15             if sys.platform == ‘darwin‘:
16                 driverName = ‘nsss‘
17             elif sys.platform == ‘win32‘:
18                 driverName = ‘sapi5‘
19             else:
20                 driverName = ‘espeak‘
21         # import driver module
22         name = ‘pyttsx.drivers.%s‘ % driverName
23         self._module = importlib.import_module(name)
24         # build driver instance
25         self._driver = self._module.buildDriver(weakref.proxy(self))
26         # initialize refs
27         self._engine = engine
28         self._queue = []
29         self._busy = True
30         self._name = None
31         self._iterator = None
32         self._debug = debug

 在pyttsx\driver\sapi5.py中可以看到say函数,在调用speak时,有一个toUtf8的转换,难道最终传入的是utf8编码格式的?

1     def say(self, text):
2         self._proxy.setBusy(True)
3         self._proxy.notify(‘started-utterance‘)
4         self._speaking = True
5         self._tts.Speak(toUtf8(text), 19)

继续向下探,在toUtf8的定义在pyttsx\driver\__init__.py中,只有1行

1 def toUtf8(value):
2     ‘‘‘
3     Takes in a value and converts it to a text (unicode) type.  Then decodes that
4     type to a byte array encoded in utf-8.  In 2.X the resulting object will be a
5     str and in 3.X the resulting object will be bytes.  In both 2.X and 3.X any
6     object can be passed in and the object‘s __str__ will be used (or __repr__ if
7     __str__ is not defined) if the object is not already a text type.
8     ‘‘‘
9     return six.text_type(value).encode(‘utf-8‘)

     继续深入,pyttsx\six.py中,对text_type有定义

 1 # Useful for very coarse version differentiation.
 2 PY2 = sys.version_info[0] == 2
 3 PY3 = sys.version_info[0] == 3
 4
 5 if PY3:
 6     string_types = str,
 7     integer_types = int,
 8     class_types = type,
 9     text_type = str
10     binary_type = bytes
11
12     MAXSIZE = sys.maxsize
13 else:
14     string_types = basestring,
15     integer_types = (int, long)
16     class_types = (type, types.ClassType)
17     text_type = unicode
18     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转换的,核心读取很简单。

1         function j2()
2         {
3             var fso=new ActiveXObject("SAPI.SpVoice");
4             fso.Speak(arr[i]);
5             i=i+1;
6             setTimeout(‘j1()‘,100);
7             return i;
8         }

结果,当txt文件为utf-8格式时,读取的结果和python实现的一样;当text文件为简体中文格式时,能够正确朗读。而一般文本编辑器在选择简体中文时,使用的就是GBK编码。

(6)黎明到来

再次修改代码,将文件编码指定为gb18030,执行,结果还是不对...

 1 #-*- coding: gb18030  -*-
 2 import sys
 3 import pyttsx
 4 import chardet
 5
 6 reload(sys)
 7 sys.setdefaultencoding("gb18030")
 8
 9 text = ‘你好,中文测试‘
10 engine = pyttsx.init()
11 engine.say(text)
12 engine.runAndWait()

 嗯,细心的同学已经猜到了,好吧,我承认我记性不好!

  之前探究pyttsx时,最终实际是按照下方的链条进行了编码转化:

输入的str(gb18030)--> unicode(系统默认coding,我指定的是"gb18030") --> str(utf8)

看来,如果要使用GBK编码,只能改pyttsx的代码了。难道是pyttsx的源码错了?去github上看看,结果... 好吧,我只能捂脸,大家看代码吧。

sapi5.py中的say函数

1     def say(self, text):
2         self._proxy.setBusy(True)
3         self._proxy.notify(‘started-utterance‘)
4         self._speaking = True
5         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))

 1 # -*- coding: mbcs -*-
 2 # Created by makepy.py version 0.5.01
 3 # By python version 2.7.10 (default, May 23 2015, 09:40:32) [MSC v.1500 32 bit (Intel)]
 4 # From type library ‘{C866CA3A-32F7-11D2-9602-00C04F8EE628}‘
 5 # On Thu May 11 15:31:19 2017
 6 ‘Microsoft Speech Object Library‘
 7 makepy_version = ‘0.5.01‘
 8 python_version = 0x2070af0
 9
10 import win32com.client.CLSIDToClass, pythoncom, pywintypes
11 import win32com.client.util
12 from pywintypes import IID
13 from win32com.client import Dispatch
14
15 # The following 3 lines may need tweaking for the particular server
16 # Candidates are pythoncom.Missing, .Empty and .ArgNotFound
17 defaultNamedOptArg=pythoncom.Empty
18 defaultNamedNotOptArg=pythoncom.Empty
19 defaultUnnamedArg=pythoncom.Empty
20
21 CLSID = IID(‘{C866CA3A-32F7-11D2-9602-00C04F8EE628}‘)

第一行codeing是mbcs,那这个是从哪里来的呢?

从genpy.py中,文件的实际编码格式,是通过file参数指定的encoding来的。

 1  def do_gen_file_header(self):
 2     la = self.typelib.GetLibAttr()
 3     moduleDoc = self.typelib.GetDocumentation(-1)
 4     docDesc = ""
 5     if moduleDoc[1]:
 6       docDesc = moduleDoc[1]
 7
 8     # Reset all the ‘per file‘ state
 9     self.bHaveWrittenDispatchBaseClass = 0
10     self.bHaveWrittenCoClassBaseClass = 0
11     self.bHaveWrittenEventBaseClass = 0
12     # You must provide a file correctly configured for writing unicode.
13     # We assert this is it may indicate somewhere in pywin32 that needs
14     # upgrading.
15     assert self.file.encoding, self.file
16     encoding = self.file.encoding # or "mbcs"
17
18     print >> self.file, ‘# -*- coding: %s -*-‘ % (encoding,)
19     print >> self.file, ‘# Created by makepy.py version %s‘ % (makepy_version,)
20     print >> self.file, ‘# By python version %s‘ % 21                         (sys.version.replace("\n", "-"),)

回溯代码,发现这个file来源于makepy.py的main函数

 1 def main():
 2     import getopt
 3     hiddenSpec = 1
 4     outputName = None
 5     verboseLevel = 1
 6     doit = 1
 7     bForDemand = bForDemandDefault
 8     try:
 9         opts, args = getopt.getopt(sys.argv[1:], ‘vo:huiqd‘)
10         for o,v in opts:
11             if o==‘-h‘:
12                 hiddenSpec = 0
13             elif o==‘-o‘:
14                 outputName = v
15             elif o==‘-v‘:
16                 verboseLevel = verboseLevel + 1
17             elif o==‘-q‘:
18                 verboseLevel = verboseLevel - 1
19             elif o==‘-i‘:
20                 if len(args)==0:
21                     ShowInfo(None)
22                 else:
23                     for arg in args:
24                         ShowInfo(arg)
25                 doit = 0
26             elif o==‘-d‘:
27                 bForDemand = not bForDemand
28
29     except (getopt.error, error), msg:
30         sys.stderr.write (str(msg) + "\n")
31         usage()
32
33     if bForDemand and outputName is not None:
34         sys.stderr.write("Can not use -d and -o together\n")
35         usage()
36
37     if not doit:
38         return 0
39     if len(args)==0:
40         rc = selecttlb.SelectTlb()
41         if rc is None:
42             sys.exit(1)
43         args = [ rc ]
44
45     if outputName is not None:
46         path = os.path.dirname(outputName)
47         if path is not ‘‘ and not os.path.exists(path):
48             os.makedirs(path)
49         if sys.version_info > (3,0):
50             f = open(outputName, "wt", encoding="mbcs")
51         else:
52             import codecs # not available in py3k.
53             f = codecs.open(outputName, "w", "mbcs")
54     else:
55         f = None
56
57     for arg in args:
58         GenerateFromTypeLibSpec(arg, f, verboseLevel = verboseLevel, bForDemand = bForDemand, bBuildHidden = hiddenSpec)
59
60     if f:
61         f.close()

可以看到,在文件指定时,打开的时候指定了mbcs的方式;在文件不指定时,后边默认也会通过mbcs来编码。因此从这里可以看到,win32com目前的版本最终都会转成mbcs编码格式。

OK,至此可以看出,win32com使用的是mbcs的编码格式,问题终于搞定。

4、总结

问题终于圆满解决了,总结一下。

(1)使用前检查是否为最新版,一定去github上。

(2)不光是最新版,还得正本清源,去源头看看,不要犯我的错误,搜索后没细看就下载了。

(3)开源时代,出现问题,多研究源码。

(4)python2的问题,编码转换太复杂,如果出现问题, 是需要具体问题具体分析的。

时间: 2024-10-08 05:46:08

pyttsx的中文语音识别问题及探究之路的相关文章

Android Camera探究之路——起步

Android Camera探究之路--起步 Camera在手机中有着举足轻重的地位,无论是二维码还是照片.识别.都离不开摄像头,本文将对Android中的Camera进行全面解析. 权限镇楼: <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE&

Unity中使用百度中文语音识别功能

下面是API类 Asr.cs using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 用户解析token的json数据 /// </summary> class TokenResponse { public string access_token = null; } public class Asr { public

小程序实现语音识别转文字,坑路历程

最近为小程序增加语音识别转文字的功能,坑路不断,特此记录. 微信开发者工具 开发者工具上的录音文件与移动端格式不同,暂时只可在工具上进行播放调试,无法直接播放或者在客户端上播放 debug的时候发现,工具上录音的路径是http://tmp/xxx.mp3,客户端上录音是wxfile://xxx.mp3. 忽悠呢,不是格式不同,是映射路径不同.其实做个兼容也不难,每次提示一行文字,很丑. 采样率与编码码率限制 每种采样率有对应的编码码率范围有效值,设置不合法的采样率或编码码率会导致录音失败.详细看

Spring Security探究之路之开始

前言 在Spring Security介绍中,我们分析到了根据请求获取匹配的SecurityFilterChain,这个类中包含了一组Filter 接下来我们从这些Filter开始探究之旅 Spring Security Filter简介 AuthenticationFilter中的attemptAuthentication方法调用AuthenticationManager(interface)的authenticate方法,AuthenticationManager的实际是现实ProvideM

(推荐)叮当——中文语音对话机器人

叮当是一款可以工作在 Raspberry Pi 上的开源中文语音对话机器人/智能音箱项目,目的是让中国的Hacker们也能快速打造个性化的智能音箱. github地址:https://github.com/wzpan/dingdang-robot 主要是github上已经提供了打包好的镜像文件,只需少量操作,烧录进入sd卡即可使用体验,推荐给大家! 特性 叮当包括以下诸多特性: 模块化.功能插件.语音识别.语音合成.对话机器人都做到了高度模块化,第三方插件单独维护,方便继承和开发自己的插件. 微

利用微软认知服务实现语音识别功能

想实现语音识别已经很久了,也尝试了许多次,终究还是失败了,原因很多,识别效果不理想,个人在技术上没有成功实现,种种原因,以至于花费了好多时间在上面.语音识别,我尝试过的有科大讯飞.百度语音,微软系.最终还是喜欢微软系的简洁高效.(勿喷,纯个人感觉) 最开始自己的想法是我说一句话(暂且在控制台上做Demo),控制台程序能识别我说的是什么,然后显示出来,并且根据我说的信息,执行相应的行为.(想法很美好,现实很糟心)初入语音识别,各种错误各种来,徘徊不定的选择哪家公司的api,百度上查找各种语音识别的

阿里云小Ai战胜人类速记,中文人工智能大时代崛起

还记得前不久2016谷歌开发者I/O大会上展示的人工智能语音搜索助理Google Assistant以及基于该语音助理的智能硬件Google Home吗?它们的背后是准确的英文语音识别能力. 如今,以微软Cortana.苹果Siri和Google Assistant为第一军团的人工智能语音识别技术正在大规模商业化与产品化,英文人工智能大时代已经开始.那么,相应的中文市场呢? 中文语音识别挑战人类速记 在6月15日的阿里云厦门云栖大会上,阿里云人工智能小Ai在上千观众的面前完成了现场中文语音识别的

语音识别概述

后验概率最大,即为判别结果 HTK Hvite解码器   Sphinx解码器 TODE解码器,生硬,修改繁琐. WFST 扩充,简单高效. 有限状态机模型被用于大词汇量连续中文语音识别系统中. 其操作思路是将传统语音识别系统中的数学模型,分别转换成有限状态机模型,再将转换后的模型进行有效地整合及优化,得到搜索空间. 有限状态自动机(Finite-state Automata,FSA) 用点表示状态,带箭头的方向线段表示转移,转移上的字符为输入字元:用加粗圆圈表示初始状态,双线圆圈表示终止状态 节

Kaldi 语音识别基础教程

Kaldi 介绍 Kaldi 是由 C++ 编写的语音识别工具,其目的在于为语音识别研究者提供一个研究和使用的平台. Kaldi 环境搭建 本文主要通过使用 Docker 和 Nvidia-docker 构建 Ubuntu 环境对 Kaldi 进行搭建.Docker 针对的是无 GPU 的环境,Nvidia-docker 针对的是需要使用 GPU 计算的环境,如果读者机器上存在 GPU 计算资源,请使用 Nvidia-docker,使用 Nvidia 官方提供的 CUDA 镜像,可以省去安装 C