(转载请注明出处)
使用SDK: Kinect for Windows SDK v2.0 public preview
这次探讨语音识别。之前都是一节写两部分,这次反过来了,分为上下两部分。
语音识别算是使用官方SDK的一个理由吧,不然都用OpenNI了,毕竟微软在SR(语音识别)上的成果还是不错的。
首先,您需要下载语音识别的SDK、运行时库以及您想支持的运行时语言,运行时语言里面SR代表语音识别,
TTS代表文本转语音,根据您的选择吧。本人选择下载了美国英语与大陆汉语。当然,坑爹的微软还专门为Kinect
准备Kinect运行时语言,支持语言较少,不过连日语都支持,竟然不支持汉语
或许仅仅是日语发音简单吧。
如果您和本人一样使用的是VS Express,那么请再下载WDK7.1,这个语音库需要一点点ATL支
持(貌似就COM智能指针),坑爹的微软在Express里面没有ATL库,为了获取合法ATL库需要下载这个
WDK7.1,里面有能合法使用的ALT库。我直接安装到C盘,请在项目里面包含这三个库的目录
语音平台、WDK、Kinect
有您自己的需要请自行包含其他库,最好将语音平台放到第一位。
目前语音识别应该只能识别PCM编码,处理后的数据是浮点编码,我们不能直接使用SDK默认提供的IStream。
是的,基于COM组件的通用性,我们只需继承IStream实现一个Stream类即可完成音频流的处理。
这个类主要实现Read方法即可,Seek方法在SR中也算比较重要不过Kinect还不支持,这个方法直接返回S_OK即可
实现这个类有两种办法,一种是基于前一节说的音频帧。自己用一个比较大的缓冲区(最好设计上是循环的),
获取音频帧再将数据写在里面,Read时将数据写入即可,比较麻烦。还有一个就是SDK提供例子里面使用的:
傀儡战术。
获取SDK提供的默认的IStream,Read的时候Read这个傀儡即可,数据获取完毕后进行转换即可,代码如下:
- // IStream Read方法的实现
- STDMETHODIMP KinectAudioStreamWrapper::Read(void *pBuffer, ULONG cbBuffer, ULONG *pcbRead){
- // 参数检查
- if (!pBuffer || !pcbRead) return E_INVALIDARG;
- // 在读取前未使用 m_SpeechActive 返回S_OK
- if (!m_SpeechActive){
- *pcbRead = cbBuffer;
- return S_OK;
- }
- HRESULT hr = S_OK;
- // 目标是将浮点编码转换成16位PCM编码
- INT16* const p16Buffer = reinterpret_cast<INT16*>(pBuffer);
- // 长度倍数
- const int multiple = sizeof(float) / sizeof(INT16);
- // 检查缓冲区释放足够
- auto float_buffer_size = cbBuffer / multiple;
- if (float_buffer_size > m_uFloatBuferSize){
- // 不够就重新申请内存
- m_uFloatBuferSize = float_buffer_size;
- if (m_pFloatBuffer) delete[]m_pFloatBuffer;
- m_pFloatBuffer = new float[m_uFloatBuferSize];
- }
- // 缓冲区写入进度 字节为单位
- BYTE* pWriteProgress = reinterpret_cast<BYTE*>(m_pFloatBuffer);
- // 目前读取量
- ULONG bytesRead = 0;
- // 需要读取量
- ULONG bytesNeed = cbBuffer * multiple;
- // 循环读取
- while (true){
- // 已经不需要语音的情况下
- if (!m_SpeechActive){
- *pcbRead = cbBuffer;
- hr = S_OK;
- break;
- }
- // 从包装对象获取数据
- hr = m_p32BitAudio->Read(pWriteProgress, bytesNeed, &bytesRead);
- bytesNeed -= bytesRead;
- pWriteProgress += bytesRead;
- // 检查是否足够
- if (!bytesNeed){
- *pcbRead = cbBuffer;
- break;
- }
- // 不然就睡一个时间片的时间
- Sleep(20);
- }
- // 数据处理 float -> 16bit PCM
- if (!bytesNeed){
- for (UINT i = 0; i < cbBuffer / multiple; i++) {
- float sample = m_pFloatBuffer[i];
- // 区间保证
- //sample = max(min(sample, 1.f), -1.f);
- if (sample > 1.f) sample = 1.f;
- if (sample < -1.f) sample = -1.f;
- // 数据转换
- float sampleScaled = sample * (float)SHRT_MAX;
- p16Buffer[i] = (sampleScaled > 0.f) ? (INT16)(sampleScaled + 0.5f) : (INT16)(sampleScaled - 0.5f);
- }
- }
- return hr;
- }
请注意,语音识别引擎初始化时要获取一定的音频数据才能完成,很可能花上几秒甚至十几秒来初始化,这很蛋疼,
不知道微软怎么想的。为此,我们增加一个变量表示SR是否处于激活状态,没被激活时,返回假数据欺骗SR引擎即可。
这样几乎就无需花时间等待初始化了。
我们这里语音识别使用的方法是载入静态SRGS语法文件,当然可以载入动态语法,不过详细的请翻阅官方文档。
至于SRGS的语法可以翻看W3C的文档,也可以看微软的文档。
好,让我们写一个SRGS语法文件吧,SRGS是基于xml的,后缀名就使用xml吧。我这里使用的是汉语,
毕竟文字处理上,汉语优势很大,几个字就能表示一个句子。经本人测试,汉语里面可以识别英语,但是识别率
- <?xml version="1.0" encoding="UTF-8" ?>
- <grammar version="1.0" xml:lang="zh-CN" mode="voice" root="根语音" xmlns="http://www.w3.org/2001/06/grammar" tag-format="semantics/1.0">
- <rule id="根语音" scope="public">
- <one-of>
- <item>前进</item>
- </one-of>
- <one-of>
- <item>后退</item>
- </one-of>
- </rule>
- </grammar>
这样差不多就是最简单的SRGS文件吧,重要的有:
开头, xml的标准,使用utf8即可
grammar标签是主标签,里面xml:lang属性选择您的语言,大陆汉语就是zh-CN了。还有就是root,表示根标签的名字,
SR引擎就从这里识别基本短语,其他的照写即可。
每个规则使用rule标签,多选一使用one-of标签,基本短语使用item,规则引用使用ruleref,tag标签能使用字符串或者
貌似js的脚本标上自己想要的数据,还有token标签,这些可以查看文档了解。
我们的目标实现下面短语:
1.我在XX YY发现了ZZ AA BB
比如我在脚(XX)上(YY)发现了两(ZZ)枚(AA)高爆穿甲弹(BB)
XX表示地点,这个信息很重要
YY表示相对方位,这个信息不重要
ZZ表示数量,这个信息很重要
AA表示量词,几乎没用
BB表示物品,这个信息很重要
- <rule id="发现东西">
- <example> 我在脚上发现两枚穿甲弹 </example>
- <item>我在</item>
- <ruleref uri="#地点"/>
- <ruleref uri="#相对位置"/>
- <item>发现</item>
- <item repeat="0-1">了</item>
- <ruleref uri="#数量"/>
- <ruleref uri="#量词"/>
- <ruleref uri="#目标物体"/>
- </rule>
- <rule id="地点">
- <example> 脚 </example>
- <example> 房子 </example>
- <one-of>
- <item>脚</item>
- <item>房子</item>
- <item>船</item>
- <item>头</item>
- </one-of>
- </rule>
- <rule id="相对位置">
- <example> 上 </example>
- <one-of>
- <item>上</item>
- <item>上面</item>
- <item>里面</item>
- <item>旁边</item>
- <item>附近</item>
- </one-of>
- </rule>
- <rule id="数量">
- <example> 两 </example>
- <one-of>
- <item>一</item>
- <item>二</item>
- <item>两</item>
- <item>三</item>
- <item>四</item>
- <item>五</item>
- <item>六</item>
- <item>七</item>
- <item>八</item>
- <item>九</item>
- <item>十</item>
- </one-of>
- </rule>
- <rule id="量词">
- <example> 枚 </example>
- <one-of>
- <item>枚</item>
- <item>个</item>
- <item>块</item>
- <item>片</item>
- <item>辆</item>
- <item>架</item>
- <item>次</item>
- <item>部</item>
- <item>台</item>
- <item>把</item>
- </one-of>
- </rule>
- <rule id="目标物体">
- <example> 坦克 </example>
- <one-of>
- <item>高爆穿甲弹</item>
- <item>穿甲弹</item>
- <item>坦克</item>
- <item>铅笔</item>
- <item>电脑</item>
- <item>苹果</item>
- <item>锤子</item>
- <item>手机</item>
- <item>阿姆斯特朗回旋加速喷气式阿姆斯特朗炮</item>
- </one-of>
- </rule>
这部分就差不多了,再在“根语言”里面更新:
- <rule id="根语音" scope="public">
- <one-of>
- <item>
- <ruleref uri="#发现东西"/>
- </item>
- </one-of>
- </rule>
如果再增加一个,"战况"
- <rule id="根语音" scope="public">
- <one-of>
- <item>
- <ruleref uri="#发现东西"/>
- </item>
- <item>
- <ruleref uri="#战况"/>
- </item>
- </one-of>
- </rule>
- </pre><p></p><pre>
在实际使用时,会遇到非常多的同义词,或者选择支很多。如果把识别的字符串进行比较的话很吃力,我们就可以用tag标签
于是在“数量” 那里可以这样写:out = 10;就能对外输出10,
- <rule id="数量">
- <example> 两 </example>
- <one-of>
- <item>一<tag>out=1;</tag></item>
- <item>二<tag>out=2;</tag></item>
- <item>两<tag>out=2;</tag></item>
- <item>三<tag>out=3;</tag></item>
- <item>四<tag>out=4;</tag></item>
- <item>五<tag>out=5;</tag></item>
- <item>六<tag>out=6;</tag></item>
- <item>七<tag>out=7;</tag></item>
- <item>八<tag>out=8;</tag></item>
- <item>九<tag>out=9;</tag></item>
- <item>十<tag>out=10;</tag></item>
- </one-of>
- </rule>
也能使用字符串。但是数据处理是数字才方便,不是么
类似地,我们将“发现东西”更新至:
- <rule id="发现东西">
- <example> 我在脚上发现两枚穿甲弹 </example>
- <item>我在</item>
- <ruleref uri="#地点"/>
- <tag> out.地点 = rules.地点; </tag>
- <ruleref uri="#相对位置"/>
- <item>发现</item>
- <item repeat="0-1">了</item>
- <ruleref uri="#数量"/>
- <tag> out.数量 = rules.数量; </tag>
- <ruleref uri="#量词"/>
- <ruleref uri="#目标物体"/>
- <tag> out.物体 = rules.目标物体; </tag>
- </rule>
毕竟是脚本,看就能看懂。 out.A = rules.B,AB不一定相同,但是B一定和前面引用的id一样
现在我们增加一个短语:“战况” 比如
我方击毁敌方厕所
也是很简单,我就将代码直接放在这了:
- <?xml version="1.0" encoding="UTF-8" ?>
- <grammar version="1.0" xml:lang="zh-CN" mode="voice" root="根语音" xmlns="http://www.w3.org/2001/06/grammar" tag-format="semantics/1.0">
- <rule id="根语音" scope="public">
- <one-of>
- <item>
- <ruleref uri="#发现东西"/>
- <tag> out.发现东西 = rules.发现东西; </tag>
- </item>
- <item>
- <ruleref uri="#战况"/>
- <tag> out.战况 = rules.战况; </tag>
- </item>
- </one-of>
- </rule>
- <rule id="战况">
- <example> 我们击毁了敌方厕所 </example>
- <example> 他们击穿我方的装甲 </example>
- <ruleref uri="#人物对象"/>
- <tag> out.主语 = rules.人物对象; </tag>
- <ruleref uri="#战况动词"/>
- <tag> out.谓语 = rules.战况动词; </tag>
- <item repeat="0-1">了</item>
- <ruleref uri="#人物对象"/>
- <tag> out.对象 = rules.人物对象; </tag>
- <item repeat="0-1">的</item>
- <ruleref uri="#战况名词"/>
- <tag> out.宾语 = rules.战况名词; </tag>
- </rule>
- <rule id="人物对象">
- <example> 我们 </example>
- <example> 他们 </example>
- <one-of>
- <item>我们<tag>out=0;</tag></item>
- <item>他们<tag>out=1;</tag></item>
- <item>我方<tag>out=0;</tag></item>
- <item>敌方<tag>out=1;</tag></item>
- </one-of>
- </rule>
- <rule id="战况动词">
- <example> 击毁 </example>
- <example> 击败 </example>
- <one-of>
- <item>击毁<tag>out=0;</tag></item>
- <item>击败<tag>out=1;</tag></item>
- <item>击穿<tag>out=2;</tag></item>
- </one-of>
- </rule>
- <rule id="战况名词">
- <example> 装甲 </example>
- <example> 计算机 </example>
- <one-of>
- <item>装甲<tag>out=0;</tag></item>
- <item>厕所<tag>out=1;</tag></item>
- <item>计算机<tag>out=2;</tag></item>
- <item>电脑<tag>out=2;</tag></item>
- <item>核弹发射井<tag>out=3;</tag></item>
- </one-of>
- </rule>
- <rule id="发现东西">
- <example> 我在脚上发现两枚穿甲弹 </example>
- <item>我在</item>
- <ruleref uri="#地点"/>
- <tag> out.地点 = rules.地点; </tag>
- <ruleref uri="#相对位置"/>
- <item>发现</item>
- <item repeat="0-1">了</item>
- <ruleref uri="#数量"/>
- <tag> out.数量 = rules.数量; </tag>
- <ruleref uri="#量词"/>
- <ruleref uri="#目标物体"/>
- <tag> out.物体 = rules.目标物体; </tag>
- </rule>
- <rule id="地点">
- <example> 脚 </example>
- <example> 房子 </example>
- <one-of>
- <item>脚<tag>out=0;</tag></item>
- <item>房子<tag>out=1;</tag></item>
- <item>船<tag>out=2;</tag></item>
- <item>头<tag>out=3;</tag></item>
- </one-of>
- </rule>
- <rule id="相对位置">
- <example> 上 </example>
- <one-of>
- <item>上</item>
- <item>上面</item>
- <item>里面</item>
- <item>旁边</item>
- <item>附近</item>
- </one-of>
- </rule>
- <rule id="数量">
- <example> 两 </example>
- <one-of>
- <item>一<tag>out=1;</tag></item>
- <item>二<tag>out=2;</tag></item>
- <item>两<tag>out=2;</tag></item>
- <item>三<tag>out=3;</tag></item>
- <item>四<tag>out=4;</tag></item>
- <item>五<tag>out=5;</tag></item>
- <item>六<tag>out=6;</tag></item>
- <item>七<tag>out=7;</tag></item>
- <item>八<tag>out=8;</tag></item>
- <item>九<tag>out=9;</tag></item>
- <item>十<tag>out=10;</tag></item>
- </one-of>
- </rule>
- <rule id="量词">
- <example> 枚 </example>
- <one-of>
- <item>枚</item>
- <item>个</item>
- <item>块</item>
- <item>片</item>
- <item>辆</item>
- <item>架</item>
- <item>次</item>
- <item>部</item>
- <item>台</item>
- <item>把</item>
- </one-of>
- </rule>
- <rule id="目标物体">
- <example> 坦克 </example>
- <one-of>
- <item>高爆穿甲弹<tag>out=0;</tag></item>
- <item>穿甲弹<tag>out=1;</tag></item>
- <item>坦克<tag>out=2;</tag></item>
- <item>铅笔<tag>out=3;</tag></item>
- <item>电脑<tag>out=4;</tag></item>
- <item>苹果<tag>out=5;</tag></item>
- <item>锤子<tag>out=6;</tag></item>
- <item>手机<tag>out=7;</tag></item>
- <item>阿姆斯特朗回旋加速喷气式阿姆斯特朗炮<tag>out=8;</tag></item>
- </one-of>
- </rule>
- </grammar>
这次就到这里了,这次的SRGS范例希望帮助各位,下一节再写C++部分代码吧。
- 本文已收录于以下专栏:
- Kinect for Windows SDK v2.0 开发笔记