Clasificación de imágenes en aplicaciones web con ML.NET
Tags: webdev, machinelearning, dotnet, csharp
Published: 2022-03-08 13:38:51

¡Hola! Un gusto saludarte. En este articulo tutorial aprenderemos paso a paso a generar un modelo de Machine Learning personalizado desde ML.NET para la clasificación de imágenes, particularmente para clasificar imágenes de personas con y sin mascarillas, para luego aprender a consumir ese modelo desde una aplicación web sobre ASP.NET con el patrón MVVM (Modelo, Vista, VistaModelo). 

###**¿Qué es ML.NET y cómo funciona?**

ML.NET es una plataforma de Machine Learning open source que nos permite transformar datos de entrada en un modelo ML, en este caso imágenes, para luego poder integrarlo en cualquier tipo de aplicación sobre el ecosistema de .NET, ya sean aplicaciones web, móviles, de escritorio, o cualquier otro tipo de aplicación.  
En nuestro caso de estudio nos enfocaremos en la categorización de imágenes. 

###**Clasificación de imágenes**

Para nuestro ejemplo, la intención es poder clasificar imágenes de personas en dos grupos: personas con mascarillas y personas sin mascarillas. 

Para lograr este objetivo, realizaremos tres actividades en este articulo: 

+ 1. Cumplir con los requisitos necesarios y preparar nuestra solución en Visual Studio. 
+ 2. Crear nuestro modelo de Machine Learning con ML.NET. 
+ 3. Consumir el modelo generado desde ASP.NET. 

Con esto mencionado, ¡comencemos! 

###**Parte 1: Requisitos previos y preparación de la solución en Visual Studio**

Para poder generar nuestro modelo de Machine Learning, utilizaremos el intérprete Model Builder de ML.NET. Para esto, es necesario que contemos con la siguiente carga de trabajo en Visual Studio (Desarrollo multiplataforma de .NET): 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/heve2cylbynrxe50bh5l.png)

Con esta carga de trabajo, ahora podemos crear nuestra solución. En este caso, la intención es consumir el modelo ML que vayamos a generar desde una aplicación web, para este propósito utilizaremos el framework DotVVM que nos permite trabajar con páginas HTML y clases C# sobre ASP.NET, y con el patrón MVVM (Modelo, Vista, VistaModelo).

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f3lkgg8xbxqf3lfazr9.png)

###**Parte 2: Construcción del modelo de Machine Learning**

Es momento de crear el modelo con ML.NET. Como en cualquier otro proceso en el ámbito del Machine Learning para generar un modelo, los pasos para construir el modelo de análisis sentimientos son los siguientes: 

+ Establecer el escenario ML.
+ Preparar el entorno para el entrenamiento de los datos. 
+ Cargar el dataset. 
+ Entrenar el modelo.
+ Evaluar el modelo. 
+ Consumir el modelo. 

Para empezar, en el proyecto principal de nuestra solución agregaremos un elemento de tipo Machine Learning:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3ttwyyjpw4pheb2xzxe.png)

Con esta opción, el intérprete de ML.NET Model Builder se inicializará y podremos comenzar a personalizar el modelo. 

**Escenario:**
El primer paso es seleccionar el escenario con el que deseamos trabajar, en este caso será `Image classification`:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/js6ltb6xlxccfcsbvjkd.png)

**Ambiente:**

Ahora, debemos seleccionar los recursos de cómputo para el entrenamiento de nuestro modelo. En este escenario tenemos tres opciones disponibles, ya sea utilizar el CPU de nuestra computadora, una tarjeta gráfica para aprovechar la GPU (ya que trabajaremos con imágenes), o realizar el entrenamiento en la nube con Azure Machine Learning. En este ejemplo utilizaremos el CPU local: 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hokkvsm5spkipcl6sgru.png)

**Fuente de datos:** 

Esta es una de las partes más importantes, ya que con los datos podremos personalizar nuestro modelo. Para este propósito, debemos especificar el directorio donde se encuentran los archivos de las imágenes que queremos clasificar. En este sentido, es importante que exista una carpeta por cada categoría que vayamos a clasificar, en este caso podemos encontrar la subcarpeta *“Con mascarilla”*, y otra llamada *“Sin mascarilla”*. Al seleccionar el directorio desde el ML.NET Model Builder podremos visualizar algo como esto: 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8c1ag8yipfg43082prcy.png)

**Entrenamiento:**

Con todo lo que ya hemos establecido hasta este momento, ya podemos realizar el proceso de entrenamiento, y generar finalmente el modelo deseado. En esta sección únicamente podremos dar inicio con el entrenamiento, y esperar a que este finalice. El tiempo dependerá de la cantidad de imágenes del dataset, las características de nuestro CPU (método seleccionado previamente), y el tamaño de los archivos correspondientes. 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5rm6l2uxrlevdkjp7yp.png)

**Evaluación:** 

Hasta este punto ya tenemos el modelo establecido, de todas maneras, desde el Model Builder de ML.NET podemos evaluar el modelo con un ejemplo en particular. 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rww0zza4nikqe2ngrcv0.png)

