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

转载于:https://blog.csdn.net/dustpg/article/details/38202371

使用SDK: Kinect for Windows SDK v2.0 public preview

紧接上节,这次要怎么初始化Kinect呢,很简单:

[cpp]  view plain  copy
  1. // 初始化Kinect  
  2. HRESULT ThisApp::init_kinect(){  
  3.     IAudioSource* pAudioSource = nullptr;  
  4.     IAudioBeamList* pAudioBeamList = nullptr;  
  5.     // 查找当前默认Kinect  
  6.     HRESULT hr = ::GetDefaultKinectSensor(&m_pKinect);  
  7.     // 绅士地打开Kinect  
  8.     if (SUCCEEDED(hr)){  
  9.         hr = m_pKinect->Open();  
  10.     }  
  11.     // 获取音频源  
  12.     if (SUCCEEDED(hr)){  
  13.         hr = m_pKinect->get_AudioSource(&pAudioSource);  
  14.     }  
  15.     // 获取音频链表  
  16.     if (SUCCEEDED(hr)){  
  17.         hr = pAudioSource->get_AudioBeams(&pAudioBeamList);  
  18.     }  
  19.     // 获取音频  
  20.     if (SUCCEEDED(hr)){  
  21.         hr = pAudioBeamList->OpenAudioBeam(0, &m_pAudioBeam);  
  22.     }  
  23.     // 获取输入音频流  
  24.     if (SUCCEEDED(hr)){  
  25.         IStream* pStream = nullptr;  
  26.         hr = m_pAudioBeam->OpenInputStream(&pStream);  
  27.         // 利用傀儡生成包装对象  
  28.         m_p16BitPCMAudioStream = new KinectAudioStreamWrapper(pStream);  
  29.         SafeRelease(pStream);  
  30.     }  
  31.     SafeRelease(pAudioBeamList);  
  32.     SafeRelease(pAudioSource);  
  33.     return hr;  
  34. }  
剩下就是初始化语音识别引擎,因为非常定式化,建议大家直接复制即可:

