前言

之前写过一篇基于ML.NET的手部关键点分类的博客,可以根据图片进行手部的提取分类,于是我就将手势分类和摄像头数据结合,集成到了我开发的电子脑壳软件里。

电子脑壳是一个为稚晖君开源的桌面机器人ElectronBot提供一些软件功能的桌面程序项目。它是由绿荫阿广也就是我开发的,使用了微软的WASDK框架。

电子脑壳算是本人学习WinUI开发的练习项目了,通过根据一些开源的项目的学习,将一些功能进行整合,比如手势识别触发语音转文本,然后接入ChatGPT结合文本转语音的方式,实现机器人的对话。

此博客算是实战记录了,替大家先踩坑。

下图链接为机器人的演示视频,通过对话,让ChatGPT给我讲了一个骆驼祥子的故事,只不过这个故事有点离谱,本来前部分还正常,后面就开始瞎编了,比如祥子有了一头驴,最后还成为了商人。

大家观看觉得不错的话给点个赞。

具体的实现方案

1. 方案思路叙述

整体的流程如下图,图画的不一定标准,但是大体如图所示:

  • 处理摄像头帧事件,通过将摄像头的帧数据处理进行手势的匹配。
  • 手势识别结果处理方法调用语音转文本逻辑。
  • 转的文本通过调用ChatGPT API实现智能回复。
  • 将回复结果文本通过TTS播放到机器人上的扬声器,完成一次对话。

2. 所用技术说明

代码讲解

1. 项目介绍

电子脑壳项目本身是一个标准的MVVM的WinUI项目,使用微软的轻量级DI容器管理对象的生命周期,MVVM使用的是社区工具包提供的框架,支持代码生成,简化VM的代码。

