使用ML.NET+ONNX预训练模型整活B站经典《华强买瓜》

前言

最近在看微软开源的机器学习框架ML.NET使用别人的预训练模型(开放神经网络交换格式.onnx)来识别图像,然后逛github发现一个好玩的repo。决定整活一期博客。

首先还是稍微科普一下机器学习相关的知识,这一块.NET虽然很早就开源了ML.NET框架,甚至在官方的ML.NET开源之前,就有一些三方社区的开源实现比如早期的AForge.NET实现。以及后来的基于python著名的神经网络框架tensorflow迁移的tensorflow.net亦或者是pytorch迁移的torchsharp来实现C#版本的深度学习,但是毕竟C#确实天生并不适合用来搞机器学习/深度学习,AI这一块也一直都是python的基本盘。但是不适合并不代表没有方案,现在AI逐渐普及的今天,我们普通的开发者依然可以使用一些别人训练好的模型来做一些应用落地。

环境准备

今天我们会用到一些训练好的模型来实现我们的目的,需要准备以下环境和工具:

1、安装有.NET5或者6的windows开发环境

2、netron 用于解析模型的参数。
下载地址:https://t.ly/8iEk8

3、ffmpeg 用于视频处理
下载地址:https://t.ly/ro0G

4、onnx预训练udnie、super-resolution

udnie模型
下载地址:https://t.ly/0cUt

super-resolution模型
下载地址:https://t.ly/rnsi(需要解压提取内部的onnx文件)

操作流程

1、首先我们将目标视频(我这里就用B站经典短视频《华强买瓜》为例)通过ffmpeg转换成普通的一帧一帧的图片

2、通过ML.NET加载【神经风格转换预训练模型】将每一帧原图迁移到新的风格(艺术风格:udnie,抽象主义)。

3、由于2只能将图片迁移到固定的240240格式,所以我们还需要通过ML.NET加载【超分辨率预训练模型】将每一帧图片进行超分辨率放大得到一张672672的图片

4、通过ffmpeg将新的图片合并成新的视频

首先先看看成品(这里我转换成gif方便演示):

原版视频《华强买瓜》 1280*720

e2583168caff98d140cebf38da6fcfee.gif

迁移后的抽象艺术版本 224*224

1e1e451b100ebb31398222438f3f58b3.gif

超分辨放大后的版本 672*672

6d5b841265242509ee9bd3c99850c000.gif

接着我们看看如何一步一步来实现这个流程的

首先我们新建一个空白文件夹,将下载好的ffmpeg.exe和准备要处理的mp4视频文件放进这个空白文件夹

接着我们需要从视频中分离音频文件,用于后期合成视频时把音频合成回去,否则视频会没有声音,打开控制台CD到刚才的目录,执行命令:

b9ab8a617523d7d6d0a128a89ec02a4e.png

然后我们从视频中将每一帧拆解成一张一张的jpg图片,这里首先要创建一个img子文件夹,否则会报错。另外我选择的r 25意思就是每秒25帧。如果你的视频不是每秒25帧(右键-属性-详细信息-帧速率)则自行根据文件调整,最后合成的时候也需要按照这个帧率合成新的视频:

d53ee08d34d1be006ab5d29a476f5be9.png

到这里为止,我们就将图片和音频拆解出来了,接下来准备编码,首先我们打开VS创建一个控制台程序,引入nuget包:

57c8626cf415db659e528d9720073b46.png

接着我们创建一个一个类文件用于加载模型以及完成相应的图片处理,在此之前我们需要使用安装好的netron来打开这两个onnx模型,查询他们的输入输出值,打开netron选择file-open,然后选择第一个模型udnie-9.onnx,点击input,可以看到右边已经展示出了这个模型的输入和输出项,接着我们创建类的时候,这里需要这一些数字。

00b22c33b2a67e05ea19a447a1530ea0.png

接着我们打开VS创建好的项目,把我们的两个onnx模型引入进去。接着编写如下代码:

首先定义一个session用于加下onnx模型

static InferenceSession styleTransferSession = new InferenceSession("model/udnie-9.onnx");

接着我们创建一个方法调用这个模型

