AI应用开发实战 - 定制化视觉服务的使用
本篇教程的目标是学会使用定制化视觉服务,并能在UWP应用中集成定制化视觉服务模型。
前一篇:AI应用开发实战 - 手写识别应用入门
建议和反馈,请发送到
https://github.com/Microsoft/vs-tools-for-ai/issues
联系我们
[email protected]
零、定制化视觉服务简介
有的时候,在构建应用的过程中,在缺少强大计算资源与高性能算法的情况下,我们不一定需要自己从零开始训练模型。我们需要用的一些轮子,已经有人给我们造好了。
就比如:
微软提供的定制化视觉服务。
在机器学习应用中,任何情况下都需要一个或大或小的模型。而怎么得到这个模型是其中最复杂的部分。定制化视觉服务相当于在云端提供了一个生成模型的方法,把模型相关的复杂的算法都简化了。同时,它不仅能够让用户自己管理训练数据,定义自己的分类问题,而且支持一键训练,一键导出模型;不仅能导出适配所有主流框架的模型,而且可以生成REST接口,让程序通过接口获取图片分类的结果。这样给用户提供了多种集成模型的方法和选择,尽可能满足用户的各种需求,这也正是定制化视觉服务的强大之处。同时,通过定制化服务来生成模型,需要的数据量可以非常少,训练过程相对来说也很快。使用上也是非常的方便。
本篇教程,就教大家如何使用定制化视觉服务。
定制化视觉服务官方地址 :https://customvision.ai/
一、准备微软账号
使用该服务需要准备微软账号,可以直接在定制化视觉服务官方地址上创建。
二、创建定制化视觉服务
三、创建定制化视觉服务项目
点击New Project
,填写项目信息。
这里不妨以一个熊的分类模型作为例子来实践吧。
填写好Name
和Description
,这里Name
不妨填写为BearClassification
。
随后选择Classification
和General(compact)
,点击Create
如果需要上传大量的图片数据,那么点击鼠标的方式肯定不够方便,微软同时提供了代码的支持,详见官方文档:
https://docs.microsoft.com/en-us/azure/cognitive-services/custom-vision-service/home
四、使用Windows ML构建应用
这次不写Winform程序,而是搭建一个识别熊的UWP的AI应用,通过这个应用来教大家如何使用Windows ML导入模型。
这部分的代码已经完成了,请使用git克隆samples-for-ai到本地,UWP项目的代码在/samples-for-ai/projects/BearClassificationUWPDemo中。
在运行代码之前,请先安装开发UWP所需的工作负载,流程如下:
- 打开Visual Studio Installer
- 在工作负载中勾选Universal Windows Platform development
- 在单个组件一栏中下拉到最下方,确认Windows 10 SDK(10.0.17134.0)已被勾选上,这是使用Windows ML开发的核心组件
另外,请将您的操作系统更新到1803版本,否则本程序将不能安装。
如果您将进行类似的开发,请将UWP项目设置成最低运行目标版本为17134,否则对于版本低于17134的用户,在运行时会出现:
"Requested Windows Runtime type 'Windows.AI.MachineLearning.Preview.LearningModelPreview' is not registered."
详见:https://github.com/MicrosoftDocs/windows-uwp/issues/575
安装需要的时间比较长,可以先看看UWP的视频教程,做一做头脑预热: https://www.bilibili.com/video/av7997007
Visual Studio 和 Windows 更新完毕后,我们打开CustomVisionApp.sln,运行这个程序。
你可以从必应上查找一些熊的图片,复制图片的URL,粘贴到输入框内,然后点击识别按钮;或者,点击浏览按钮,选择一张本地图片,点击确定,你就可以看到识别结果了:
现在来看看这个程序是怎么实现的。
我们来梳理一下这个应用的逻辑,这个应用的逻辑与上一篇博客中的手写数字识别大体上是一样的:
- 导入模型
- 按下按钮后,通过某种方式获取要用来识别的图片
- 将图片交给模型识别
- 将图片与识别结果展示在界面上
1. 文件结构:
文件结构见下图:
- Assets文件夹存放了这个项目的资产文件,比如程序图标等等,在本示例程序中,.onnx文件也存放在其中。
- Strings文件夹存放了用于本地化与全球化资源文件,这样可以支持不同的语言。
- ViewModel文件夹中则存放了本项目的关键代码,整个程序运行的逻辑都在ResultViewModel.cs中
- BearClassification.cs则是系统自动生成的模型包装文件
- MainPage.xaml是程序的UI布局文件
2. 核心代码一:BearClassification.cs
这部分的代码是自动生成的,教程详见链接:https://docs.microsoft.com/zh-cn/windows/uwp/machine-learning/
- 将.onnx文件添加到UWP项目的Assets文件夹中,随后将自动生成一个对应的包装
.cs
文件,在本例中为BearClassification.cs
。 - 由于目前存在的一些BUG,生成的类名会有乱码,需要将乱码替换为别的字符串。
- 修改
BearClassification.onnx
的属性->生成操作
,将其改为内容
,确保在生成时,能够调用到这个模型。
生成的文件共有三个类:
- BearClassificationModelInput:定义了该模型的输入格式是VideoFrame
- BearClassificationModelOutput:定义了该模型的输出为一个list和一个dict,list存储了所有标签按照probability降序排列,dict则存储了标签与概率的键值对
- BearClassificationModel:定义了该模型的初始化函数与推理函数
// 模型的输入格式为VideoFrame
public sealed class BearClassificationModelInput
{
public VideoFrame data { get; set; }
}
// 模型的输出格式,其中包含了一个列表:classLabel和一个字典:loss
// 列表中包含每种熊的标签,按照概率降序排列
// 字典中则包含了每种熊的标签和其概率,按照用户在创建模型时的添加顺序排列
public sealed class BearClassificationModelOutput
{
public IList classLabel { get; set; }
public IDictionary loss { get; set; }
public BearClassificationModelOutput()
{
this.classLabel = new List();
this.loss = new Dictionary(){...}
}
}
// 模型的包装类,提供了两个函数
// CreateBearClassificationModel:从.onnx文件中创建模型
// EvaluateAsync:对输入对象进行评估,并返回结果
public sealed class BearClassificationModel
{
private LearningModelPreview learningModel;
public static async Task CreateBearClassificationModel(StorageFile file)
{
...
}
public async Task EvaluateAsync(BearClassificationModelInput input)
{
...
}
}
3. 核心代码二:ResultViewModel.cs
通过之前的运行可以发现:每次识别图片,UI中的内容需要进行频繁地更新,为了简化更新控件内容的代码逻辑,这个程序使用UWP开发中常用的MVVM(model-view-viewmodel)这一组合模式开发,使用“绑定”的方式,将UI控件与数据绑定起来,让数据与界面自动地同步更新,简化了代码逻辑,保证了ResultViewModel职责单一。
绑定源(ResultViewMode.cs) | 绑定目标(MainPage.xaml) |
---|---|
string BearUrl | TextBox InputUriBox |
ObservableCollection Results | ListView ResultArea |
BitmapImage BearImage | Image DisplayArea |
string Description | TextBox DescribeArea |
ICommand RecognizeCommand | Button RecognizeButton |
ICommand BrowseCommand | Button BrowseButton |
绑定好之后,程序还需要一系列逻辑才能运行,这里就包括:
导入与初始化模型:
在程序一开始,需要调用LoadModel进行模型初始化工作。
private async void LoadModel()
{
//导入模型文件,实例化模型对象
StorageFile modelFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/BearClassification.onnx"));
model = await BearClassificationModel.CreateBearClassificationModel(modelFile);
}
图片推理:
本程序提供了两种方式访问图片资源:
- 通过URL访问网络图片
- 通过文件选取器访问本地图片
private async void EvaluateNetPicAsync()
{
try
{
...
//BearClassification要求的输入格式为VideoFrame
//程序需要以stream的形式从URL中读取数据,生成VideoFrame
var response = await new HttpClient().GetAsync(BearUrl);
var stream = await response.Content.ReadAsStreamAsync();
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream.AsRandomAccessStream());
VideoFrame imageFrame = VideoFrame.CreateWithSoftwareBitmap(await decoder.GetSoftwareBitmapAsync());
//将videoframe交给函数进行识别
EvaluateAsync(imageFrame);
}
catch (Exception ex){ ... }
}
private async void EvaluateLocalPicAsync()
{
try
{
...
// 从文件选取器中获得文件
StorageFile file = await openPicker.PickSingleFileAsync();
var stream = await file.OpenReadAsync();
...
// 生成videoframe
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
VideoFrame imageFrame = VideoFrame.CreateWithSoftwareBitmap(await decoder.GetSoftwareBitmapAsync());
// 将videoframe交给函数进行识别
EvaluateAsync(imageFrame);
}
catch (Exception ex){ ... }
}
private async void EvaluateAsync(VideoFrame imageFrame)
{
//将VideoFrame包装进BearClassificationModelInput中,交给模型识别
//模型的输出格式为BearClassificationModelOutput
//其中包含一个列表,存储了每种熊的标签名称,按照probability降序排列
//和一个字典,存储了每种熊的标签,和对应的probability
//这里取出输出中的字典,并对其进行降序排列
var result = await model.EvaluateAsync(new BearClassificationModelInput() { data = imageFrame });
var resultDescend = result.loss.OrderByDescending(p => p.Value).ToDictionary(p => p.Key, o => o.Value).ToList();
//根据结果生成图片描述
Description = DescribResult(resultDescend.First().Key, resultDescend.First().Value);
Results.Clear();
foreach (KeyValuePair kvp in resultDescend)
{
Results.Add(resourceLoader.GetString(kvp.Key) + " : " + kvp.Value.ToString("0.000"));
}
}
五、使用其他方法构建应用
同样,用之前使用Visual Studio Tools for AI提供的推理类库生成器也能够构建相似的应用。想看视频教程的请移步:
【教程】普通程序员一小时入门AI应用——看图识熊(不含公式,包会)
该教程讲解了如何使用模型浏览工具Netron
想看图文教程请继续往下看:
1. 界面设计
创建Windows窗体应用(.NET Framework)项目,这里给项目起名ClassifyBear。
注意,项目路径不要包含中文。
在解决方案资源管理器中找到Form1.cs,双击,打开界面设计器。从工具箱中向Form中依次拖入控件并调整,最终效果如下图所示:
左侧从上下到依次是:
- Label控件,将内容改为“输入要识别的图片地址:”
- TextBox控件,可以将控件拉长一些,方便输入URL
- Button控件,将内容改为“识别”
- Lable控件,将label的内容清空,用来显示识别后的结果。因为label也没有边框,所以在界面看不出来。可以将此控件的字体调大一些,能更清楚的显示推理结果。
右侧的控件是一个PictureBox,用来预览输入的图片,同时,我们也从这个控件中取出对应的图片数据,传给我们的模型推理类库去推理。建议将控件属性的SizeMode更改为StretchImage,并将控件长和宽设置为同样的值,保持一个正方形的形状,这样可以方便我们直观的了解模型的输入,因为在前面查看模型信息的时候也看到了,该模型的输入图片应是正方形。
2. 查看模型信息
在将模型集成到应用之前,我们先来看一看模型的基本信息,比如模型需要什么样的输入和输出。打开Visual Studio中的AI工具菜单,选择模型工具下的查看模型,会启动Netron模型查看工具。该工具默认不随Tools for AI扩展一起安装,第一次使用时可以按照提示去下载并安装。
Netron打开后,点击Open model选择打开之前下载的BearModel.onnx文件。然后点击左上角的汉堡菜单显示模型的输入输出。
上图中可以看到该模型需要的输入data是一个float数组,数组中要求依次放置227*227图片的所有蓝色分量、绿色分量和红色分量,后面程序中调用时要对输入图片做相应的处理。
上图中还可以看到输出有两个值,第一个值loss包含所有分类的得分,第二个值classLabel是确定的分类的标签,这里只需用到第二个输出即可。
3. 封装模型推理类库
由于目前模型推理用到的库只支持x64,所以这里需要将解决方案平台设置为x64。打开解决方案资源管理器,在解决方案上点右键,选择配置管理器。
在配置管理器对话框中,点开活动解决方案平台下拉框,选择新建
在新建解决方案平台对话框中,输入新平台名x64,点击确定即可
下面添加模型推理类库,再次打开解决方案资源管理器,在解决方案上点右键,选择添加,然后选择新建项目。
添加新项目对话框中,将左侧目录树切换到AI Tools下的Inference,右侧选择模型推理类库,下方填入项目名称,这里用Model作为名称。
确定以后会出现检查环境的进度条,耐心等待一会就可以出现模型推理类库创建向导对话框。
点击模型路径后面的浏览按钮,选择前面下载的BearModel.onnx模型文件。
注意,这里会出现几处错误提示,我们需要手动修复一下。首先会看到“发现不支持的张量的数据类型”提示,可以直接点确定。
确定后如果弹出“正在创建项目…”的进度条,一直不消失,这里只需要在类名后面的输入框内点一下,切换下焦点即可。
然后,我们来手动配置一下模型的相关信息。类名输入框中填入模型推理类的名字,这里用Bear。然后点击推理接口右侧的添加按钮,在弹出的编辑接口对话框中,随便起个方法名,这里用Infer。输入节点的变量名和张量名填入data,输出节点的变量名和张量名填入classLabel,字母拼写要和之前查看模型时看到的拼写一模一样。然后一路确定,再耐心等待一会,就可以在解决方案资源管理器看到新建的模型推理类库了。
还有一处错误需要手动修复一下,切换到解决方案资源管理器,在Model项目的Bear目录下找到Bear.cs双击打开,将函数Infer的最后一行
return r0;
替换为
List> results = new List>();
results.Add(r0);
return results;
至此,模型推理类库封装完成。相信Tools for AI将来的版本中会修复这些问题,直接选择模型文件创建模型推理类库就可以了。
4. 使用模型推理类库
首先添加对模型推理类库的引用,切换到解决方案资源管理器,在ClassifyBear项目的引用上点右键,选择添加引用。
在弹出的引用管理器对话框中,选择项目、解决方案,右侧可以看到刚刚创建的模型推理类库,勾选该项目,点击确定即可。
在Form1.cs上点右键,选择查看代码,打开Form1.cs的代码编辑窗口。
添加两个成员变量
// 使用Netron查看模型,得到模型的输入应为227*227大小的图片
private const int imageSize = 227;
// 模型推理类
private Model.Bear model;
回到Form1的设计界面,双击Form的标题栏,会自动跳转到代码页面并添加了Form1_Load方法,在其中初始化模型推理对象
private void Form1_Load(object sender, EventArgs e)
{
// 初始化模型推理对象
model = new Model.Bear();
}
回到Form1的设计界面,双击识别按钮,会自动跳转到代码页面并添加了button1_Click方法,在其中添加以下代码:
首先,每次点击识别按钮时都先将界面上显示的上一次的结果清除
// 识别之前先重置界面显示的内容
label1.Text = string.Empty;
pictureBox1.Image = null;
pictureBox1.Refresh();
然后,让图片控件加载图片
bool isSuccess = false;
try
{
pictureBox1.Load(textBox1.Text);
isSuccess = true;
}
catch (Exception ex)
{
MessageBox.Show($"读取图片时出现错误:{ex.Message}");
throw;
}
如果加载成功,将图片数据传给模型推理类库来推理。
if (isSuccess)
{
// 图片加载成功后,从图片控件中取出227*227的位图对象
Bitmap bitmap = new Bitmap(pictureBox1.Image, imageSize, imageSize);
float[] imageArray = new float[imageSize * imageSize * 3];
// 按照先行后列的方式依次取出图片的每个像素值
for (int y = 0; y < imageSize; y++)
{
for (int x = 0; x < imageSize; x++)
{
var color = bitmap.GetPixel(x, y);
// 使用Netron查看模型的输入发现
// 需要依次放置227 *227的蓝色分量、227*227的绿色分量、227*227的红色分量
imageArray[y * imageSize + x] = color.B;
imageArray[y * imageSize + x + 1* imageSize * imageSize] = color.G;
imageArray[y * imageSize + x + 2* imageSize * imageSize] = color.R;
}
}
// 模型推理类库支持一次推理多张图片,这里只使用一张图片
var inputImages = new List();
inputImages.Add(imageArray);
// 推理结果的第一个First()是取第一张图片的结果
// 之前定义的输出只有classLabel,所以第二个First()就是分类的名字
label1.Text = model.Infer(inputImages).First().First();
}
注意,这里的数据转换一定要按照前面查看的模型的信息来转换,图片大小需要长宽都是227像素,并且要依次放置所有的蓝色分量、所有的绿色分量、所有的红色分量,如果顺序不正确,不能达到最佳的推理结果。
5. 测试
编译运行,然后在网上找一张熊的图片,把地址填到输入框内,然后点击识别按钮,就可以看到识别的结果了。注意,这个URL应该是图片的URL,而不是包含该图片的网页的URL。
六、下一步?
本篇博客我们学会了使用定制化视觉服务与在UWP应用中集成定制化视觉服务模型。这里我提两个课后习题:(想不到吧)
当训练含有多个标签、大量图片数据时,如何做到一键上传图片并训练?
如何通过调用REST接口的方式完成对图片的推理?
提示:请看看定制化视觉服务给我们提供的API,这一题肯定是要写代码做的
https://docs.microsoft.com/en-us/azure/cognitive-services/custom-vision-service/home
加油!
七、内容预告
接下来我们将会陆续推出:
- 微软认知服务使用教程
- 模型训练及推理的通常流程及原理
- 模型转换工具的使用
- 开放AI平台-大规模计算资源调度系统
请在下方留言,告知我们您最想阅读哪个教程,我们将优先考虑。
如果您有别的想要了解的内容,也可以在评论区留言。