Skip to main content

ML.NET Image Classification: Train a Model in C#

Learn ML.NET image classification in C#. Step-by-step tutorial to train, evaluate, and deploy a custom image classifier in .NET. Start building today!

If you have ever wanted to add image recognition to a .NET app without leaving C#, ML.NET image classification is the fastest way to get there. In this tutorial you will train a custom image classifier in C#, evaluate its accuracy, and deploy the trained model into a production .NET application — all using free, open-source tooling from Microsoft. No Python, no external ML services, and no PhD in deep learning required.

ML.NET is Microsoft's cross-platform machine learning framework for .NET developers. It runs on Windows, macOS, and Linux, integrates directly with your existing C# codebase, and uses transfer learning under the hood so you can build an accurate model with only a few hundred images. By the end of this guide you will understand not just how to build an image classifier, but why each step matters.

What Is ML.NET Image Classification?

ML.NET image classification is the task of assigning a label (a class) to an input image — for example, deciding whether a photo shows a "cat", a "dog", or a "pizza". Under the hood, ML.NET uses a technique called transfer learning. Instead of training a deep neural network from scratch (which would need millions of images and expensive GPUs), it takes a pre-trained model such as ResNet or Inception that already knows how to "see" edges, textures, and shapes, and retrains only the final layers on your custom data.

This is a game-changer for C# machine learning because it means:

  • Small datasets work. You can get solid results with 100–200 images per class.
  • Training is fast. Minutes on a CPU, seconds on a GPU.
  • It stays in .NET. The model saves as a .zip file you load with C# — no interop headaches.

Prerequisites and Project Setup

To follow this ML.NET image classification tutorial, you will need the .NET 8 SDK (or later) and any editor — Visual Studio 2022, VS Code, or Rider. Start by creating a console project and adding the required NuGet packages.

// Create the project from the terminal
dotnet new console -n ImageClassifier
cd ImageClassifier

// Add the ML.NET packages needed for deep-learning image classification
dotnet add package Microsoft.ML
dotnet add package Microsoft.ML.Vision
dotnet add package Microsoft.ML.ImageAnalytics
dotnet add package SciSharp.TensorFlow.Redist

The Microsoft.ML.Vision package provides the ImageClassificationTrainer, and SciSharp.TensorFlow.Redist supplies the native TensorFlow backend that powers transfer learning. If you have a supported NVIDIA GPU, you can swap in the GPU redist package for dramatically faster training.

Organizing Your Training Data

ML.NET expects one folder per class, with images inside each folder. The folder name becomes the label. This convention keeps data loading trivial:

/assets
   /images
      /cat      -> 150 cat photos
      /dog      -> 150 dog photos
      /pizza    -> 150 pizza photos

Loading and Preparing the Image Data

First, define a class that represents a single input row. We only need the file path and the label — ML.NET reads the pixels for us during training.

using Microsoft.ML.Data;

public class ImageData
{
    [LoadColumn(0)]
    public string ImagePath { get; set; }

    [LoadColumn(1)]
    public string Label { get; set; }
}

public class ImagePrediction
{
    [ColumnName("PredictedLabel")]
    public string PredictedLabel { get; set; }

    public float[] Score { get; set; }
}

Next, scan the folders to build an in-memory list of ImageData. This helper walks each subdirectory and uses the folder name as the label — a clean, reusable pattern for any image dataset.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public static IEnumerable<ImageData> LoadImagesFromDirectory(string folder)
{
    var files = Directory.GetFiles(folder, "*",
        SearchOption.AllDirectories)
        .Where(f => Path.GetExtension(f).ToLower() is ".jpg" or ".jpeg" or ".png");

    foreach (var file in files)
    {
        // The parent folder name is the class label (e.g. "cat")
        var label = Directory.GetParent(file).Name;
        yield return new ImageData { ImagePath = file, Label = label };
    }
}

How to Train the Image Classification Model in C#

Now for the core of this C# machine learning tutorial: building and training the pipeline. The workflow is always the same in ML.NET — create an MLContext, load data into an IDataView, split it into training and test sets, then chain transforms into an EstimatorChain.

using Microsoft.ML;
using Microsoft.ML.Vision;

var mlContext = new MLContext(seed: 1);

// 1. Load images into an IDataView
string imagesFolder = Path.Combine(Environment.CurrentDirectory, "assets", "images");
IEnumerable<ImageData> images = LoadImagesFromDirectory(imagesFolder);
IDataView fullData = mlContext.Data.LoadFromEnumerable(images);

// 2. Shuffle so classes are not grouped together — critical for good training
IDataView shuffledData = mlContext.Data.ShuffleRows(fullData);

// 3. Split: 80% training, 20% testing
var split = mlContext.Data.TrainTestSplit(shuffledData, testFraction: 0.2);

Why shuffle? If cat images all appear first and pizza images last, a mini-batch during training could see only one class at a time, which destabilizes learning. Shuffling ensures each batch is representative of the whole dataset.

Now define the pipeline. Two steps matter: mapping the text label to a numeric key (models work with numbers, not strings), then applying the ImageClassificationTrainer.

