当前位置:首页 > 嵌入式 > 嵌入式硬件

1. 使用定向麦克风进行波速追踪(Beam Tracking for a Directional Microphone)

可以使用这4个麦克风来模拟定向麦克风产生的效果,这个过程称之为波束追踪(beam tracking),为此我们新建一个WPF项目,过程如下:

1. 创建一个名为KinectFindAudioDirection的WPF项目。

2. 添加对Microsoft.Kinect.dll和Microsoft.Speech.dll的引用。

3. 将主窗体的名称改为“Find Audio Direction”

4. 在主窗体中绘制一个垂直的细长矩形。

界面上的细长矩形用来指示某一时刻探测到的说话者的语音方向。矩形有一个旋转变换,在垂直轴上左右摆动,以表示声音的不同来源方向。前端页面代码:

<Rectangle Fill="#1BA78B" HorizontalAlignment="Left" Margin="240,41,0,39" Stroke="Black" Width="10" RenderTransformOrigin="0.5,0">    <Rectangle.RenderTransform>        <TransformGroup>            <ScaleTransform/>            <SkewTransform/>            <RotateTransform Angle="{Binding BeamAngle}"/>            <TranslateTransform/>        </TransformGroup>    </Rectangle.RenderTransform></Rectangle>

pic1

上图是程序的UI界面。后台逻辑代码和之前的例子大部分都是相同的。首先实例化一个KinectAudioSource对象,然后将主窗体的DataContext赋值给本身。将BeamAngleMode设置为Adaptive,使得能够自动追踪说话者的声音。我们需要编写KinectAudioSource对象的BeamChanged事件对应的处理方法。当用户的说话时,位置发生变化时就会触发该事件。我们需要创建一个名为BeamAngle的属性,使得矩形的RotateTransform可以绑定这个属性。

public partial class MainWindow : Window, INotifyPropertyChanged{    public MainWindow()    {        InitializeComponent();        this.DataContext = this;        this.Loaded += delegate { ListenForBeamChanges(); };    }    private KinectAudioSource CreateAudioSource()    {        var source = KinectSensor.KinectSensors[0].AudioSource;        source.NoiseSuppression = true;        source.AutomaticGainControlEnabled = true;        source.BeamAngleMode = BeamAngleMode.Adaptive;        return source;    }    private void ListenForBeamChanges()    {        KinectSensor.KinectSensors[0].Start();        var audioSource = CreateAudioSource();        audioSource.BeamAngleChanged += audioSource_BeamAngleChanged;        audioSource.Start();    }    public event PropertyChangedEventHandler PropertyChanged;    private void OnPropertyChanged(string propName)    {        if (PropertyChanged != null)            PropertyChanged(this, new PropertyChangedEventArgs(propName));    }    private double _beamAngle;    public double BeamAngle    {        get { return _beamAngle; }        set        {            _beamAngle = value;            OnPropertyChanged("BeamAngle");        }    }}

以上代码中,还需要对BeamChanged事件编写对应的处理方法。每次当波束的方向发生改变时,就更改BeamAngle的属性。SDK中使用弧度表示角度。所以在事件处理方法中我们需要将弧度换成度。为了能达到说话者移到左边,矩形条也能够向左边移动的效果,我们需要将角度乘以一个-1。代码如下:

void audioSource_BeamAngleChanged(object sender, BeamAngleChangedEventArgs e){    BeamAngle = -1 * e.Angle;}

运行程序,然后在房间里不同地方走动,可以看到矩形条会根据你的位置左右摆动。

pic2w

2. 语音命令识别

在这一部分,我们将会结合KinectAudioSource和SpeechRecognitionEngine来演示语音命令识别的强大功能。为了展示语音命令能够和骨骼追踪高效结合,我们会使用语音命令向窗体上绘制图形,并使用命令移动这些图形到光标的位置。命令类似如下:

Create a yellow circle, there.

Create a cyan triangle, there.

Put a magenta square, there.

Create a blue diamond, there.

Move that ... there.

Put that ... there.

Move that ... below that.

Move that ... west of the diamond.

Put a large green circle ... there.

程序界面大致如下:

pic4w

和之前的应用程序一样,首先创建一下项目的基本结构:

1. 创建一个名为KinectPutThatThere的WPF项目。

2. 添加对Microsoft.Kinect.dll和Microsoft.Speech.dll的引用。

3. 将主窗体的名称改为“Put That There”

4. 添加一个名为CrossHairs.xaml的用户自定义控件。

CrossHair用户控件简单的以十字光标形式显示当前用户右手的位置。下面的代码显示了这个自定义控件的XAML文件。注意到对象于容器有一定的偏移使得十字光标的中心能够处于Grid的零点。