**Consumo:**

Finalmente, el intérprete nos brindará opciones para consumir el modelo creado, ya sea creando otros proyectos (por consola o con una web api), o indicándonos las llamadas que debemos hacer desde nuestra propia aplicación. 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvmcm2ca3q09eih5q35q.png)

En este caso, ya podemos consumir el modelo desde nuestra aplicación web con DotVVM sobre ASP.NET con el nuevo paquete generado: `MLModel.mbconfig`. 

###**Parte 2: Consumir el modelo generado desde ASP.NET**

Para consumir el modelo debemos hacer referencia a la clase MLModel, la cual fue generada por ML.NET en el grupo `MLModel.mbconfig`:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pw8hs12ippkno8m1z581.png)

En este sentido, es necesario establecer una instancia de ModelInput con la referencia de la imagen que queremos clasificar, para luego llamar al método `Predict`, y obtener un objeto de tipo ModelOutput con la clasificación correspondiente (*With mask*, *Without mask*), y su probabilidad. 

**ViewModel**

Para este propósito, y para poder establecer una página web con DotVVM, en un ViewModel, en este caso el `DefaultViewModel.cs`, definiremos cuatro atributos o variables globales:

```csharp
private IUploadedFileStorage storage;
public UploadedFilesCollection Files { get; set; }

public string Result { get; set; } = null;
public decimal? Score { get; set; } = null;
```

Estas variables nos permitirán hacer referencia al archivo de la imagen en un storage, y representar los resultados de la predicción en los atributos `Result` y `Score`.

Luego podemos establecer un método en esta clase, de tal manera que represente la acción de un botón para realizar la predicción:

```csharp
public void Predict()
{
	var uploadPath = GetUploadPath();
	var targetPath = Path.Combine(uploadPath, Files.Files[0].FileId + ".bin");
	storage.SaveAsAsync(Files.Files[0].FileId, targetPath);

	var sampleData = new MLModel.ModelInput()
	{
		ImageSource = targetPath,
	};

	//Load model and predict output
	var output = MLModel.Predict(sampleData);
	Result = output.Prediction;

	if (Result.Equals("With mask"))
	{
		Score = decimal.Round((decimal)(output.Score[0]) * 100, 2);
	}
	else
	{
		Score = decimal.Round((decimal)(output.Score[1]) * 100, 2);
	}

	//storage.DeleteFileAsync(Files.Files[0].FileId);
	Files.Clear();
}
```

En este código tenemos tres partes importantes:

+ Almacenar el archivo de la imagen en un storage local, de tal manera que podamos tener la ruta de este archivo para el modelo.
+ Inicializar un `MLModel.ModelInput` con la ruta de la imagen. 
+ Llamar al `MLModel.Predict` con el `ModelInput` como parámetro, obteniendo así el `ModelOutput` con los resultados.
+ Preparar el `Result` para visualizar el resultado de la clasificación posteriormente, y su exactitud en la predicción. 

**View**

Muy bien, ahora podemos construir la vista al hacer referencia a los atributos y al método creado en el ViewModel. Esta vista la codificaremos en el archivo `Default.dothtml` de la siguiente manera:

```html
<table style="border: hidden">
    <tbody style="border: hidden">
        <tr style="border: hidden">
            <td style="border: hidden" align="center">
                <dot:FileUpload UploadedFiles="{value: Files}"
                                AllowMultipleFiles="false"
                                SuccessMessageText="Predicting..."
                                NumberOfFilesIndicatorText=""
                                UploadButtonText="Search Image"
                                AllowedFileTypes="jpg,png"
                                UploadCompleted="{command: Predict()}" />
            </td>
            <td style="border: hidden" align="center">
                <b>{{value: Result}}</b> {{value: Score}}
            </td>
        </tr>
    </tbody>
</table>
```

Aquí lo más importante es el control `FileUpload` para que el usuario pueda cargar el archivo de una imagen desde su computadora, para luego realizar la llamada al método `Predict()` con un `Button`, y establecer la visualización de los resultados al mostrar los valores de las variables `Result`, y `Score`. 

**Ejemplos en ejecución**
 
Con esta View, y su ViewModel, nuestra página web se encuentra lista. Aquí podemos ver dos ejemplos en tiempo de ejecución: 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gj882ysngobv5nzgfuh.png)

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1wqs9us45a7bm8nyp8uj.png)

El código fuente del ejemplo se puede encontrar en el siguiente repositorio: [github.com/esdanielgomez/MLNET_ImageClassification](https://github.com/esdanielgomez/MLNET_ImageClassification).

##**¡Gracias!**

Muchas gracias por leer, espero que este demo pueda serte de utilidad. Si tienes alguna pregunta o alguna idea que necesites discutir, será un gusto poder colaborarte y juntos intercambiar conocimientos entre sí.

¡Nos vemos en [Twitter](https://twitter.com/esDanielGomez) / [esdanielgomez.com](https://esdanielgomez.com)! :)