2. 核心代码讲解

  • 实时视频流解析手势,通过命名空间Windows.Media.Capture下的MediaCapture类和Windows.Media.Capture.Frames命名空间下的MediaFrameReader类,创建对象并注册帧处理事件,在帧处理事件中处理视频画面并传出到手势识别服务里进行手势识别,主要代码如下。

    //帧处理结果订阅
    private void Current_SoftwareBitmapFrameCaptured(object? sender, SoftwareBitmapEventArgs e)
    {
    if (e.SoftwareBitmap is not null)
    { if (e.SoftwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
    e.SoftwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
    {
    e.SoftwareBitmap = SoftwareBitmap.Convert(
    e.SoftwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
    }
    //手势识别服务获取
    var service = App.GetService<GestureClassificationService>();
    //调用手势分析代码
    _ = service.HandPredictResultUnUseQueueAsync(calculator, modelPath, e.SoftwareBitmap);
    }
    }

    涉及到的代码如下:

    MainViewModel

    CameraFrameService

  • 语音转文本的实现,WinUI(WASDK)继承了UWP的现代化的UI,也可以很好的使用WinRT的API进行操作。主要涉及的对象为命名空间Windows.Media.SpeechRecognition下的SpeechRecognizer对象。

    官网文档地址语音交互 定义自定义识别约束

    以下是语音转文本的部分代码 详细代码点击文字

    //创建识别为网络搜索
    var webSearchGrammar = new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario.WebSearch, "webSearch", "sound");
    //webSearchGrammar.Probability = SpeechRecognitionConstraintProbability.Min;
    speechRecognizer.Constraints.Add(webSearchGrammar);
    SpeechRecognitionCompilationResult result = await speechRecognizer.CompileConstraintsAsync(); if (result.Status != SpeechRecognitionResultStatus.Success)
    {
    // Disable the recognition buttons.
    }
    else
    {
    // Handle continuous recognition events. Completed fires when various error states occur. ResultGenerated fires when
    // some recognized phrases occur, or the garbage rule is hit.
    //注册指定的事件
    speechRecognizer.ContinuousRecognitionSession.Completed += ContinuousRecognitionSession_Completed;
    speechRecognizer.ContinuousRecognitionSession.ResultGenerated += ContinuousRecognitionSession_ResultGenerated;
    }
  • 语音转文本之后调用ChatGPT API进行对话回复获取,使用ChatGPTSharp封装库实现。

    代码如下:

    private async void ContinuousRecognitionSession_ResultGenerated(SpeechContinuousRecognitionSession sender, SpeechContinuousRecognitionResultGeneratedEventArgs args)
    {
    // The garbage rule will not have a tag associated with it, the other rules will return a string matching the tag provided
    // when generating the grammar.
    var tag = "unknown"; if (args.Result.Constraint != null && isListening)
    {
    tag = args.Result.Constraint.Tag; App.MainWindow.DispatcherQueue.TryEnqueue(() =>
    {
    ToastHelper.SendToast(tag, TimeSpan.FromSeconds(3));
    }); Debug.WriteLine($"识别内容---{tag}");
    } // Developers may decide to use per-phrase confidence levels in order to tune the behavior of their
    // grammar based on testing.
    if (args.Result.Confidence == SpeechRecognitionConfidence.Medium ||
    args.Result.Confidence == SpeechRecognitionConfidence.High)
    {
    var result = string.Format("Heard: '{0}', (Tag: '{1}', Confidence: {2})", args.Result.Text, tag, args.Result.Confidence.ToString()); App.MainWindow.DispatcherQueue.TryEnqueue(() =>
    {
    ToastHelper.SendToast(result, TimeSpan.FromSeconds(3));
    }); if (args.Result.Text.ToUpper() == "打开B站")
    {
    await Launcher.LaunchUriAsync(new Uri(@"https://www.bilibili.com/"));
    }
    else if (args.Result.Text.ToUpper() == "撒个娇")
    {
    ElectronBotHelper.Instance.ToPlayEmojisRandom();
    }
    else
    {
    try
    {
    // 根据机器人客户端工厂创建指定类型的处理程序 可以支持多种聊天API
    var chatBotClientFactory = App.GetService<IChatbotClientFactory>(); var chatBotClientName = (await App.GetService<ILocalSettingsService>()
    .ReadSettingAsync<ComboxItemModel>(Constants.DefaultChatBotNameKey))?.DataKey; if (string.IsNullOrEmpty(chatBotClientName))
    {
    throw new Exception("未配置语音提供程序机密数据");
    } var chatBotClient = chatBotClientFactory.CreateChatbotClient(chatBotClientName);
    //调用指定的实现获取聊天返回结果
    var resultText = await chatBotClient.AskQuestionResultAsync(args.Result.Text); //isListening = false;
    await ReleaseRecognizerAsync();
    //调用文本转语音并进行播放方法
    await ElectronBotHelper.Instance.MediaPlayerPlaySoundByTTSAsync(resultText, false);
    }
    catch (Exception ex)
    {
    App.MainWindow.DispatcherQueue.TryEnqueue(() =>
    {
    ToastHelper.SendToast(ex.Message, TimeSpan.FromSeconds(3));
    }); }
    }
    }
    else
    {
    }
    }
  • 结果文本转语音并进行播放,通过Windows.Media.SpeechSynthesis命名空间下的SpeechSynthesizer类,使用下面的代码可以将文本转化成Stream。

      using SpeechSynthesizer synthesizer = new();
    // Create a stream from the text. This will be played using a media element. //将文本转化为Stream
    var synthesisStream = await synthesizer.SynthesizeTextToStreamAsync(text);

    然后使用MediaPlayer对象进行语音的播报。


    /// <summary>
    /// 播放声音
    /// </summary>
    /// <param name="content"></param>
    /// <returns></returns>
    public async Task MediaPlayerPlaySoundByTTSAsync(string content, bool isOpenMediaEnded = true)
    {
    _isOpenMediaEnded = isOpenMediaEnded;
    if (!string.IsNullOrWhiteSpace(content))
    {
    try
    {
    var localSettingsService = App.GetService<ILocalSettingsService>(); var audioModel = await localSettingsService
    .ReadSettingAsync<ComboxItemModel>(Constants.DefaultAudioNameKey); var audioDevs = await EbHelper.FindAudioDeviceListAsync(); if (audioModel != null)
    {
    var audioSelect = audioDevs.FirstOrDefault(c => c.DataValue == audioModel.DataValue) ?? new ComboxItemModel(); var selectedDevice = (DeviceInformation)audioSelect.Tag!; if (selectedDevice != null)
    {
    mediaPlayer.AudioDevice = selectedDevice;
    }
    }
    //获取TTS服务实例
    var speechAndTTSService = App.GetService<ISpeechAndTTSService>();
    //转化文本到Stream
    var stream = await speechAndTTSService.TextToSpeechAsync(content);
    //播放stream
    mediaPlayer.SetStreamSource(stream);
    mediaPlayer.Play();
    isTTS = true;
    }
    catch (Exception)
    {
    }
    }
    }

    至此一次完整的识别对话流程就结束了,软件的界面如下图,感兴趣的同学可以点击图片查看项目源码地址查看其他的功能:

个人感悟

个人觉得DotNET的生态还是差了些,尤其是ML.NET的轮子还是太少了,毕竟参与的人少,而且知识迁移也需要成本,熟悉其他机器学习框架的人可能不懂DotNET。

所以作为社区的一员,我觉得我们需要走出去,然后再回来,走出去就是先学习其他的机器学习框架,然后回来用DotNET进行应用,这样轮子多了,社区就会越来越繁荣。

我也能多多的复制粘贴大家的代码了。

参考推荐文档项目如下:

WinUI(WASDK)使用ChatGPT和摄像头手势识别结合TTS让机器人更智能的更多相关文章

  1. WinUI(WASDK)使用MediaPipe检查手部关键点并通过ML.NET进行手势分类

    前言 之所以会搞这个手势识别分类,其实是为了满足之前群友提的需求,就是针对稚晖君的ElectronBot机器人的上位机软件的功能丰富,因为本来擅长的技术栈都是.NET,也刚好试试全能的.NET是不是真 ...

  2. SLAM+语音机器人DIY系列:(三)感知与大脑——4.音响麦克风与摄像头

    摘要 在我的想象中机器人首先应该能自由的走来走去,然后应该能流利的与主人对话.朝着这个理想,我准备设计一个能自由行走,并且可以与人语音对话的机器人.实现的关键是让机器人能通过传感器感知周围环境,并通过 ...

  3. WinUI 3试玩报告

    1. 什么是 WinUI 3 在微软 Build 2020 开发者大会上,WinUI 团队宣布可公开预览的 WinUI 3 Preview 1,它让开发人员可以在 Win32 中使用 WinUI.Wi ...

  4. Win10 禁用摄像头的方法及注意事项

    Win10 禁用摄像头的方法及注意事项 windows教程 2020-03-04  223 最新的Windows10系统中应该如何禁用摄像头呢?下面MS酋长与大家分享一下.当然,如果你说用个便利贴把摄 ...

  5. JavaCV的摄像头实战之一:基础

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<JavaCV的摄像头实战>系列 &l ...

  6. ChatGPT 会开源吗?

    最近,我被一款叫做 ChatGPT 的人工智能(AI)聊天机器人,刷屏了.网上有人说它是搜索引擎杀手,还有人说它将取代程序员... 最后,我还是没扛住铺天盖地的赞美,跑去注册了个账号,抱着调侃&quo ...

  7. ChatGPT/InstructGPT详解

    作者:京东零售 刘岩 前言 GPT系列是OpenAI的一系列预训练文章,GPT的全称是Generative Pre-Trained Transformer,顾名思义,GPT的目的就是通过Transfo ...

  8. OpenCV中Kinect的使用(1)

    图像处理中一般为了更好的获取外部信息都会使用到Kinect,其优势在于除了传统的RGB摄像头之外,还拥有一个获取深度信息的3D深度感应器,因此可以获得外界物体的3维信息实现物体的跟踪.手势识别等各项功 ...

  9. Unity3d入门 - 关于unity工具的熟悉

    上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...

  10. 安卓APP与智能硬件相结合的简易方案

    第1章 概  述 (作者小波QQ463431476) (来源http://blog.chinaaet.com/zhaocundang/p/5100017645博客) (来源   http://www. ...

随机推荐

  1. mycat分片的十四种算法

    MyCat的分片规则配置在 conf目录下的 rule.xml文件中定义 ; 环境准备 : schema.xml中的内容做好备份 , 并配置 逻辑库; <schema name="PA ...

  2. HCIA-ICT实战基础09-远程接入安全管理

    HCIA-ICT实战基础-远程接入安全管理 目录 AAA概述 AAA配置实现 telnet原理与配置 Stelnet(华为ssh的另一种称呼)配置 1 AAA概述 1.1 基本概念 AAA是Authe ...

  3. React整洁的代码的一些原则

    1. Model is everything models are  the heart of your app. If you have models separated from th rest ...

  4. git常用命令查询手册

    默认已经连接到远程仓库的情况下 本地文件夹初始化成git仓库.提交本地仓库并添加注释 git init git add 文件1(文件夹1) 文件2(文件夹2)... git commit -m &qu ...

  5. vue接口

    前端的接口与后端进行对接,根据后台的接口字段与前端的字段对应 这是前端的定义方法,下面是一个方法定义的默认值下标 接下来就是提交的方法里面进行对接,再将ruleForm重新定义,然后进入接口进行存储 ...

  6. PHP Redis - String (字符串)

    string 是 Redis 最基本的类型,与Memcached类似,一个 key 对应一个 value string 类型是二进制 安全的.这意味着 Redis 的 string 可以包含任何数据. ...

  7. vivado程序示例

    //full_add.v 全加器 module full_add( input a, input b, input carry, output sum, output count ); assign ...

  8. mysql查询和更新不能同时出现

    mysql出现You can't specify target table for update in FROM clause 这个错误的意思是不能在同一个sql语句中,先select同一个表的某些值 ...

  9. 高并发解决方案之 redis 分布式锁

    背景:秒杀服务中要写一个定时任务:活动到期时给order微服务发送关闭订单的通知.这需要改变数据库表中的数据,而集群中服务是多节点的方式进行部署,会出现并发执行的情况,所以采用的redis的分布式锁的 ...

  10. 工控小工具 snmp 、opc ua 、modbus 、tcp、bacnet 开发环境Net6.0

    下载地址 https://files.cnblogs.com/files/blogs/745639/net6.0-windows.rar?t=1674114312