<Grid Height="50" Width="50" RenderTransformOrigin="0.5,0.5">    <Grid.RenderTransform>        <TransformGroup>            <ScaleTransform/>            <SkewTransform/>            <RotateTransform/>            <TranslateTransform X="-25" Y="-25"/>        </TransformGroup>    </Grid.RenderTransform>    <Rectangle Fill="#FFF4F4F5" Margin="22,0,20,0" Stroke="#FFF4F4F5"/>    <Rectangle Fill="#FFF4F4F5" Margin="0,22,0,21" Stroke="#FFF4F4F5"/>  </Grid>

在应用程序的主窗体中,将根节点从grid对象改为canvas对象。Canvas对象使得将十字光标使用动画滑动到手的位置比较容易。在主窗体上添加一个CrossHairs自定义控件。在下面的代码中,我们可以看到将Canvas对象嵌套在了一个Viewbox控件中。这是一个比较老的处理不同屏幕分辨率的技巧。ViewBox控件会自动的将内容进行缩放以适应实际屏幕的大小。设置MainWindows的背景色,并将Canvas的颜色设置为黑色。然后在Canvas的底部添加两个标签。一个标签用来显示SpeechRecognitionEngine将要处理的语音指令,另一个标签显示匹配正确的置信度。CrossHair自定义控件绑定了HandTop和HandLeft属性。两个标签分别绑定了HypothesizedText和Confidence属性。代码如下:

<Window x:Class="KinectPutThatThere.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:local="clr-namespace:KinectPutThatThere"        Title="Put That There"  Background="Black">    <Viewbox>        <Canvas x:Name="MainStage" Height="1080" Width="1920" Background="Black" VerticalAlignment="Bottom">            <local:CrossHairs Canvas.Top="{Binding HandTop}" Canvas.Left="{Binding HandLeft}" />                <Label  Foreground="White"  Content="{Binding HypothesizedText}" Height="55"   FontSize="32" Width="965"  Canvas.Left="115" Canvas.Top="1025" />            <Label Foreground="Green" Content="{Binding Confidence}" Height="55"  Width="114" FontSize="32" Canvas.Left="0" Canvas.Top="1025"  />        </Canvas>    </Viewbox></Window>

在后台逻辑代码中,让MainWindows对象实现INofityPropertyChanged事件并添加OnPropertyChanged帮助方法。我们将创建4个属性用来为前台UI界面进行绑定。

public partial class MainWindow : Window, INotifyPropertyChanged{    private double _handLeft;    public double HandLeft    {        get { return _handLeft; }        set        {            _handLeft = value;            OnPropertyChanged("HandLeft");        }    }    private double _handTop;    public double HandTop    {        get { return _handTop; }        set        {            _handTop = value;            OnPropertyChanged("HandTop");        }    }    private string _hypothesizedText;    public string HypothesizedText    {        get { return _hypothesizedText; }        set        {            _hypothesizedText = value;            OnPropertyChanged("HypothesizedText");        }    }    private string _confidence;    public string Confidence    {        get { return _confidence; }        set        {            _confidence = value;            OnPropertyChanged("Confidence");        }    }    public event PropertyChangedEventHandler PropertyChanged;    private void OnPropertyChanged(string propertyName)    {        if (PropertyChanged != null)        {            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));        }    }}

添加CreateAudioSource方法,在该方法中,将KinectAudioSource对象的AutoGainControlEnabled的属性设置为false。

private KinectAudioSource CreateAudioSource(){    var source = KinectSensor.KinectSensors[0].AudioSource;    source.AutomaticGainControlEnabled = false;    source.EchoCancellationMode = EchoCancellationMode.None;    return source;}

接下来实现骨骼追踪部分逻辑来获取右手的坐标,相信看完骨骼追踪那两篇文章后这部分的代码应该会比较熟悉。首先创建一个私有字段_kinectSensor来保存当前的KienctSensor对象,同时创建SpeechRecognitionEngine对象。在窗体的构造函数中,对这几个变量进行初始化。例外注册骨骼追踪系统的Skeleton事件并将主窗体的DataContext对象赋给自己。

KinectSensor _kinectSensor;SpeechRecognitionEngine _sre;KinectAudioSource _source;public MainWindow(){    InitializeComponent();    this.DataContext = this;    this.Unloaded += delegate    {        _kinectSensor.SkeletonStream.Disable();        _sre.RecognizeAsyncCancel();        _sre.RecognizeAsyncStop();        _sre.Dispose();    };    this.Loaded += delegate    {        _kinectSensor = KinectSensor.KinectSensors[0];        _kinectSensor.SkeletonStream.Enable(new TransformSmoothParameters()        {            Correction = 0.5f,            JitterRadius = 0.05f,            MaxDeviationRadius = 0.04f,            Smoothing = 0.5f        });        _kinectSensor.SkeletonFrameReady += nui_SkeletonFrameReady;        _kinectSensor.Start();        StartSpeechRecognition();    };}