[cpp]  view plain  copy
  1. // 初始化语音识别  
  2. HRESULT ThisApp::init_speech_recognizer(){  
  3.     HRESULT hr = S_OK;  
  4.     // 创建语音输入流  
  5.     if (SUCCEEDED(hr)){  
  6.         hr = CoCreateInstance(CLSID_SpStream, nullptr, CLSCTX_INPROC_SERVER, __uuidof(ISpStream), (void**)&m_pSpeechStream);;  
  7.     }  
  8.     // 与我们的Kinect语音输入相连接  
  9.     if (SUCCEEDED(hr)){  
  10.         WAVEFORMATEX wft = {  
  11.             WAVE_FORMAT_PCM, // PCM编码  
  12.             1, // 单声道  
  13.             16000,  // 采样率为16KHz  
  14.             32000, // 每分钟数据流 = 采样率 * 对齐  
  15.             2, // 对齐 : 单声道 * 样本深度 = 2byte  
  16.             16, // 样本深度 16BIT  
  17.             0 // 额外数据  
  18.         };  
  19.         // 设置状态  
  20.         hr = m_pSpeechStream->SetBaseStream(m_p16BitPCMAudioStream, SPDFID_WaveFormatEx, &wft);  
  21.     }  
  22.     // 创建语音识别对象  
  23.     if (SUCCEEDED(hr)){  
  24.         ISpObjectToken *pEngineToken = nullptr;  
  25.         // 创建语言识别器  
  26.         hr = CoCreateInstance(CLSID_SpInprocRecognizer, nullptr, CLSCTX_INPROC_SERVER, __uuidof(ISpRecognizer), (void**)&m_pSpeechRecognizer);  
  27.         if (SUCCEEDED(hr)) {  
  28.             // 连接我们创建的语音输入流对象  
  29.             m_pSpeechRecognizer->SetInput(m_pSpeechStream, TRUE);  
  30.             // 创建待识别语言 这里选择大陆汉语(zh-cn)   
  31.             // 目前没有Kinect的汉语语音识别包 有的话可以设置"language=804;Kinect=Ture"  
  32.             hr = SpFindBestToken(SPCAT_RECOGNIZERS, L"Language=804", nullptr, &pEngineToken);  
  33.             if (SUCCEEDED(hr)) {  
  34.                 // 设置待识别语言  
  35.                 m_pSpeechRecognizer->SetRecognizer(pEngineToken);  
  36.                 // 创建语音识别上下文  
  37.                 hr = m_pSpeechRecognizer->CreateRecoContext(&m_pSpeechContext);  
  38.                 // 适应性 ON! 防止因长时间的处理而导致识别能力的退化  
  39.                 if (SUCCEEDED(hr))  {  
  40.                     hr = m_pSpeechRecognizer->SetPropertyNum(L"AdaptationOn", 0);  
  41.                 }  
  42.             }  
  43.         }  
  44.         SafeRelease(pEngineToken);  
  45.     }  
  46.     // 创建语法  
  47.     if (SUCCEEDED(hr)){  
  48.         hr = m_pSpeechContext->CreateGrammar(1, &m_pSpeechGrammar);  
  49.     }  
  50.     // 加载静态SRGS语法文件  
  51.     if (SUCCEEDED(hr)){  
  52.         hr = m_pSpeechGrammar->LoadCmdFromFile(s_GrammarFileName, SPLO_STATIC);  
  53.     }  
  54.     // 激活语法规则  
  55.     if (SUCCEEDED(hr)){  
  56.         hr = m_pSpeechGrammar->SetRuleState(nullptr, nullptr, SPRS_ACTIVE);  
  57.     }  
  58.     // 设置识别器一直读取数据  
  59.     if (SUCCEEDED(hr)){  
  60.         hr = m_pSpeechRecognizer->SetRecoState(SPRST_ACTIVE_ALWAYS);  
  61.     }  
  62.     // 设置对识别事件感兴趣  
  63.     if (SUCCEEDED(hr)){  
  64.         hr = m_pSpeechContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));  
  65.     }  
  66.     // 保证语音识别处于激活状态  
  67.     if (SUCCEEDED(hr)){  
  68.         hr = m_pSpeechContext->Resume(0);  
  69.     }  
  70.     // 获取识别事件  
  71.     if (SUCCEEDED(hr)){  
  72.         m_p16BitPCMAudioStream->SetSpeechState(TRUE);  
  73.         m_hSpeechEvent = m_pSpeechContext->GetNotifyEventHandle();  
  74.     }  
  75. #ifdef _DEBUG  
  76.     else  
  77.         printf_s("init_speech_recognizer failed\n");  
  78. #endif  
  79.     return hr;  
  80. }  

需注意几点:

1.SetBaseStream时填写WAVEFORMATEX时,PCM数据格式的填写。

2.SpFindBestToken初始化时选择区域,是16进制的,大陆汉语是0x0804。如果Kinect提供的附加的语音包,可以追加

设置Kinect=Ture,使用分号分开。

3.LoadCmdFromFile设置加载SRGS的语法文件,还能设置是否动态。

4.GetNotifyEventHandle用来获取语音识别事件句柄,这个句柄不由程序员关闭,请勿主动CloseHandle


等待SR事件触发时