public static Bitmap ProcessStyleTransfer(Bitmap originBmp)
{//根据netron得到的input,我们在这里构建对应的输入张量var input = new DenseTensor<float>(new[] { 1, 3, 224, 224 });//将bitmap转换成inputTool.BitmapToTensor(originBmp, 224, 224, ref input, true);//接着调用模型得到迁移后的张量outputusing var results = styleTransferSession.Run(new[] { NamedOnnxValue.CreateFromTensor("input1", input) });if (results.FirstOrDefault()?.Value is not Tensor<float> output)throw new ApplicationException("无法处理图片");//由于模型输出的是3*224*224的张量,所以这里只能构建出224*224的图片return Tool.TensorToBitmap(output, 224, 224);
}

其实到这一步神经风格迁移就完成了,最后的bitmap就是迁移后的新图片,我们只需要调用bitmap.save即可保存到磁盘上

接着我们创建超分辨率模型的方法来,其实同上面的调用非常类似的代码

这里唯一需要注意的是超分辨率提取并非采用RGB直接放大,而是用了YCbCr来放大,所以这里需要有一个转换,原文在这里:https://github.com/onnx/models/tree/main/vision/super_resolution/sub_pixel_cnn_2016

static InferenceSession superResolutionSession = new InferenceSession("model/super_resolution.onnx");
public static Bitmap ProcessSuperResolution(Bitmap originBmp)
{//根据netron得到的input,我们在这里构建对应的输入张量,由于该模型并非采用RGB而是YCbCr,所以中间会做一些转换,不过整体流程和上一个类似var input = new DenseTensor<float>(new[] { 1, 1, 224, 224 });//将bitmap转换成inputTool.BitmapToTensor(originBmp, 224, 224, ref input, true);//由于模型处理Y值,剩下的Cb和Cr需要我们单独调用System.Drawing.Common双三次插值算法放大得到对应的Cb和Cr值var inputCbCr = new DenseTensor<float>(new[] { 1, 672, 672 });inputCbCr = Tool.ResizeGetCbCr(originBmp, 672, 672);//接着调用模型得到超分重建后的张量outputusing var results = superResolutionSession.Run(new[] { NamedOnnxValue.CreateFromTensor("input", input) });if (results.FirstOrDefault()?.Value is not Tensor<float> output)throw new ApplicationException("无法处理图片");//创建一个新的bitmap用于填充迁移后的像素,这里需要通过Y+CbCr转换为RGB填充return Tool.TensorToBitmap(output, 224, 224,false, inputCbCr);
}

其实基本上到这两步,我们的整个核心代码就完成了。剩余的部分只是一些图片处理的代码。接着我们要做的就是在Program.cs调用它得到迁移后的图片

Directory.CreateDirectory("new img path");
foreach (var path in Directory.GetFiles("old img path"))
{//由于ffmpeg拆帧后的图片就是按照帧率从1开始排序好的图片,所以我们只需要将上一层的文件夹名字修改一下即可得到要替换的新文件路径 like: D://img/1.jpeg -> D://newimg/1.jpegvar newpath = path.Replace("old img path", "new img path");using var originBitmap = new Bitmap(Image.FromFile(path));using var transferBitmap = OnnxModelManager.ProcessStyleTransfer(originBitmap);using var reSizeBitmap = OnnxModelManager.ProcessSuperResolution(transferBitmap);reSizeBitmap.Save(newpath);
}

接着F5 run,然后静待,一般要转换20分钟左右(cpu i5)基本就转换完成了。最后我们只需要再使用工具合成新的视频(或者gif)