在上面的代码中,我们添加了一些TransformSmoothParameters参数来使得骨骼追踪更加平滑。nui_SkeletonFrameReady方法如下。方式使用骨骼追踪数据来获取我们感兴趣的右手的关节点位置。这部分代码和之前文章中的类似。大致流程是:遍历当前处在追踪状态下的骨骼信息。然后找到右手关节点的矢量信息,然后使用SkeletonToDepthImage来获取相对于屏幕尺寸的X,Y坐标信息。

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e){    using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())    {        if (skeletonFrame == null)            return;        var skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];        skeletonFrame.CopySkeletonDataTo(skeletons);        foreach (Skeleton skeletonData in skeletons)        {            if (skeletonData.TrackingState == SkeletonTrackingState.Tracked)            {                Microsoft.Kinect.SkeletonPoint rightHandVec = skeletonData.Joints[JointType.HandRight].Position;                var depthPoint = _kinectSensor.MapSkeletonPointToDepth(rightHandVec                    , DepthImageFormat.Resolution640x480Fps30);                HandTop = depthPoint.Y * this.MainStage.ActualHeight / 480;                HandLeft = depthPoint.X * this.MainStage.ActualWidth / 640;            }        }    }}

上面是多有设计到手部追踪的代码。一旦我们设置好HandTop和HandLeft属性后,UI界面上的十字光标位置就会自动更新。

接下来我们需要实现语音识别部分的逻辑。SpeechRecognitionEngine中的StartSpeechRecognition方法必须找到正确的语音识别库来进行语音识别。下面的代码展示了如何设置语音识别库预计如何将KinectAudioSource传递给语音识别引起。我们还添加了SpeechRecognized,SpeechHypothesized以及SpeechRejected事件对应的方法。SetInputToAudioStream中的参数和前篇文章中的含义一样,这里不多解释了。注意到SpeechRecognitionEngine和KinectAudioSource都是Disposable类型,因此在整个应用程序的周期内,我们要保证这两个对象都处于打开状态。

private void StartSpeechRecognition(){    _source = CreateAudioSource();    Func<RecognizerInfo, bool> matchingFunc = r =>    {        string value;        r.AdditionalInfo.TryGetValue("Kinect", out value);        return "True".Equals(value, StringComparison.InvariantCultureIgnoreCase)            && "en-US".Equals(r.Culture.Name, StringComparison.InvariantCultureIgnoreCase);    };    RecognizerInfo ri = SpeechRecognitionEngine.InstalledRecognizers().Where(matchingFunc).FirstOrDefault();    _sre = new SpeechRecognitionEngine(ri.Id);    CreateGrammars(ri);    _sre.SpeechRecognized += sre_SpeechRecognized;    _sre.SpeechHypothesized += sre_SpeechHypothesized;    _sre.SpeechRecognitionRejected += sre_SpeechRecognitionRejected;    Stream s = _source.Start();    _sre.SetInputToAudioStream(s,                                new SpeechAudioFormatInfo(                                    EncodingFormat.Pcm, 16000, 16, 1,                                    32000, 2, null));    _sre.RecognizeAsync(RecognizeMode.Multiple);}

要完成程序逻辑部分,我们还需要处理语音识别时间以及语音逻辑部分,以使得引擎能够直到如何处理和执行我们的语音命令。SpeechHypothesized以及SpeechRejected事件代码如下,这两个事件的逻辑很简单,就是更新UI界面上的label。SpeechRecognized事件有点复杂,他负责处理传进去的语音指令,并对识别出的指令执行相应的操作。另外,该事件还负责创建一些GUI对象(实际就是命令模式),我们必须使用Dispatcher对象来发挥InterpretCommand到主UI线程中来。

void sre_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e){    HypothesizedText += " Rejected";    Confidence = Math.Round(e.Result.Confidence, 2).ToString();}void sre_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e){    HypothesizedText = e.Result.Text;}void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e){    Dispatcher.BeginInvoke(new Action<SpeechRecognizedEventArgs>(InterpretCommand), e);}

现在到了程序核心的地方。创建语法逻辑并对其进行解析。本例中的程序识别普通的以“put”或者“create”开头的命令。前面是什么我们不关心,紧接着应该是一个颜色,然后是一种形状,最后一个词应该是“there”。下面的代码显示了创建的语法。

