Crear una Windows 10 UWP App y utilizar YoloV2 para reconocer objetos
- Introduccion a YoloV2 como modelo de reconocimiento de objetos
- Como crear una UWP App y analizar los frames de la webcam utilizando YoloV2
- Convirtiendo el resultado de YoloV2 a clases C# y mostrarlas en modo Frames
- Redimensionar el output para trabajar con diferentes resoluciones y mostrar los Frames procesados por segundo con YoloV2
Buenas!
Hoy empiezo por el resultado final ya mucha parte del post será código y mas código. El output esperado en una Windows 10 App de reconocimiento de objetos con YoloV2 es similar a la siguiente imagen
Siguiendo con los pasos de mi post anterior, nos encontrábamos con el resultado del proceso de nuestra WebCam con YoloV2 y que es un array de 21125 números Float. Pues bien, este numero no es trivial, ya que releyendo la documentación de YoloV2 vemos que YOLO divide la imagen en una cuadrícula de 13 por 13 celdas:
Cada una de estas celdas es responsable de predecir 5 cuadros delimitadores. Un cuadro delimitador describe el rectángulo que encierra un objeto. Y de aquí el numero
13 * 13 * 125 = 21125
Hay muchos posts que describen como funciona Yolo internamente, he dejado algunos en las referencias por si alguien esta interesado en los detalles.
Pues bien, en este escenario el siguiente paso era comenzar a traducir ese Grid[21125] en objetos C# con los que trabajar. Como internet es una fuente de conocimiento muy amplia, en lugar de traducir alguna de las clases Python que ya existen, vi que Rene Schulte tenía entre sus repositorios de GitHub un fork de otro repo donde se podían ver las siguientes clases
- YoloWinMLParser.cs
- Esta clase es un parser para convertir la Grid en una colección de frames / marcos con el tamaño y coordenadas de los objetos detectados en la imagen.
- YoloBoundingBox.cs
- Esta clase representa el marco / frame de un objeto encontrado.
Para mostrar estos frames, agregamos un Canvas sobre el control que nos muestra el feed de la cámara.
El siguiente código completa el ejemplo, con las siguientes consideraciones
- Hay una serie de variables privadas para trabajar con el modelo, la colección de frames y los estilos visuales con los que se pintaran los mismos.
- Los Frames de personas se pintan en verde, los demás objetos en amarillo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using Windows.Media; | |
using Windows.UI.Core; | |
using Windows.UI.Text; | |
using Windows.UI.Xaml; | |
using Windows.UI.Xaml.Controls; | |
using Windows.UI.Xaml.Media; | |
using Windows.UI.Xaml.Navigation; | |
using UwpAppYolo01.Yolo9000; | |
namespace UwpAppYolo01 | |
{ | |
public sealed partial class MainPage : Page | |
{ | |
private TinyYoloV2Model _model; | |
private IList<YoloBoundingBox> _boxes = new List<YoloBoundingBox>(); | |
private readonly YoloWinMlParser _parser = new YoloWinMlParser(); | |
private readonly SolidColorBrush _lineBrushYellow = new SolidColorBrush(Windows.UI.Colors.Yellow); | |
private readonly SolidColorBrush _lineBrushGreen = new SolidColorBrush(Windows.UI.Colors.Green); | |
private readonly SolidColorBrush _fillBrush = new SolidColorBrush(Windows.UI.Colors.Transparent); | |
private readonly double _lineThickness = 2.0; | |
public MainPage() | |
{ | |
InitializeComponent(); | |
} | |
protected override async void OnNavigatedTo(NavigationEventArgs e) | |
{ | |
LoadYoloOnnxModel(); | |
await CameraPreview.StartAsync(); | |
CameraPreview.CameraHelper.FrameArrived += CameraHelper_FrameArrived; | |
} | |
private async void LoadYoloOnnxModel() | |
{ | |
var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Tiny-YOLOv2.onnx")); | |
_model = await TinyYoloV2Model.CreateTinyYoloV2Model(file); //, | |
} | |
private async void CameraHelper_FrameArrived(object sender, Microsoft.Toolkit.Uwp.Helpers.FrameEventArgs e) | |
{ | |
if (e?.VideoFrame?.SoftwareBitmap == null) return; | |
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => | |
{ | |
var input = new TinyYoloV2ModelInput { Image = e.VideoFrame }; | |
var output = await _model.EvaluateAsync(input); | |
_boxes = _parser.ParseOutputs(output.Grid.ToArray()); | |
DrawOverlays(e.VideoFrame); | |
}); | |
} | |
private void DrawOverlays(VideoFrame inputImage) | |
{ | |
YoloCanvas.Children.Clear(); | |
if (_boxes.Count <= 0) return; | |
var filteredBoxes = _parser.NonMaxSuppress(_boxes, 5, .5F); | |
foreach (var box in filteredBoxes) | |
DrawYoloBoundingBox(box, YoloCanvas); | |
} | |
private void DrawYoloBoundingBox(YoloBoundingBox box, Canvas overlayCanvas) | |
{ | |
var x = (uint)Math.Max(box.X, 0); | |
var y = (uint)Math.Max(box.Y, 0); | |
var w = (uint)Math.Min(overlayCanvas.ActualWidth – x, box.Width); | |
var h = (uint)Math.Min(overlayCanvas.ActualHeight – y, box.Height); | |
var rectStroke = box.Label == "person" ? _lineBrushGreen : _lineBrushYellow; | |
var r = new Windows.UI.Xaml.Shapes.Rectangle | |
{ | |
Tag = box, | |
Width = w, | |
Height = h, | |
Fill = _fillBrush, | |
Stroke = rectStroke, | |
StrokeThickness = _lineThickness, | |
Margin = new Thickness(x, y, 0, 0) | |
}; | |
var tb = new TextBlock | |
{ | |
Margin = new Thickness(x + 4, y + 4, 0, 0), | |
Text = $"{box.Label} ({Math.Round(box.Confidence, 4)})", | |
FontWeight = FontWeights.Bold, | |
Width = 126, | |
Height = 21, | |
HorizontalTextAlignment = TextAlignment.Center | |
}; | |
var textBack = new Windows.UI.Xaml.Shapes.Rectangle | |
{ | |
Width = 134, | |
Height = 29, | |
Fill = rectStroke, | |
Margin = new Thickness(x, y, 0, 0) | |
}; | |
overlayCanvas.Children.Add(textBack); | |
overlayCanvas.Children.Add(tb); | |
overlayCanvas.Children.Add(r); | |
} | |
} | |
} |
El único detalle que queda pendiente de comentar es que YoloV2 esta pensado para trabajar con imágenes de tamaño 416 x 416. En este caso hay que redimensionar el control de WebCam y el Canvas a ese tamanio para que los frames se muestren en la posición correcta.
En el siguiente post compartiré el ejemplo final, y además agregare un poco de trabajo de reescalado para poder soportar otras definiciones diferentes a 416 x 416.
Happy Coding!
Saludos @ Toronto
El Bruno
References
- YOLO: Real-time object detection
- YOLO9000: Better, Faster, Stronger by Joseph Redmon and Ali Farhadi (2016)
- ONNX Tools
- Azure AI Gallery, Tiny YOLO V2
- El Bruno, Windows Community Toolkit V 3.0 makes life incredibly easy if you need working with the camera in a UWP App
- Visual Studio Marketplace, Visual Studio Tools for AI
- Real-time object detection with YOLO
- Rene Schulte GitHub
- Sevans4067 WinML-TinyYolo
5 comments