./ffmpeg -f image2 -i newimg/%d.jpeg -i 1.aac -map 0:0 -map 1:a -r 25 -shortest output.mp4
1 internal class Tool2     {3         /// <summary>4         /// 将bitmap转换为tensor5         /// </summary>6         /// <param name="bitmap"></param>7         /// <returns></returns>8         public static void BitmapToTensor(Bitmap originBmp, int resizeWidth, int resizeHeight, ref DenseTensor<float> input, bool toRGB)9         {10             using var inputBmp = new Bitmap(resizeWidth, resizeHeight);11             using Graphics g = Graphics.FromImage(inputBmp);12             g.DrawImage(originBmp, 0, 0, resizeWidth, resizeHeight);13             g.Save();14             for (var y = 0; y < inputBmp.Height; y++)15             {16                 for (var x = 0; x < inputBmp.Width; x++)17                 {18                     var color = inputBmp.GetPixel(x, y);19                     if (toRGB)20                     {21                         input[0, 0, y, x] = color.R;22                         input[0, 1, y, x] = color.G;23                         input[0, 2, y, x] = color.B;24                     }25                     else26                     {27                         //将RGB转成YCbCr,此处仅保留Y值用于超分辨率放大28                         var ycbcr = RGBToYCbCr(color);29                         input[0, 0, y, x] = ycbcr.Y;30                     }31                 }32             }33         }34         /// <summary>35         /// 将tensor转换成对应的bitmap36         /// </summary>37         /// <param name="output"></param>38         /// <returns></returns>39         public static Bitmap TensorToBitmap(Tensor<float> output, int width, int height, bool toRGB = true, Tensor<float> inputCbCr = null)40         {41             //创建一个新的bitmap用于填充迁移后的像素42             var newBmp = new Bitmap(width, height);43             for (var y = 0; y < newBmp.Height; y++)44             {45                 for (var x = 0; x < newBmp.Width; x++)46                 {47                     if (toRGB)48                     {49                         //由于神经风格迁移可能存在异常值,所以我们需要将迁移后的RGB值确保只在0-255这个区间内,否则会报错50                         var color = Color.FromArgb((byte)Math.Clamp(output[0, 0, y, x], 0, 255), (byte)Math.Clamp(output[0, 1, y, x], 0, 255), (byte)Math.Clamp(output[0, 2, y, x], 0, 255));51                         newBmp.SetPixel(x, y, color);52                     }53                     else54                     {55                         //分别将模型推理得出的Y值以及我们通过双三次插值得到的Cr、Cb值转换为对应的RGB色56                         var color = YCbCrToRGB(output[0, 0, y, x], inputCbCr[0, y, x], inputCbCr[1, y, x]);57                         newBmp.SetPixel(x, y, color);58                     }59                 }60             }61             return newBmp;62         }63         /// <summary>64         /// RGB转YCbCr65         /// </summary>66         public static (float Y, float Cb, float Cr) RGBToYCbCr(Color color)67         {68             float fr = (float)color.R / 255;69             float fg = (float)color.G / 255;70             float fb = (float)color.B / 255;71             return ((float)(0.2989 * fr + 0.5866 * fg + 0.1145 * fb), (float)(-0.1687 * fr - 0.3313 * fg + 0.5000 * fb), (float)(0.5000 * fr - 0.4184 * fg - 0.0816 * fb));72         }73         /// <summary>74         /// YCbCr转RGB75         /// </summary>76         public static Color YCbCrToRGB(float Y, float Cb, float Cr)77         {78             return Color.FromArgb((byte)Math.Clamp(Math.Max(0.0f, Math.Min(1.0f, (float)(Y + 0.0000 * Cb + 1.4022 * Cr))) * 255, 0, 255),79                 (byte)Math.Clamp(Math.Max(0.0f, Math.Min(1.0f, (float)(Y - 0.3456 * Cb - 0.7145 * Cr))) * 255, 0, 255),80                 (byte)Math.Clamp(Math.Max(0.0f, Math.Min(1.0f, (float)(Y + 1.7710 * Cb + 0.0000 * Cr))) * 255, 0, 255)81                 );82         }83         /// <summary>84         /// 双三次插值提取CbCr值85         /// </summary>86         public static DenseTensor<float> ResizeGetCbCr(Bitmap original, int newWidth, int newHeight)87         {88             var cbcr = new DenseTensor<float>(new[] { 2, newWidth, newHeight });89             using var bitmap = new Bitmap(newWidth, newHeight);90             using var g = Graphics.FromImage(bitmap);91             g.InterpolationMode = InterpolationMode.HighQualityBicubic;92             g.SmoothingMode = SmoothingMode.HighQuality;93             g.DrawImage(original, new Rectangle(0, 0, newWidth, newHeight),94                 new Rectangle(0, 0, original.Width, original.Height), GraphicsUnit.Pixel);95             g.Dispose();96             for (var y = 0; y < bitmap.Width; y++)97             {98                 for (var x = 0; x < bitmap.Height; x++)99                 {
100                     var color = bitmap.GetPixel(x, y);
101                     var ycbcr = RGBToYCbCr(color);
102                     cbcr[0, y, x] = ycbcr.Cb;
103                     cbcr[1, y, x] = ycbcr.Cr;
104                 }
105             }
106             return cbcr;
107         }
108     }