// Convert string labels ("cat") to numeric keys the trainer understands
var pipeline = mlContext.Transforms.Conversion.MapValueToKey(
        inputColumnName: "Label",
        outputColumnName: "LabelKey")
    .Append(mlContext.Transforms.LoadRawImageBytes(
        outputColumnName: "Image",
        imageFolder: null,
        inputColumnName: "ImagePath"))
    .Append(mlContext.MulticlassClassification.Trainers.ImageClassification(
        new ImageClassificationTrainer.Options
        {
            FeatureColumnName = "Image",
            LabelColumnName = "LabelKey",
            Arch = ImageClassificationTrainer.Architecture.ResnetV2101,
            Epoch = 50,
            BatchSize = 20,
            LearningRate = 0.01f,
            ValidationSet = split.TestSet,
            MetricsCallback = m => Console.WriteLine(m)
        }))
    // Turn the numeric prediction back into a readable label
    .Append(mlContext.Transforms.Conversion.MapKeyToValue(
        outputColumnName: "PredictedLabel",
        inputColumnName: "PredictedLabel"));

Console.WriteLine("Training model... this may take a few minutes.");
ITransformer model = pipeline.Fit(split.TrainSet);

The Arch option selects the pre-trained architecture. ResnetV2101 is accurate but larger; MobilenetV2 is smaller and faster, ideal for edge or mobile deployment. An epoch is one full pass over your training data — 50 is a sensible default for transfer learning. The MetricsCallback lets you watch accuracy climb in real time.

Evaluating Model Accuracy

Never trust a model you have not measured. Run the held-out test set through the model and inspect the metrics. This is where you catch overfitting before it reaches production.

IDataView predictions = model.Transform(split.TestSet);

var metrics = mlContext.MulticlassClassification.Evaluate(
    predictions,
    labelColumnName: "LabelKey",
    predictedLabelColumnName: "PredictedLabel");

Console.WriteLine($"Macro Accuracy: {metrics.MacroAccuracy:P2}");
Console.WriteLine($"Micro Accuracy: {metrics.MicroAccuracy:P2}");
Console.WriteLine($"Log Loss:       {metrics.LogLoss:F3}");

Macro accuracy averages accuracy per class and is your best metric when classes are imbalanced — it prevents a dominant class from hiding poor performance on rare ones. Micro accuracy weights every sample equally. A large gap between the two signals class imbalance. Aim for macro accuracy above 90% on a clean dataset; if it is low, add more images or check for mislabeled data.

How to Save and Deploy the ML.NET Model

The whole point of ML.NET image classification in C# is deployment inside real .NET apps. Save the trained model as a single .zip file, then load it anywhere — an ASP.NET Core API, a Blazor app, a WPF desktop tool, or an Azure Function.

// Persist the model and its input schema to disk
mlContext.Model.Save(model, split.TrainSet.Schema, "imageClassifier.zip");
Console.WriteLine("Model saved to imageClassifier.zip");

To make predictions in production, load the model once and reuse a PredictionEngine. Because PredictionEngine is not thread-safe, in a web app you should use PredictionEnginePool from the Microsoft.Extensions.ML package instead of creating engines per request.

// Load the saved model
DataViewSchema schema;
ITransformer loadedModel = mlContext.Model.Load("imageClassifier.zip", out schema);

// Create a prediction engine for single, in-memory predictions
var predictor = mlContext.Model.CreatePredictionEngine<ImageData, ImagePrediction>(loadedModel);

// Classify a brand-new image
var sample = new ImageData { ImagePath = "assets/test/mystery.jpg" };
ImagePrediction result = predictor.Predict(sample);

float confidence = result.Score.Max();
Console.WriteLine($"Predicted: {result.PredictedLabel} ({confidence:P1} confident)");

Deploying in an ASP.NET Core API

For scalable image recognition in C#, register a PredictionEnginePool in Program.cs and inject it into your controllers. The pool manages a thread-safe set of engines for you.

builder.Services.AddPredictionEnginePool<ImageData, ImagePrediction>()
    .FromFile(modelName: "classifier", filePath: "imageClassifier.zip", watchForChanges: true);

Best Practices and Common Pitfalls

Getting a model to train is easy; getting a reliable one takes discipline. Keep these ML.NET best practices in mind:

  • Balance your classes. Aim for a similar number of images per label. Heavy imbalance biases predictions toward the majority class.
  • Use varied, realistic images. Include different lighting, angles, and backgrounds so the model generalizes instead of memorizing.
  • Always keep a separate test set. Evaluating on training data gives falsely high accuracy — the classic overfitting trap.
  • Reuse the PredictionEngine. Creating one per request is slow and, in web apps, unsafe. Use PredictionEnginePool.
  • Match architecture to deployment. Use MobilenetV2 for mobile/edge and ResnetV2101 when accuracy matters most.
  • Pin the TensorFlow redist version. Mismatched native binaries are the number-one cause of "DllNotFound" errors at runtime.

A frequent beginner mistake is forgetting the MapKeyToValue step at the end of the pipeline. Without it, your predictions come back as opaque numeric keys instead of human-readable labels like "dog".

Conclusion and Key Takeaways

You now have an end-to-end blueprint for ML.NET image classification — from organizing training data to training with transfer learning, evaluating accuracy, and deploying a custom model in a real C# application. The biggest advantage of doing image recognition in C# with ML.NET is that everything stays inside the .NET ecosystem you already know, so there is no context-switching to Python or costly cloud APIs.

Key takeaways:

  • Transfer learning lets you train an accurate image classifier with only a few hundred images.
  • The pipeline pattern — MapValueToKey → LoadRawImageBytes → ImageClassification → MapKeyToValue — is reusable for any dataset.
  • Always evaluate on a held-out test set and prefer macro accuracy for imbalanced data.
  • Deploy with PredictionEnginePool for thread-safe, high-throughput inference.

Try it on your own dataset today: swap in your image folders, hit train, and you will have a production-ready image classifier in .NET within minutes. Once you are comfortable, experiment with different architectures and epoch counts to squeeze out extra accuracy. Happy coding on csharp-coder.com!

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...