ONNX Runtime을 사용하여 당신의 WinUI 앱에서 ONNX 모델을 시작하세요.
이 문서에서는 ONNX 모델을 사용하여 이미지의 개체를 분류하고 각 분류의 신뢰도를 표시하는 WinUI 3 앱을 만드는 방법을 안내합니다. AI 및 기계 학습 모델을 Windows 앱에서 사용하는 방법에 대한 자세한 내용은 Windows 앱에서 AI 및 기계 학습 모델 사용을 시작하기를 참조하세요.
AI 기능을 활용할 때는 Windows에서 '책임 있는 생성 AI 애플리케이션 및 기능 개발
ONNX 런타임이란?
ONNX Runtime 하드웨어 관련 라이브러리를 통합하는 유연한 인터페이스를 갖춘 플랫폼 간 기계 학습 모델 가속기입니다. ONNX Runtime PyTorch, Tensorflow/Keras, TFLite, scikit-learn및 기타 프레임워크의 모델과 함께 사용할 수 있습니다. 자세한 내용은 ONNX Runtime 웹 사이트 https://onnxruntime.ai/docs/을 참조하세요.
이 샘플에서는 Windows 디바이스의 다양한 하드웨어 옵션을 추상화하고 실행하는 DirectML Execution Provider 사용하며 GPU 및 NPU와 같은 로컬 가속기에서 실행을 지원합니다.
필수 구성 요소
- 디바이스에 개발자 모드가 설정되어 있어야 합니다. 자세한 내용은 개발 디바이스사용을 참조하세요.
- .NET 데스크톱 개발 워크로드가 있는 Visual Studio 2022 이상.
새 C# WinUI 앱 만들기
Visual Studio에서 새 프로젝트를 만듭니다. 새 프로젝트 만들기 대화 상자에서 언어 필터를 "C#"으로 설정하고 프로젝트 형식 필터를 "winui"로 설정한 다음, 비어 있는 앱인 Packaged(데스크톱의 WinUI3) 템플릿을 선택합니다. 새 프로젝트의 이름을 "ONNXWinUIExample"로 지정합니다.
Nuget 패키지에 대한 참조 추가
패키지 | 설명 |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | GPU에서 ONNX 모델을 실행하기 위한 API를 제공합니다. |
SixLabors.ImageSharp | 모델 입력을 위해 이미지를 처리하기 위한 이미지 유틸리티를 제공합니다. |
SharpDX.DXGI | C#에서 DirectX 디바이스에 액세스하기 위한 API를 제공합니다. |
지시문을 사용하여
// MainWindow.xaml.cs
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using SharpDX.DXGI;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
프로젝트에 모델 추가
솔루션 탐색기에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가 ->새 폴더를 선택합니다. 새 폴더 이름을 "model"으로 지정합니다. 이 예제에서는 의 https://github.com/onnx/models 모델을 사용합니다. https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx모델의 리포지토리 보기로 이동합니다. *원시 파일 다운로드 단추를 클릭합니다. 이 파일을 방금 만든 "모델" 디렉터리에 복사합니다.
솔루션 탐색기에서 모델 파일을 클릭하고 출력 디렉터리 복사를 "최신인 경우 복사"로 설정합니다.
간단한 UI 만들기
이 예제에서는 사용자가 모델을 사용하여 평가할 이미지를 선택할 수 있도록 단추 포함하는 간단한 UI를 만들고, 선택한 이미지를 표시하는 이미지 컨트롤, 이미지에서 검색된 개체와 각 개체 분류의 신뢰도를 나열하는 TextBlock 만듭니다.
MainWindow.xaml
파일에서 기본 StackPanel 요소를 다음 XAML 코드로 바꿉니다.
<!--MainWindow.xaml-->
<Grid Padding="25" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="myButton" Click="myButton_Click" Grid.Column="0" VerticalAlignment="Top">Select photo</Button>
<Image x:Name="myImage" MaxWidth="300" Grid.Column="1" VerticalAlignment="Top"/>
<TextBlock x:Name="featuresTextBlock" Grid.Column="2" VerticalAlignment="Top"/>
</Grid>
모델 초기화
MainWindow.xaml.cs
파일의 MainWindow 클래스 내에서 모델을 초기화하는 InitModel 도우미 메서드를 만듭니다. 이 메서드는 SharpDX.DXGI 라이브러리의 API를 사용하여 사용 가능한 첫 번째 어댑터를 선택합니다. 선택한 어댑터는 이 세션의 DirectML 실행 공급자에 대한 SessionOptions 개체에 설정됩니다. 마지막으로 모델 파일의 경로와 세션 옵션을 전달하여 새 InferenceSession 초기화됩니다.
// MainWindow.xaml.cs
private InferenceSession _inferenceSession;
private string modelDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "model");
private void InitModel()
{
if (_inferenceSession != null)
{
return;
}
// Select a graphics device
var factory1 = new Factory1();
int deviceId = 0;
Adapter1 selectedAdapter = factory1.GetAdapter1(0);
// Create the inference session
var sessionOptions = new SessionOptions
{
LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO
};
sessionOptions.AppendExecutionProvider_DML(deviceId);
_inferenceSession = new InferenceSession($@"{modelDir}\resnet50-v2-7.onnx", sessionOptions);
}
이미지 로드 및 분석
간단히 하기 위해 이 예제에서는 이미지 로드 및 서식 지정, 모델 호출 및 결과 표시를 위한 모든 단계가 단추 클릭 처리기 내에 배치됩니다. 참고로, 처리기에서 비동기 작업을 실행할 수 있도록 기본 템플릿에 포함된 버튼 클릭 처리기에 async 키워드를 추가합니다.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
FileOpenPicker 사용하여 사용자가 컴퓨터에서 이미지를 선택하여 분석하고 UI에 표시할 수 있습니다.
FileOpenPicker fileOpenPicker = new()
{
ViewMode = PickerViewMode.Thumbnail,
FileTypeFilter = { ".jpg", ".jpeg", ".png", ".gif" },
};
InitializeWithWindow.Initialize(fileOpenPicker, WinRT.Interop.WindowNative.GetWindowHandle(this));
StorageFile file = await fileOpenPicker.PickSingleFileAsync();
if (file == null)
{
return;
}
// Display the image in the UI
var bitmap = new BitmapImage();
bitmap.SetSource(await file.OpenAsync(Windows.Storage.FileAccessMode.Read));
myImage.Source = bitmap;
다음으로 입력을 처리하여 모델에서 지원하는 형식으로 가져와야 합니다. SixLabors.ImageSharp 라이브러리는 이미지를 24비트 RGB 형식으로 로드하고 이미지 크기를 224x224 픽셀로 조정하는 데 사용됩니다. 그런 다음 픽셀 값이 정규화되고 평균이 255*[0.485, 0.456, 0.406]이고 표준 편차가 255*[0.229, 0.224, 0.225]로 정규화됩니다. 모델이 예상하는 형식의 세부 정보는 github 페이지에서 ResNet 모델을 찾을 수 있습니다.
using var fileStream = await file.OpenStreamForReadAsync();
IImageFormat format = SixLabors.ImageSharp.Image.DetectFormat(fileStream);
using Image<Rgb24> image = SixLabors.ImageSharp.Image.Load<Rgb24>(fileStream);
// Resize image
using Stream imageStream = new MemoryStream();
image.Mutate(x =>
{
x.Resize(new ResizeOptions
{
Size = new SixLabors.ImageSharp.Size(224, 224),
Mode = ResizeMode.Crop
});
});
image.Save(imageStream, format);
// Preprocess image
// We use DenseTensor for multi-dimensional access to populate the image data
var mean = new[] { 0.485f, 0.456f, 0.406f };
var stddev = new[] { 0.229f, 0.224f, 0.225f };
DenseTensor<float> processedImage = new(new[] { 1, 3, 224, 224 });
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgb24> pixelSpan = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
processedImage[0, 0, y, x] = ((pixelSpan[x].R / 255f) - mean[0]) / stddev[0];
processedImage[0, 1, y, x] = ((pixelSpan[x].G / 255f) - mean[1]) / stddev[1];
processedImage[0, 2, y, x] = ((pixelSpan[x].B / 255f) - mean[2]) / stddev[2];
}
}
});
다음으로, 관리되는 이미지 데이터 배열을 기반으로 한 Tensor 유형의 OrtValue를 생성하여 입력을 설정합니다.
// Setup inputs
// Pin tensor buffer and create a OrtValue with native tensor that makes use of
// DenseTensor buffer directly. This avoids extra data copy within OnnxRuntime.
// It will be unpinned on ortValue disposal
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
processedImage.Buffer, new long[] { 1, 3, 224, 224 });
var inputs = new Dictionary<string, OrtValue>
{
{ "data", inputOrtValue }
};
다음으로, 유추 세션이 아직 초기화되지 않은 경우 InitModel 도우미 메서드를 호출합니다. 그런 다음 Run 메서드를 호출하여 모델을 실행하고 결과를 검색합니다.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
모델은 결과를 네이티브 텐서 버퍼로 출력합니다. 다음 코드는 출력을 부동 소수 자릿수 배열로 변환합니다. 값이 [0,1] 범위에 있고 합계가 1이 되도록 softmax 함수가 적용됩니다.
// Postprocess output
// We copy results to array only to apply algorithms, otherwise data can be accessed directly
// from the native buffer via ReadOnlySpan<T> or Span<T>
var output = results[0].GetTensorDataAsSpan<float>().ToArray();
float sum = output.Sum(x => (float)Math.Exp(x));
IEnumerable<float> softmax = output.Select(x => (float)Math.Exp(x) / sum);
출력 배열의 각 값 인덱스는 모델이 학습된 레이블에 매핑되고, 해당 인덱스의 값은 레이블이 입력 이미지에서 검색된 개체를 나타낸다는 모델의 신뢰도입니다. 신뢰도 값이 가장 높은 10개 결과를 선택합니다. 이 코드는 다음 단계에서 정의할 몇 가지 도우미 개체를 사용합니다.
// Extract top 10
IEnumerable<Prediction> top10 = softmax.Select((x, i) => new Prediction { Label = LabelMap.Labels[i], Confidence = x })
.OrderByDescending(x => x.Confidence)
.Take(10);
// Print results
featuresTextBlock.Text = "Top 10 predictions for ResNet50 v2...\n";
featuresTextBlock.Text += "-------------------------------------\n";
foreach (var t in top10)
{
featuresTextBlock.Text += $"Label: {t.Label}, Confidence: {t.Confidence}\n";
}
} // End of myButton_Click
도우미 개체 선언
Prediction 클래스는 개체 레이블을 신뢰도 값과 연결하는 간단한 방법을 제공합니다.
MainPage.xaml.cs
ONNXWinUIExample 네임스페이스 블록 내에 이 클래스를 삽입하고, MainWindow 클래스 정의 외부에 추가합니다.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
그런 다음 모델이 학습된 모든 개체 레이블을 나열하는 LabelMap 도우미 클래스를 특정 순서로 추가하여 레이블이 모델에서 반환된 결과의 인덱스에 매핑되도록 합니다. 레이블 목록이 너무 길어 여기에서 전체 목록을 표시할 수 없습니다. ONNXRuntime github 리포지토리 샘플 코드 파일에서 전체 LabelMap 클래스를 복사하여 ONNXWinUIExample 네임스페이스 블록에 붙여넣을 수 있습니다.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
예제 실행
프로젝트를 빌드하고 실행합니다. 사진 선택 단추를 클릭하고 분석할 이미지 파일을 선택합니다. LabelMap 도우미 클래스 정의를 확인하여 모델이 인식할 수 있는 항목을 확인하고 흥미로운 결과가 있을 수 있는 이미지를 선택할 수 있습니다. 모델이 초기화되고 처음 실행되고 모델 처리가 완료된 후 이미지에서 검색된 개체 목록과 각 예측의 신뢰도 값이 표시됩니다.
Top 10 predictions for ResNet50 v2...
-------------------------------------
Label: lakeshore, Confidence: 0.91674984
Label: seashore, Confidence: 0.033412453
Label: promontory, Confidence: 0.008877817
Label: shoal, Confidence: 0.0046836217
Label: container ship, Confidence: 0.001940886
Label: Lakeland Terrier, Confidence: 0.0016400366
Label: maze, Confidence: 0.0012478716
Label: breakwater, Confidence: 0.0012336193
Label: ocean liner, Confidence: 0.0011933135
Label: pier, Confidence: 0.0011284945