[cpp]  view plain  copy
  1. // 音频处理  
  2. void ThisApp::speech_process() {  
  3.     // 置信阈值  
  4.     const float ConfidenceThreshold = 0.3f;  
  5.   
  6.     SPEVENT curEvent = { SPEI_UNDEFINED, SPET_LPARAM_IS_UNDEFINED, 0, 0, 0, 0 };  
  7.     ULONG fetched = 0;  
  8.     HRESULT hr = S_OK;  
  9.     // 获取事件  
  10.     m_pSpeechContext->GetEvents(1, &curEvent, &fetched);  
  11.     while (fetched > 0)  
  12.     {  
  13.         // 确定是识别事件  
  14.         switch (curEvent.eEventId)  
  15.         {  
  16.         case SPEI_RECOGNITION:  
  17.             // 保证位对象  
  18.             if (SPET_LPARAM_IS_OBJECT == curEvent.elParamType) {  
  19.                 ISpRecoResult* result = reinterpret_cast<ISpRecoResult*>(curEvent.lParam);  
  20.                 SPPHRASE* pPhrase = nullptr;  
  21.                 // 获取识别短语  
  22.                 hr = result->GetPhrase(&pPhrase);  
  23.                 if (SUCCEEDED(hr)) {  
[cpp]  view plain  copy
  1. // XXXXXXXXXXXXXXXXXXX  
[cpp]  view plain  copy
  1.                     ::CoTaskMemFree(pPhrase);  
  2.                 }  
  3.             }  
  4.             break;  
  5.         }  
  6.   
  7.         m_pSpeechContext->GetEvents(1, &curEvent, &fetched);  
  8.     }  
  9.   
  10.     return;  
  11. }  
依然是定式


获取短语成功后可以使用

[cpp]  view plain  copy
  1. WCHAR* pwszFirstWord;  
  2. result->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &pwszFirstWord, nullptr);  
  3. // XXX  
  4. ::CoTaskMemFree(pwszFirstWord);  
获取识别后完整的字符串
SPPHRASE结构体可谓相当复杂,重要的有
SPPHRASEPROPERTY* 指针,是个树指针,每个节点都有SREngineConfidence表示置信度

父节点的置信度表示这个支总体大致的置信度,子节点表示本节短语的置信度。一般使用父节点的置信度即可。

为了表示一个SR事件识别是否准确,可以设置一个置信阈值,大于该阈值才认为识别准确。可以是经验值,

例如0.3,0.4等,还可以是动态的,由环境指定或者干脆由玩家制定。


在这,我们假设说一句“我们击毁了敌方厕所”,根据上节提供的SRGS。

pPhrase->pProperties大致是:


"_value"

      |------>"战况"

                    |----------->"主语"

                                        |

                                     "谓语"

                                        |

                                     "对象"

                                        |

                                     "宾语"


每个节点的pszValue成员能获取字符数据,比如我们设置的out = 0;则这个就是"0",

vValue获取识别后的数据,比如vValue.lVal获取long类型的数据,可以自行查看,毕竟全是数据。


具体怎么实现那真是太随意,各位可以自行发挥。这里就给个半成品吧:

[cpp]  view plain  copy
  1. // 音频处理  
  2. void ThisApp::speech_process() {  
  3.     // 置信阈值  
  4.     const float ConfidenceThreshold = 0.3f;  
  5.   
  6.     SPEVENT curEvent = { SPEI_UNDEFINED, SPET_LPARAM_IS_UNDEFINED, 0, 0, 0, 0 };  
  7.     ULONG fetched = 0;  
  8.     HRESULT hr = S_OK;  
  9.     // 获取事件  
  10.     m_pSpeechContext->GetEvents(1, &curEvent, &fetched);  
  11.     while (fetched > 0)  
  12.     {  
  13.         // 确定是识别事件  
  14.         switch (curEvent.eEventId)  
  15.         {  
  16.         case SPEI_RECOGNITION:  
  17.             // 保证位对象  
  18.             if (SPET_LPARAM_IS_OBJECT == curEvent.elParamType) {  
  19.                 ISpRecoResult* result = reinterpret_cast<ISpRecoResult*>(curEvent.lParam);  
  20.                 SPPHRASE* pPhrase = nullptr;  
  21.                 // 获取识别短语  
  22.                 hr = result->GetPhrase(&pPhrase);  
  23.                 if (SUCCEEDED(hr)) {  
  24. #ifdef _DEBUG  
  25.                     // DEBUG时显示识别字符串  
  26.                     WCHAR* pwszFirstWord;  
  27.                     result->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &pwszFirstWord, nullptr);  
  28.                     _cwprintf(pwszFirstWord);  
  29.                     ::CoTaskMemFree(pwszFirstWord);  
  30. #endif  
  31.                     pPhrase->pProperties;  
  32.                     const SPPHRASEELEMENT* pointer = pPhrase->pElements + 1;  
  33.                     if ((pPhrase->pProperties != nullptr) && (pPhrase->pProperties->pFirstChild != nullptr)) {  
  34.                         const SPPHRASEPROPERTY* pSemanticTag = pPhrase->pProperties->pFirstChild;  
  35. #ifdef _DEBUG  
  36.                         _cwprintf(L"   置信度:%d%%\n", (int)(pSemanticTag->SREngineConfidence*100.f));  
  37. #endif  
  38.                         if (pSemanticTag->SREngineConfidence > ConfidenceThreshold) {  
  39.                             speech_behavior(pSemanticTag);  
  40.                         }  
  41.                     }  
  42.                     ::CoTaskMemFree(pPhrase);  
  43.                 }  
  44.             }  
  45.             break;  
  46.         }  
  47.   
  48.         m_pSpeechContext->GetEvents(1, &curEvent, &fetched);  
  49.     }  
  50.   
  51.     return;  
  52. }  
  53.   
  54.   
  55. // 语音行为  
  56. void ThisApp::speech_behavior(const SPPHRASEPROPERTY* tag){  
  57.     if (!tag) return;  
  58.     if (!wcscmp(tag->pszName, L"战况")){  
  59.         enum class Subject{  
  60.             US = 0,  
  61.             Enemy  
  62.         } ;  
  63.         enum class Predicate{  
  64.             Destroy = 0,  
  65.             Defeat,  
  66.             Breakdown  
  67.         };  
  68.         // 分析战况  
  69.         union  Situation{  
  70.             struct{  
  71.                 // 主语  
  72.                 Subject subject;  
  73.                 // 谓语  
  74.                 Predicate predicate;  
  75.                 // 对象  
  76.                 int object2;  
  77.                 // 宾语  
  78.                 int object;  
  79.   
  80.             };  
  81.             UINT32 data[4];  
  82.         };  
  83.         Situation situation;  
  84.         auto obj = tag->pFirstChild;  
  85.         auto pointer = situation.data;  
  86.         // 填写数据  
  87.         while (obj) {  
  88.             *pointer = obj->vValue.lVal;  
  89.             ++pointer;  
  90.             obj = obj->pNextSibling;  
  91.         }  
  92.         // XXX  
  93.     }  
  94.     else if (!wcscmp(tag->pszName, L"发现东西")){  
  95.         // 发现东西  
  96.     }  
  97. }  

好啦,语音识别就到此为止了,范例如下:



依然是控制台程序,请不要点击X退出程序,而是按任意键退出。

下载地址:点击这里


关于面部识别与可视化手势:

查看SDK时相信大家也看到了,在SDK中还有

Kinect.Face.h

Kinect.VisualGestureBuilder.h

其中“Kinect.VisualGestureBuilder.h”相对应的lib与dll只有x64版本,不知道是微软偷懒还是因为某个特性只能x64能使用,

不过希望是偷懒吧,毕竟几乎没开发过64位程序。8字节指针感觉太浪费了。


但是这两个:没有发现C++部分的官方文档与例子,估计目前是给C#用的。目前使用了一下,某些函数/方法均是返回

路径未找到

比如手势识别的

IVisualGestureBuilderFrameSource::AddGestures

面部识别的

CreateHighDefinitionFaceFrameSource


只能坐等SDK更新或者高手解答了,那么《Kinect for Windows SDK v2.0 开发笔记》在这里暂时结束

,感谢大家的支持,SDK更新时再见

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