总结

这一期整活基本到此就结束了,虽然只是调用了两个小模型搞着玩,但是其实只要能搞到业界主流的开源预训练模型,其实可以解决很多实际的商业场景,比如我们最近在使用美团开源的yolov6模型做一些图像对象检测来落地就是一个很好的例子这里就不再展开。另外微软也承诺ML.NET的RoadMap会包含对预训练模型的迁移学习能力,这样我们可以通过通用的预训练模型根据我们自己的定制化场景只需要提供小规模数据集即可完成特定场景的迁移学习来提高模型对特定场景问题的解决能力。今天就到这里吧,下次再见。

作者:a1010

原文:https://www.cnblogs.com/gmmy/p/16433499.html

END

aa70f08449953bb08a8272f026b48d94.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/285355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言试题125之一个 5 位数,判断它是不是回文数。即 12321 是回文数,个位与万位相同,十位与千位相同

✅作者简介:大家好我是码莎拉蒂,CSDN博客专家🥇🥇🥇 📃个人主页:个人主页 🔥系列专栏:C语言试题200例 💬推荐一款模拟面试、刷题神器👉 点击跳转进入网站 1、题目 题目:一个 5 位数,判断它是不是回文数。即 12321 是回文数,个位与万位相同,十位与千位相…

SQL Server在更改计算机名后的设置

把原来的账号删除 再添加现有的账号 添加权限 搞定了上面的账号配置&#xff0c;接下来就是设置服务器名称 参考&#xff1a;http://www.cnblogs.com/EasonJim/p/6114249.html 后话&#xff1a;当初为了设置这个问题&#xff0c;选择了重装SQL Server&#xff0c;但是也是无用的…

Jupyter Notebook 入门指南

简介 Jupyter Notebook&#xff08;此前被称为 IPython notebook&#xff09;是一个交互式笔记本&#xff0c;支持运行 40 多种编程语言。 Jupyter Notebook 的本质是一个 Web 应用程序&#xff0c;便于创建和共享文学化程序文档&#xff0c;支持实时代码&#xff0c;数学方程…

C语言试题127之 100 之内的素数

✅作者简介:大家好我是码莎拉蒂,CSDN博客专家🥇🥇🥇 📃个人主页:个人主页 🔥系列专栏:C语言试题200例 💬推荐一款模拟面试、刷题神器👉 点击跳转进入网站 1、题目 题目:求 100 之内的素数 2 、温馨提示 想获取更多C语言题目请猛搓这里==========》200个C语…

MongoDB中的分组

一.MongoDB中的Count函数、Distinct函数以及分组 准备工作&#xff0c;插入一个班级的文档 > for(var i0;i<10;i){ ... db.Classes.insert({ClassName:"Class"i,_id:i}); ... } WriteResult({ "nInserted" : 1 }) > db.Classes.find() { "_i…

在 .NET 6 项目中使用 Startup.cs

对于 .NET 6 项目&#xff0c;现在已经找不到 Startup.cs 文件。默认情况下&#xff0c;此文件已经被删除&#xff0c;并且 Program.cs 是配置依赖注入服务和 Middleware 的新位置。但是&#xff0c;有些人可能更喜欢使用 Startup.cs , 并且我也是&#xff0c;可能已经习惯了&a…

编写iptables脚本实现IP地址、端口过滤

实验案例&#xff1a;公司使用一台运行RHEL5系统的服务器作为网关&#xff0c;分别连接三个网络&#xff0c;其中LAN1为普通员工电脑所在的局域网&#xff0c;LAN2为DNS缓存服务器所在的局域网。eth0通过10M光纤接入Internet。为了有效的管理网络环境及增强内部网络的安全性&am…

让VS Code 支持 Jupyter Notebook

一、Jupyter Notebook Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。——Jupyter Notebook官方介绍。 Jupyter Notebook是以网页的形式打开&#xff0c;可以在网页页面中直接编写代码和运行代…

C语言试题128之对 10 个数进行排序

✅作者简介:大家好我是码莎拉蒂,CSDN博客专家🥇🥇🥇 📃个人主页:个人主页 🔥系列专栏:C语言试题200例 💬推荐一款模拟面试、刷题神器👉 点击跳转进入网站 1、题目 题目:对 10 个数进行排序 分析:可以利用选择法,即从后 9 个比较过程中,选择一个最小的与…