private void CreateGrammars(RecognizerInfo ri){    var colors = new Choices();    colors.Add("cyan");    colors.Add("yellow");    colors.Add("magenta");    colors.Add("blue");    colors.Add("green");    colors.Add("red");    var create = new Choices();    create.Add("create");    create.Add("put");    var shapes = new Choices();    shapes.Add("circle");    shapes.Add("triangle");    shapes.Add("square");    shapes.Add("diamond");    var gb = new GrammarBuilder();    gb.Culture = ri.Culture;    gb.Append(create);    gb.AppendWildcard();    gb.Append(colors);    gb.Append(shapes);    gb.Append("there");    var g = new Grammar(gb);    _sre.LoadGrammar(g);    var q = new GrammarBuilder{ Culture = ri.Culture };    q.Append("quit application");    var quit = new Grammar(q);    _sre.LoadGrammar(quit);}

上面的代码中,我们首先创建一个Choices对象,这个对象会在命令解析中用到。在程序中我们需要颜色和形状对象。另外,第一个单词是“put”或者“create”,因此我们也创建Choices对象。然后使用GrammarBuilder类将这些对象组合到一起。首先是”put”或者“create”然后是一个占位符,因为我们不关心内容,然后是一个颜色Choices对象,然后是一个形状Choices对象,最后是一个“there”单词。

我们将这些语法规则加载进语音识别引擎。同时我们也需要有一个命令来停止语音识别引擎。因此我们创建了第二个语法对象,这个对象只有一个”Quit”命令。然后也将这个语法规则加载到引擎中。

一旦识别引擎确定了要识别的语法,真正的识别工作就开始了。被识别的句子必须被解译,出别出来想要的指令后,我们必须决定如何进行下一步处理。下面的代码展示了如何处理识别出的命令,以及如何根据特定的指令来讲图形元素绘制到UI界面上去。

private void InterpretCommand(SpeechRecognizedEventArgs e){    var result = e.Result;    Confidence = Math.Round(result.Confidence, 2).ToString();    if (result.Confidence < 95 && result.Words[0].Text == "quit" && result.Words[1].Text == "application")    {        this.Close();    }    if (result.Words[0].Text == "put" || result.Words[0].Text == "create")    {        var colorString = result.Words[2].Text;        Color color;        switch (colorString)        {            case "cyan": color = Colors.Cyan;                break;            case "yellow": color = Colors.Yellow;                break;            case "magenta": color = Colors.Magenta;                break;            case "blue": color = Colors.Blue;                break;            case "green": color = Colors.Green;                break;            case "red": color = Colors.Red;                break;            default:                return;        }        var shapeString = result.Words[3].Text;        Shape shape;        switch (shapeString)        {            case "circle":                shape = new Ellipse();                shape.Width = 150;                shape.Height = 150;                break;            case "square":                shape = new Rectangle();                shape.Width = 150;                shape.Height = 150;                break;            case "triangle":                var poly = new Polygon();                poly.Points.Add(new Point(0, 0));                poly.Points.Add(new Point(150, 0));                poly.Points.Add(new Point(75, -150));                shape = poly;                break;            case "diamond":                var poly2 = new Polygon();                poly2.Points.Add(new Point(0, 0));                poly2.Points.Add(new Point(75, 150));                poly2.Points.Add(new Point(150, 0));                poly2.Points.Add(new Point(75, -150));                shape = poly2;                break;            default:                return;        }        shape.SetValue(Canvas.LeftProperty, HandLeft);        shape.SetValue(Canvas.TopProperty, HandTop);        shape.Fill = new SolidColorBrush(color);        MainStage.Children.Add(shape);    }}

方法中,我们首先检查语句识别出的单词是否是”Quit”如果是的,紧接着判断第二个单词是不是”application”如果两个条件都满足了,就不进行绘制图形,直接返回。如果有一个条件不满足,就继续执行下一步。

InterpretCommand方法然后判断第一个单词是否是“create”或者“put”,如果不是这两个单词开头就什么也不执行。如果是的,就判断第三个单词,并根据识别出来的颜色创建对象。如果第三个单词没有正确识别,应用程序也停止处理。否则,程序判断第四个单词,根据接收到的命令创建对应的形状。到这一步,基本的逻辑已经完成,最后第五个单词用来确定整个命令是否正确。命令处理完了之后,将当前受的X,Y坐标赋给创建好的对象的位置。

运行程序后,按照语法设定的规则就可以通过语音以及骨骼追踪协作来创建对象了。

pic3w

有一点遗憾的是,Kinect的语音识别目前不支持中文,即使在5月份发布的SDK1.5版本增加的识别语言种类中也没有中文,也有可能是因为目前Xbox和kinect没有在中国大陆销售的原因吧。所以大家只有用英语进行测试了。

3. 结语

本文接上文,用一个显示语音来源方向的例子展示了语音识别中的方向识别。用一个简单的语音识别结合骨骼追踪来演示了如何根据语音命令来执行一系列操作。在语音命令识别中详细介绍了语音识别语法对象逻辑的建立,以及语音识别引擎相关事件的处理。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