前言

之所以会搞这个手势识别分类,其实是为了满足之前群友提的需求,就是针对稚晖君的ElectronBot机器人的上位机软件的功能丰富,因为本来擅长的技术栈都是.NET,也刚好试试全能的.NET是不是真的全能就想着做下试试了,MediaPipe作为谷歌开源的机器视觉库,功能很丰富了,而且也支持c++,翻遍社区果然找到了一个基于MediaPipe包装的C#版本,叫MediaPipe.NET,于是就开始整活了。

所用框架介绍

1. WASDK

这个框架是微软最新的UI框架,我主要是用来开发程序的主体,做一些交互和功能的承载,本质上和wpf,uwp这类程序没什么太大的区别,区别就是一些工具链的不同。

2. MediaPipe

MediaPipe offers open source cross-platform, customizable ML solutions for live and streaming media.

我主要使用MediaPipe进行手部的检测和手部关键点坐标的提取,因为MediaPipe只能达到这种程度,对于手势的分类什么的需要我们自己处理计算数据,但是这样也有好处,就是我们可以做出自己想要的手势。

3. ML.NET

开放源代码的跨平台机器学习框架

既然是个机器学习框架,那我们肯定可以通过框架提供的功能进行一些数据的处理学习。

ML.NET包含的一些功能如下:

  • 分类/类别划分 自动将客户反馈分为积极和消极两类
  • 回归/预测连续值 根据面积和地段预测房价
  • 异常检测 检测欺诈性的银行交易
  • 建议 根据网购者以前的购买情况,推荐他们可能想购买的产品
  • 时序/顺序数据 预测天气/产品销售额
  • 图像分类 对医学影像中的病状进行分类
  • 文本分类 根据文档内容对文档进行分类
  • 句子相似性 测量两个句子的相似程度

我在使用MediaPipe进行手部关键点检测之后,就获取了手部关键点的坐标数据,可以通过坐标数据整理成表格保存下来,然后通过ML.NET进行数据分析,主要使用文本分类功能。

整体的思路,MediaPipe检测是是手部关键点的坐标,即我们的手部保持一个动作的话,坐标点之间的相对关系肯定差别不大,当我们的某个手势的数据量足够的多,那我们就可以通过ML.NET得到一个手势的数据规则,当我们通过数据进行分类的时候就能够匹配到最接近的手势了。

目标我通过ML.NET训练的手势如下图:

手势的数据也上传到仓库了,大家可以进行查看详细的在代码讲解的地方进行介绍。

主要得到启发的项目是下面的仓库,大家可以自行学习。

DJI Tello Hand Gesture control

代码讲解(干货篇)

1. 项目介绍

项目地址

项目结构如下图:

注意由于MSIX打包的WASDK的路径访问为虚拟文件系统所以我们需要在项目里加入VFS目录,将引用的mediapipe的模块和dll放进去,不然会导致代码无法使用。

详情见如下文档:

打包的 VFS 位置

软件处理过程如下:

WinUI(WASDK)项目调用摄像头

=>OpencvSharp处理帧数据

=>转换成ImageFrame

=>MediaPipe处理返回手部关键点数据

=>ML.NET项目分析关键点手势分类

=>返回手势标签

=>软件进行业务处理

由于WASDK的摄像头帧处理事件有点问题,所以我只能先用本地图片做演示了。

2.核心代码讲解

初始化的代码如下图:

