Kinect for Windows SDK v2.0 开发笔记 (七)语音识别(上)

(转载请注明出处)

使用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++部分代码吧。



©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页