Windows 11 23H2 25145 推送!全新隐私设置和 OneDrive 体验

面向 Dev 频道的 Windows 预览体验成员&#xff0c;微软现已推送 Windows 11 预览版 Build 25145。主要变化1.微软宣布为 Windows 11 设置引入全新 OneDrive 体验&#xff0c;您可以在设置中查看 OneDrive 云存储服务的订阅详情&#xff0c;包括付费方式、容量大小和定期付费等…

用jekyll制作高大上的网站(二)——实际应用

最近公司要制作个文档库&#xff0c;直接就可以将jekyll应用到实际中。 模版使用了Jekyll Clean&#xff0c;这么模版相对内部简单一点&#xff0c;学习成本不会很大&#xff0c;而复杂的Minimal Mistakes就当作参考。 模版使用的CSS是Bootstrap v3.2.0版本的。为了省时点&…

C语言试题129之求一个 3乘3 矩阵对角线元素之和

✅作者简介:大家好我是码莎拉蒂,CSDN博客专家🥇🥇🥇 📃个人主页:个人主页 🔥系列专栏:C语言试题200例 💬推荐一款模拟面试、刷题神器👉 点击跳转进入网站 1、题目 题目:求一个 3乘3 矩阵对角线元素之和 分析:利用双重 for 循环控制输入二维数组,再将 a[…

ssh key生成

Mac电脑用终端生成SSH key 访问自己的Github 字数684 阅读427 评论4 喜欢15前言&#xff1a;最近有不少刚刚使用github管理代码的开发者或者新手码农在网上 问我如何关联自己的github&#xff0c;今天就写篇文章仅供参考。一、首先你要检测自己电脑是否存在 SSH key 在终端输出…

[转]小白都能看懂的softmax详解

1.softmax初探 在机器学习尤其是深度学习中&#xff0c;softmax是个非常常用而且比较重要的函数&#xff0c;尤其在多分类的场景中使用广泛。他把一些输入映射为0-1之间的实数&#xff0c;并且归一化保证和为1&#xff0c;因此多分类的概率之和也刚好为1。 首先我们简单来看看s…

MAUI 入门教程系列(5.XAML及页面介绍)

前言作为微软的UI框架&#xff0c;除了Winform以外&#xff0c;多数是以创建XAML文件的方式来编写前端的页面&#xff0c;尽管你也可以通过C#代码来编写你的用户界面&#xff0c;与Xamarin.Forms相同&#xff0c;在MAUI上编写XAML的声明方式与其相同&#xff0c;重构了底层部分…

蓝牙mesh网络基础

蓝牙mesh网络基础转载于:https://blog.51cto.com/11534544/2044130

Linux系统开机自启流程

第一步&#xff1a;POST&#xff08;Power On Safe Test&#xff09;加电自检当按下电源键以后&#xff0c;CPU因为有电流通过便开始对自身、I/O设备、内存等硬件进行检验。那么CPU如何得知这些自检指令&#xff1f;我们知道指令存在于内存当中&#xff0c;寻找内存也需要指令。…

ArcGIS空间数据:矢量和栅格数据结构详解

文章目录 矢量数据结构矢量数据简介Shapefile矢量格式栅格数据结构地理数据库中的栅格栅格管理策略栅格数据的地理属性栅格的地理属性通常包括地理数据集中的栅格块表实体栅格表示矢量数据结构 矢量数据简介 基于矢量数据模型的数据结构简称矢量数据结构,是通过记录实体坐标…

C语言试题130之有一个已经排好序的数组。现输入一个数,要求按原来的规律将它插入数组中

✅作者简介:大家好我是码莎拉蒂,CSDN博客专家🥇🥇🥇 📃个人主页:个人主页 🔥系列专栏:C语言试题200例 💬推荐一款模拟面试、刷题神器👉 点击跳转进入网站 1、题目 题目:有一个已经排好序的数组。现输入一个数,要求按原来的规律将它插入数组中 分析:先判…

[转]一文读懂目标检测:R-CNN、Fast R-CNN、Faster R-CNN、YOLO、SSD

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/kwame211/article/details/88016151 一、目标检测常见算法 object detection&#xff0c;就是在给定的图片中精确找到物体所在位置&#xff0c;并标注出物体的类别。所以&a…