核心代码如下:

 private async void CameraHelper_FrameArrived(object sender, CommunityToolkit.WinUI.Helpers.FrameEventArgs e)
{
try
{
// Gets the current video frame
VideoFrame currentVideoFrame = e.VideoFrame; // Gets the software bitmap image
SoftwareBitmap softwareBitmap = currentVideoFrame.SoftwareBitmap; if (softwareBitmap != null)
{
//if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
// softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
//{
// softwareBitmap = SoftwareBitmap.Convert(
// softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
//} //using IRandomAccessStream stream = new InMemoryRandomAccessStream(); //var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream); //// Set the software bitmap
//encoder.SetSoftwareBitmap(softwareBitmap); //await encoder.FlushAsync(); //var image = new Bitmap(stream.AsStream()); //var matData = OpenCvSharp.Extensions.BitmapConverter.ToMat(image); var matData = new OpenCvSharp.Mat(Package.Current.InstalledLocation.Path + $"\\Assets\\hand.png"); var mat2 = matData.CvtColor(OpenCvSharp.ColorConversionCodes.BGR2RGB); var dataMeta = mat2.Data; var length = mat2.Width * mat2.Height * mat2.Channels(); var data = new byte[length]; Marshal.Copy(dataMeta, data, 0, length); var widthStep = (int)mat2.Step(); var imgframe = new ImageFrame(ImageFormat.Types.Format.Srgb, mat2.Width, mat2.Height, widthStep, data); var handsOutput = calculator.Compute(imgframe); Bitmap bitmap = BitmapConverter.ToBitmap(matData); var ret = await BitmapToBitmapImage(bitmap); if (ret.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
ret.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
ret = SoftwareBitmap.Convert(ret, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
} if (handsOutput.MultiHandLandmarks != null)
{
var landmarks = handsOutput.MultiHandLandmarks[0].Landmark; Debug.WriteLine($"Got hands output with {landmarks.Count} landmarks" + $" at frame {frameCount}"); var result = HandDataFormatHelper.PredictResult(landmarks.ToList(), modelPath); this.DispatcherQueue.TryEnqueue(async() =>
{
var source = new SoftwareBitmapSource(); await source.SetBitmapAsync(ret); HandResult.Text = result;
VideoFrame.Source = source;
});
}
else
{
Debug.WriteLine("No hand landmarks");
}
}
}
catch (Exception ex)
{ }
frameCount++;
}

主要注意的点是图片格式的转换,opencv加载出来的格式转换成RGB的时候要看下是BGR2RGB还是BGRA2RGBA。

如果不确定的话,可以使用源码里采用FFmpeg封装的demo代码进行使用,那个包含了摄像头帧读取,和数据转换。

核心代码如下:

   private static async void onFrameEventHandler(object? sender, FrameEventArgs e)
{
if (calculator == null)
return; Frame frame = e.Frame;
if (frame.Width == 0 || frame.Height == 0)
return; converter ??= new FrameConverter(frame, PixelFormat.Rgba);
Frame cFrame = converter.Convert(frame); ImageFrame imgframe = new ImageFrame(ImageFormat.Types.Format.Srgba,
cFrame.Width, cFrame.Height, cFrame.WidthStep, cFrame.RawData); HandsOutput handsOutput = calculator.Compute(imgframe); if (handsOutput.MultiHandLandmarks != null)
{
var landmarks = handsOutput.MultiHandLandmarks[0].Landmark;
Console.WriteLine($"Got hands output with {landmarks.Count} landmarks"
+ $" at frame {frameCount}"); //await HandDataFormatHelper.SaveDataToTextAsync(landmarks.ToList()); HandDataFormatHelper.PredictResult(landmarks.ToList());
//Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(landmarks));
}
else
{
Console.WriteLine("No hand landmarks");
} frameCount++;
}

特别感谢的项目就是这个MediaPipe.NET了,没有它就没有我的这篇文章,更没有我的项目了。

个人感悟

又到了个人感悟环节,在最近测试的环节里,发现WASDK还是要有很长一段路要走,开发体验和UWP差太大了,但是好处是它比UWP的自由度高了很多,也可以使用.NET的新特性,和一些轮子,就很舒服。

再者随着.NET社区越来越好,很多好用的轮子就会越来越多了,社区大家记得多多贡献了。

参考推荐文档如下

demo地址

WASDK文档地址

MediaPipe

MediaPipe.NET

ML.NET

hand-gesture-recognition-using-mediapipe

Control DJI Tello drone with Hand gestures

