Deploying YOLOv9 Object Detection and Instance Segmentation Models with OpenVINO™ C# API

YOLOv9 represents the latest advancement in the YOLO family of real-time object detection models, offering significant improvements in accuracy, speed, and efficiency. It introduces novel architectural components such as Generalized ELAN (GELAN) and Programmable Gradient Information (PGI) to mitigate information loss in deep networks and enhance feature learning. This article demonstrates how to deploy both YOLOv9 object detection and instance segmentation models using the OpenVINO™ C# API with OpenVINO™ 2024.0.

The OpenVINO™ C# API is a .NET wrapper around the OpenVINO™ Runtime C API, enabling C# developers to leverage OpenVINO’s cross-platform inference acceleration capabilities on Intel CPUs, GPUs, and NPUs. The full implementation and sample code are available at:

Model Preparation

To obtain YOLOv9 models in OpenVINO IR format:

  1. Clone the official repository: ``` git clone https://github.com/WongKinYiu/yolov9.git cd yolov9
  2. Create a Python environment and install dependencies: ``` conda create -n yolov9 python=3.10 conda activate yolov9 pip install -r requirements.txt pip install openvino==2024.0.0
  3. Export and convert the model: ``` wget https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-c.pt python export.py --weights ./yolov9-c.pt --imgsz 640 --include onnx ovc yolov9-c.onnx
    
    

For instance segmentation, use gelan-c-seg.pt instead.

C# Project Setup

Create a .NET 6 console application:

dotnet new console --framework net6.0 -o yolov9

Add required NuGet packages:

dotnet add package OpenVINO.CSharp.API
dotnet add package OpenVINO.runtime.win
dotnet add package OpenVINO.CSharp.API.Extensions
dotnet add package OpenVINO.CSharp.API.Extensions.OpenCvSharp
dotnet add package OpenCvSharp4
dotnet add package OpenCvSharp4.Extensions
dotnet add package OpenCvSharp4.runtime.win

The resulting .csproj file includes:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="OpenCvSharp4" Version="4.9.0.20240103" />
    <PackageReference Include="OpenVINO.CSharp.API" Version="2024.0.0.1" />
    <PackageReference Include="OpenVINO.runtime.win" Version="2024.0.0.1" />
    <PackageReference Include="OpenVINO.CSharp.API.Extensions.OpenCvSharp" Version="1.0.4" />
  </ItemGroup>
</Project>

Model Inference Implementation

Object Detection

The inference pipeline follows standard OpenVINO steps: initialize Core, load model, compile for target device, create infer request, preprocess input, run inference, and post-process output.

static void RunYoloDetection(string modelPath, string imagePath, string device = "AUTO")
{
    var core = new Core();
    var model = core.read_model(modelPath);
    var compiledModel = core.compile_model(model, device);
    var inferRequest = compiledModel.create_infer_request();

    using var image = new Mat(imagePath);
    int maxSize = Math.Max(image.Cols, image.Rows);
    using var squareImage = Mat.Zeros(maxSize, maxSize, MatType.CV_8UC3);
    image.CopyTo(new Mat(squareImage, new Rect(0, 0, image.Cols, image.Rows)));
    float scale = maxSize / 640.0f;

    var inputTensor = inferRequest.get_input_tensor();
    var shape = inputTensor.get_shape();
    using var blob = CvDnn.BlobFromImage(squareImage, 1.0 / 255.0,
        new Size(shape[2], shape[3]), 0, true, false);
    float[] inputData = new float[blob.Total()];
    Marshal.Copy(blob.Ptr(0), inputData, 0, inputData.Length);
    inputTensor.set_data(inputData);

    inferRequest.infer();

    Tensor outputTensor = model.get_outputs_size() > 1 
        ? inferRequest.get_output_tensor(1) 
        : inferRequest.get_output_tensor();
    
    float[] output = outputTensor.get_data<float>((int)outputTensor.get_size());
    using var resultMat = new Mat(84, 8400, MatType.CV_32F, output).T();

    var boxes = new List<Rect>();
    var classIds = new List<int>();
    var confidences = new List<float>();

    for (int i = 0; i < resultMat.Rows; i++)
    {
        using var scores = new Mat(resultMat, new Rect(4, i, 80, 1));
        Cv2.MinMaxLoc(scores, out _, out double maxScore, out _, out Point maxLoc);
        if (maxScore > 0.25)
        {
            float cx = resultMat.At<float>(i, 0);
            float cy = resultMat.At<float>(i, 1);
            float w = resultMat.At<float>(i, 2);
            float h = resultMat.At<float>(i, 3);

            int x = (int)((cx - 0.5 * w) * scale);
            int y = (int)((cy - 0.5 * h) * scale);
            int width = (int)(w * scale);
            int height = (int)(h * scale);

            boxes.Add(new Rect(x, y, width, height));
            classIds.Add(maxLoc.X);
            confidences.Add((float)maxScore);
        }
    }

    CvDnn.NMSBoxes(boxes, confidences, 0.5f, 0.5f, out int[] indices);
    foreach (int idx in indices)
    {
        Cv2.Rectangle(image, boxes[idx], Scalar.Red, 2);
        Cv2.PutText(image, $"{classIds[idx]}-{confidences[idx]:F2}",
            new Point(boxes[idx].X, boxes[idx].Y + 25),
            HersheyFonts.HersheySimplex, 0.8, Scalar.Black, 2);
    }

    string outputPath = Path.ChangeExtension(imagePath, "_det_result.jpg");
    Cv2.ImWrite(outputPath, image);
}

Instance Segmentation

Segmentation adds a second output tensor containing mask prototypes. The detection output includes mask coefficients used to reconstruct instance masks:

static void RunYoloSegmentation(string modelPath, string imagePath, string device = "AUTO")
{
    // ... (same initialization and input processing as detection)

    inferRequest.infer();

    var detTensor = inferRequest.get_output_tensor(0);
    var protoTensor = inferRequest.get_output_tensor(1);
    float[] detData = detTensor.get_data<float>((int)detTensor.get_size());
    float[] protoData = protoTensor.get_data<float>((int)protoTensor.get_size());

    using var detections = new Mat(116, 8400, MatType.CV_32F, detData).T();
    using var protos = new Mat(32, 25600, MatType.CV_32F, protoData);

    var boxes = new List<Rect>();
    var classIds = new List<int>();
    var confidences = new List<float>();
    var maskCoeffs = new List<Mat>();

    // ... (parse detections similar to object detection)

    CvDnn.NMSBoxes(boxes, confidences, 0.5f, 0.5f, out int[] indices);
    using var overlay = Mat.Zeros(image.Size(), MatType.CV_8UC3);
    var rand = new Random();

    foreach (int idx in indices)
    {
        using var maskCoeff = maskCoeffs[idx];
        using var maskFeatures = maskCoeff * protos; // (1x32) * (32x25600) = (1x25600)
        ApplySigmoid(maskFeatures); // element-wise sigmoid

        using var mask160 = maskFeatures.Reshape(1, 160); // 160x160
        Rect box = boxes[idx];
        int mx1 = Math.Max(0, (int)((box.X / scale) * 0.25));
        int my1 = Math.Max(0, (int)((box.Y / scale) * 0.25));
        int mx2 = Math.Min(160, (int)(((box.X + box.Width) / scale) * 0.25));
        int my2 = Math.Min(160, (int)(((box.Y + box.Height) / scale) * 0.25));

        using var croppedMask = new Mat(mask160, new Range(my1, my2), new Range(mx1, mx2));
        using var resizedMask = new Mat();
        Cv2.Resize(croppedMask, resizedMask, new Size(box.Width, box.Height));

        using var binaryMask = new Mat();
        Threshold(resizedMask, binaryMask, 0.5f, 1.0f); // binarize

        using var fullMask = Mat.Zeros(image.Size(), MatType.CV_8UC1);
        binaryMask.ConvertTo(binaryMask, MatType.CV_8U, 255);
        binaryMask.CopyTo(new Mat(fullMask, new Rect(box.X, box.Y, box.Width, box.Height)));

        Scalar color = new(rand.Next(256), rand.Next(256), rand.Next(256));
        Cv2.Add(overlay, color, overlay, fullMask);
    }

    using var result = new Mat();
    Cv2.AddWeighted(image, 0.6, overlay, 0.4, 0, result);
    string outputPath = Path.ChangeExtension(imagePath, "_seg_result.jpg");
    Cv2.ImWrite(outputPath, result);
}

static void ApplySigmoid(Mat mat)
{
    for (int i = 0; i < mat.Rows; i++)
        for (int j = 0; j < mat.Cols; j++)
            mat.Set<float>(i, j, 1.0f / (1.0f + MathF.Exp(-mat.At<float>(i, j))));
}

static void Threshold(Mat src, Mat dst, float thresh, float maxVal)
{
    dst.Create(src.Size(), src.Type());
    for (int r = 0; r < src.Rows; r++)
        for (int c = 0; c < src.Cols; c++)
            dst.Set<float>(r, c, src.At<float>(r, c) > thresh ? maxVal : 0.0f);
}

Using OpenVINO Preprocessing

OpenVINO’s built-in preprocessing can be integrated during model loading to offload image resizing and normalization to the inference engine:

var processor = new PrePostProcessor(model);
processor.input(0)
    .tensor()
    .set_from(new Tensor(new OvType(ElementType.U8), new Shape(1, 640, 640, 3)))
    .set_layout(new Layout("NHWC"))
    .set_color_format(ColorFormat.BGR);

processor.input(0)
    .preprocess()
    .convert_color(ColorFormat.RGB)
    .resize(ResizeAlgorithm.RESIZE_LINEAR)
    .convert_element_type(new OvType(ElementType.F32))
    .scale(255.0f)
    .convert_layout(new Layout("NCHW"));

var optimizedModel = processor.build();

This allows direct feeding of raw BGR image data without manual normalization.

Execution

The main method supports automatic download of pre-converted models and test images:

static async Task Main(string[] args)
{
    string modelPath, imagePath, device = "AUTO";
    if (args.Length == 0)
    {
        // Download pre-converted model and sample image if not present
        await EnsureModelAndImageAsync();
        modelPath = "./model/yolov9-c-converted.xml";
        imagePath = "./model/test_det_01.jpg";
    }
    else
    {
        (modelPath, imagePath, device) = (args[0], args[1], args.Length > 2 ? args[2] : "AUTO");
    }

    Console.WriteLine($"Running inference on {device}");
    RunYoloDetection(modelPath, imagePath, device);
    RunYoloSegmentation(modelPath.Replace("yolov9-c", "gelan-c-seg"), imagePath, device);
}

Build and run with:

dotnet build
dotnet run --no-build

Both OpenCvSharp and EmguCV variant are provided in the sample repository for developer flexibility.

Tags: OpenVINO C# YOLOv9 ObjectDetection InstanceSegmentation

Posted on Sun, 20 Sep 2026 16:21:38 +0000 by padiwak