- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
之前写过一篇基于ML.NET的手部关键点分类的博客,可以根据图片进行手部的提取分类,于是我就将手势分类和摄像头数据结合,集成到了我开发的 电子脑壳 软件里.
电子脑壳 是一个为稚晖君开源的桌面机器人 ElectronBot 提供一些软件功能的桌面程序项目。它是由 绿荫阿广 也就是我开发的,使用了微软的WASDK框架.
电子脑壳 算是本人学习WinUI开发的练习项目了,通过根据一些开源的项目的学习,将一些功能进行整合,比如手势识别触发语音转文本,然后接入ChatGPT结合文本转语音的方式,实现机器人的对话.
此博客算是实战记录了,替大家先踩坑.
下图链接为机器人的演示视频,通过对话,让ChatGPT给我讲了一个骆驼祥子的故事,只不过这个故事有点离谱,本来前部分还正常,后面就开始瞎编了,比如祥子有了一头驴,最后还成为了商人.
大家观看觉得不错的话给点个赞.
整体的流程如下图,图画的不一定标准,但是大体如图所示:
WASDK 。
MediaPipe offers open source cross-platform, customizable ML solutions for live and streaming media. 。
ML.NET 开放源代码的跨平台机器学习框架 。
上面的技术栈在我上面文章里有讲述,这里就不展开了,大家有兴趣的可以点击之前的文章查看.
WinUI(WASDK)使用MediaPipe检查手部关键点并通过ML.NET进行手势分类 。
电子脑壳 项目本身是一个标准的MVVM的WinUI项目,使用微软的轻量级DI容器管理对象的生命周期,MVVM使用的是社区工具包提供的框架,支持代码生成,简化VM的代码.
实时视频流解析手势,通过命名空间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进行应用,这样轮子多了,社区就会越来越繁荣.
我也能多多的复制粘贴大家的代码了.
电子脑壳有在使用的得意黑字体 。
项目模板——TemplateStudio 。
表盘参考项目——一个番茄钟 。
社区工具集——CommunityToolkit 。
控件库展示demo——WinUI-Gallery 。
图像处理库——opencvsharp 。
Emoji8 表情识别例子 。
ChatGPTSharp 。
WASDK文档地址 。
MediaPipe 。
MediaPipe.NET 。
ML.NET 。
hand-gesture-recognition-using-mediapipe 。
Control DJI Tello drone with Hand gestures 。
最后此篇关于WinUI(WASDK)使用ChatGPT和摄像头手势识别结合TTS让机器人更智能的文章就讲到这里了,如果你想了解更多关于WinUI(WASDK)使用ChatGPT和摄像头手势识别结合TTS让机器人更智能的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一张 Excel 表格,用于更新玩家评分。 播放器 配售 初始化 1 2 3 4 金融评级 一个 1 2.0 1.000 0.018 0.016 0.014 2.007 D 2 -2.0 54.5
我有一个 map = std::map ,其中 myItemModel继承QAbstractItemModel . 我现在要合并所有 myItemModel合一myItemModel (其他所有元素模
我大量使用“do.call”来生成函数调用。例如: myfun <- "rnorm"; myargs <- list(n=10, mean=5); do.call(myfun, myargs); 但是
想象一下 InputStream 的以下变体: trait FutureInputStream { //read bytes asynchronously. Empty array means E
这是我的 C 代码: #include void sum(); int newAlphabet; int main(void) { sum();
我只是想选择类“.last”之后的每个元素。 HTML: 1 2 Jquery
我正在为一个项目构建一个 XML 反序列化器,我经常遇到这种类型的代码情况: var myVariable = ParseNDecimal(xml.Element("myElement")) == n
这是来自 Selecting the highest salary 的继续问题 假设有一个表 'wagetable' name lowhours highhours wage pri
我正在为我的程序创建一个战舰程序;该程序运行良好,但我试图确保当用户将坐标超出范围时,程序会说他们输入的坐标不正确。这是代码: #include #include void
我有一个函数,它为每种情况返回不同的 DWORD 值,如果出现错误。所以我有以下定义: #define ERR_NO_DB_CONNECTION 0x90000 #define ERR_DB_N
在派生类中引发基类事件以下简单示例演示了在基类中声明可从派生类引发的事件的标准方法。此模式广泛应用于 .NET Framework 类库中的 Windows 窗体类。在创建可用作其他类的基类的类时,应
我只是想知道这是否可能: use Modern::Perl; my @list = ('a' .. 'j'); map { func($_) } each(@list); sub func { m
我一直在使用 =IF(L2="","Active",IF(K2I2,"Late"))) 有效,但现在我需要检查 F 上的多个条件 专栏 我试过了 OR 函数 =IF(OR(F2="Scheduled"
我有 2 个命令,如下所示。 在视频中添加介绍图片 ffmpeg -y -loop 1 -framerate 10 -t 3 -i intro.png -i video.mp4 -filter_com
好的,我有这个公式可以根据名字和姓氏列表生成用户名。现在,虽然这可行,但我希望单元格改为引用我自己的 VBA 函数。但是,由于代码少得多,我仍然想使用原始公式。 我有这个公式: =SUBSTITUTE
我有两个 HAProxy 实例。两个实例都启用了统计信息并且工作正常。 我正在尝试将两个实例的统计信息合并为一个,以便我可以使用单个 HAProxy 来查看前端/后端统计信息。我试图让两个 hapro
我有一个 Observable,其中每个新值都应该引起一个 HTTP 请求。在客户端,我只关心最新的响应值;但是,我希望每个请求都能完成以进行监控/等。目的。 我目前拥有的是这样的: function
我的网站上有 TinyMCE 插件。在 TinyMCE 插件的 textarea 中添加图像时,我希望这些图像包含延迟加载。我网站的缩略图具有特定类型的延迟加载,其中 src 图像是灰色背景。根据用户
我希望合并润滑间隔,以便如果它们重叠,则从内部第一个时间获取最小值和从内部最后一个时间获取最大值并总结以创建一个跨越整个时间段的新间隔。这是一个reprex: library(lubridate, w
我有一个应用程序,它本质上是一个页眉、主要内容和一个始终可见的页脚。页脚可以改变大小,我想在页脚上方的主内容面板上放置一些工具。主要布局是用 flex 完成的,我阅读文档的理解是绝对定位通过相对于最近
我是一名优秀的程序员,十分优秀!