WinUI(WASDK)使用MediaPipe检查手部关键点并通过ML.NET进行手势分类的更多相关文章

  1. [UWP] WinUI 2.6 使用指南

    2021年6月24日,Windows 11 正式对外发布,对于UWP开发者来说,这一天同样值得纪念,因为WinUI 2.6也正式发布了! 相同的时间点意味着一件事,即WinUI 2.6和Windows ...

  2. RHEL7 CentOS7 检查查看精简指令

    RHEL7 CentOS7 检查查看精简指令: //////////////////////////检查查看精简指令://///////////////////////////// ///////// ...

  3. 基于Spark.NET和ML.NET Automated ML (自动学习)进行餐厅等级的检查预测

    简介 Apache Spark是一个开源.分布式.通用的分析引擎.多年来,它一直是大数据生态系统中对大型数据集进行批量和实时处理的主要工具.尽管对该平台的本地支持仅限于JVM语言集,但其他通常用于数据 ...

  4. laravel7 百度智能云检测图片是否合规

    APP 文件下建一个Libs目录建一个BaiduService文件,需要检测引入进行调用即可 BaiduService文件内容如下 <?php namespace App\Libs; use A ...

  5. 手势识别(一)--手势基本概念和ChaLearn Gesture Challenge

    以下转自: http://blog.csdn.net/qq1175421841/article/details/50312565 像点击(clicks)是GUI平台的核心,轻点(taps)是触摸平台的 ...

  6. Kinect 开发 —— 手势识别(下)

    基本手势追踪 手部追踪在技术上和手势识别不同,但是它和手势识别中用到的一些基本方法是一样的.在开发一个具体的手势控件之前,我们先建立一个可重用的追踪手部运动的类库以方便我们后续开发.这个手部追踪类库包 ...

  7. oracle异常(-)

    一.概述异常分成三大类:预定义异常.非预定义异常.自定义异常处理方法分为:直接抛出异常.内部块处理异常.游标处理异常 预定义异常:由PL/SQL定义的异常.由于它们已在standard包中预定义了,因 ...

  8. Hibernate validation 注解 springmvc 验证 分组

    SpringMVC验证框架Validation特殊用法   1. 分组 有的时候,我们对一个实体类需要有多中验证方式,在不同的情况下使用不同验证方式,比如说对于一个实体类来的id来说,保存的时候是不需 ...

  9. Machine Learning - 第6周(Advice for Applying Machine Learning、Machine Learning System Design)

    In Week 6, you will be learning about systematically improving your learning algorithm. The videos f ...

  10. chrome开发工具指南(七)

    检查动画 使用 Chrome DevTools 动画检查器检查和修改动画. 通过打开动画检查器捕捉动画.检查器会自动检测动画并将它们分类为多个组. 通过慢速播放.重播或查看动画源代码来检查动画. 通过 ...

随机推荐

  1. kubeoperator升级步骤

    当前kubeoperator版本是3.6.0 官方文档:https://kubeoperator.io/docs/installation/install/ # 离线升级需要提前下载离线安装包,并解压 ...

  2. js内置禁用按钮 disabled

    按钮在监听到disabled后面的布尔值就可以实现是否禁用 一: <button :disabled="book.count <= 1" @click="de ...

  3. SQL日期查询语句

    --查询当天(1: select * from ShopOrder where datediff(day,ordTime,getdate()-1)=0 --查询当天(2:select * from i ...

  4. P4588 [TJOI2018]数学计算 (线段树)

    用线段树维护操作序列,叶子结点存要乘的数,非叶子结点存区间乘积,每次输出tr[1] 就是答案. 1 #include<bits/stdc++.h> 2 #define ll long lo ...

  5. 在vue中_this和this的区别

    _this只是一个变量名,this代表父函数,如果在子函数还用this,this的指 向就变成子函数了,_this就是用来存储指向的 普通函数中的this表示调用此函数时的对象,箭头函数里面的this ...

  6. 一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.简述MySQL中索引类型对数据库的性能的影响 2.RDB和AOF机制 3.Redis的过期键的删除策略 4.Redis ...

  7. 一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.分布式架构下,Session共享有什么方案 2.简述你对RPC.RMI的理解 3.分布式id生成方案 4.分布式锁解决 ...

  8. 齐博x2新用户手工注册接口

    由于手工注册有点太落后了,并不推荐,所以我们也没有单独的为API接口开发一个注册的页面,大家可以统一使用PC或WAP的注册页来当接口使用.请求地址是:http://qb.net/index.php/i ...

  9. 33.ModelSerializer详解

    ModelSerializer特点 根据Model模型的定义,自动生成字段 自动生成相应的验证器 实现create和update 自动默认将关系字段映射成PrimaryKeyRelatedField主 ...

  10. 第一阶段:linux运维基础·1

    1. 服务器的主要硬件是?以及其作用是? cpu 相当于人体的大脑,负责计算机的运算和控制 内存 解决cpu与硬盘之间速度不匹配的问题 磁盘 永久存放数据的存储器 主板 直接或间接的将所有的设备连接在 ...