C# OpenVinoSharp部署DEIMv2
关于DEIMv2的介绍,大家可以看这篇文章,据说是对小目标检测效果比较好(没有大量实测)
https://zhuanlan.zhihu.com/p/1954932639887791489
目标检测效果:

模型信息:
测试模型名称 deimv2_hgnetv2_n_coco.onnx,可在Hugging Face获取
模型输入:
name: images
tensor: float32[N,3,640,640]
name: orig_target_sizes
tensor: int64[unk__446,2]
模型输出:
name: labels
tensor: int64[Sublabels_dim_0,Sublabels_dim_1]
name: boxes
tensor: float32[GatherElementsboxes_dim_0,GatherElementsboxes_dim_1,4]
name: scores
tensor: float32[GatherElementsboxes_dim_0,GatherElementsboxes_dim_1]
接下来,就是模型部署推理相关代码了,直接复制就可以用
///检测类 DEIMv2Detector.cs
using OpenCvSharp;
using OpenVinoSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace yolo_world_opencvsharp_net4._8
{
public class DEIMv2Detector : IDisposable
{
private Core _core;
private CompiledModel _compiledModel;
private InferRequest _inferRequest;
private string _modelSize;
private int _inputSize;
private bool _shouldNormalize;
// ImageNet归一化参数
private readonly float[] _imagenetMean = new float[] { 0.485f, 0.456f, 0.406f };
private readonly float[] _imagenetStd = new float[] { 0.229f, 0.224f, 0.225f };
// 颜色列表用于不同类别的绘制
private readonly Scalar[] _colors = new Scalar[]
{
new Scalar(255, 0, 0), // 红色
new Scalar(0, 255, 0), // 绿色
new Scalar(0, 0, 255), // 蓝色
new Scalar(255, 255, 0), // 青色
new Scalar(255, 0, 255), // 洋红色
new Scalar(0, 255, 255), // 黄色
new Scalar(128, 0, 0), // 深红色
new Scalar(0, 128, 0), // 深绿色
new Scalar(0, 0, 128), // 深蓝色
new Scalar(128, 128, 0) // 橄榄色
};
public DEIMv2Detector(string modelPath, string modelSize)
{
_core = new Core();
_compiledModel = _core.compile_model(modelPath, "CPU");
_inferRequest = _compiledModel.create_infer_request();
_modelSize = modelSize.ToLower();
// 根据模型大小决定是否使用归一化
_shouldNormalize = !new[] { "atto", "femto", "pico", "n" }.Contains(_modelSize);
// 获取输入尺寸
var inputShape = _inferRequest.get_tensor("images").get_shape();
_inputSize = (int)inputShape[2]; // 形状为 [N,3,640,640]
Console.WriteLine($"Model loaded: {modelPath}");
Console.WriteLine($"Input size: {_inputSize}");
Console.WriteLine($"Normalization: {_shouldNormalize}");
}
public Mat ProcessImage(string imagePath)
{
// 使用OpenCvSharp加载图像
var originalImage = Cv2.ImRead(imagePath);
if (originalImage.Empty())
{
Console.WriteLine($"Failed to load image: {imagePath}");
return null;
}
// 预处理
var (processedImage, ratio, padW, padH) = ResizeWithAspectRatio(originalImage, _inputSize);
var inputTensor = PreprocessImage(processedImage, _shouldNormalize);
var sizeTensor = PrepareTargetSizes(_inputSize, _inputSize);
// 设置输入
_inferRequest.set_input_tensor(0, inputTensor);
_inferRequest.set_input_tensor(1, sizeTensor);
// 推理
_inferRequest.infer();
// 获取输出
var labels = _inferRequest.get_output_tensor(0);
var boxes = _inferRequest.get_output_tensor(1);
var scores = _inferRequest.get_output_tensor(2);
// 处理结果
var resultImage = DrawDetections(originalImage, labels, boxes, scores, ratio, padW, padH);
// 保存结果
//string outputPath = "onnx_result_csharp.jpg";
//Cv2.ImWrite(outputPath, resultImage);
//Console.WriteLine($"Result saved as: {outputPath}");
// 清理资源
inputTensor.Dispose();
sizeTensor.Dispose();
labels.Dispose();
boxes.Dispose();
scores.Dispose();
processedImage.Dispose();
return resultImage;
}
private (Mat resizedImage, float ratio, int padW, int padH) ResizeWithAspectRatio(Mat image, int size)
{
int originalWidth = image.Width;
int originalHeight = image.Height;
float ratio = Math.Min((float)size / originalWidth, (float)size / originalHeight);
int newWidth = (int)(originalWidth * ratio);
int newHeight = (int)(originalHeight * ratio);
// 调整尺寸
var resizedImage = new Mat();
Cv2.Resize(image, resizedImage, new Size(newWidth, newHeight));
// 创建填充后的图像
var paddedImage = new Mat(size, size, image.Type(), new Scalar(0, 0, 0)); // 用黑色填充
int padW = (size - newWidth) / 2;
int padH = (size - newHeight) / 2;
// 将调整大小后的图像复制到填充图像的中央
var roi = new Rect(padW, padH, newWidth, newHeight);
resizedImage.CopyTo(paddedImage[roi]);
resizedImage.Dispose();
return (paddedImage, ratio, padW, padH);
}
private Tensor PreprocessImage(Mat image, bool normalize)
{
int width = image.Width;
int height = image.Height;
// 转换为浮点数并归一化
var floatImage = new Mat();
image.ConvertTo(floatImage, MatType.CV_32FC3, 1.0 / 255.0);
// 分离通道
Mat[] channels = new Mat[3];
Cv2.Split(floatImage, out channels);
float[] result = new float[3 * height * width];
if (normalize)
{
// 应用ImageNet归一化
for (int c = 0; c < 3; c++)
{
var channelData = new float[height * width];
channels[c].GetArray(out channelData);
for (int i = 0; i < channelData.Length; i++)
{
channelData[i] = (channelData[i] - _imagenetMean[c]) / _imagenetStd[c];
}
Array.Copy(channelData, 0, result, c * height * width, channelData.Length);
}
}
else
{
// 只使用0-1范围的数值
for (int c = 0; c < 3; c++)
{
var channelData = new float[height * width];
channels[c].GetArray(out channelData);
Array.Copy(channelData, 0, result, c * height * width, channelData.Length);
}
}
// 清理资源
floatImage.Dispose();
foreach (var channel in channels)
{
channel.Dispose();
}
// 创建Tensor [1, 3, height, width]
Tensor tensor = new Tensor(new OpenVinoSharp.element.Type(ElementType.F32), new Shape(1, 3, height, width));
tensor.set_data<float>(result);
return tensor;
}
private Tensor PrepareTargetSizes(int height, int width)
{
long[] sizeData = new long[] { height, width };
Tensor tensor = new Tensor(new OpenVinoSharp.element.Type(ElementType.I64), new Shape(1, 2));
tensor.set_data<long>(sizeData);
return tensor;
}
private Mat DrawDetections(Mat originalImage, Tensor labels, Tensor boxes, Tensor scores,
float ratio, int padW, int padH, float threshold = 0.3f)
{
var resultImage = originalImage.Clone();
// 获取输出数据
long[] labelsData = labels.get_data<long>((int)labels.get_size());
float[] boxesData = boxes.get_data<float>((int)boxes.get_size());
float[] scoresData = scores.get_data<float>((int)scores.get_size());
// 获取输出形状
long[] labelsShape = labels.get_shape().ToArray();
long[] boxesShape = boxes.get_shape().ToArray();
int numDetections = (int)labelsShape[1];//[1,100]
for (int i = 0; i < numDetections; i++)
{
float score = scoresData[i];
// 过滤低置信度检测
if (score < threshold)
continue;
long label = labelsData[i];
// 获取边界框坐标 (x1, y1, x2, y2)
int baseIdx = i * 4;
float x1 = boxesData[baseIdx];
float y1 = boxesData[baseIdx + 1];
float x2 = boxesData[baseIdx + 2];
float y2 = boxesData[baseIdx + 3];
// 调整边界框到原始图像坐标
float adjustedX1 = (x1 - padW) / ratio;
float adjustedY1 = (y1 - padH) / ratio;
float adjustedX2 = (x2 - padW) / ratio;
float adjustedY2 = (y2 - padH) / ratio;
// 确保坐标在图像范围内
adjustedX1 = Math.Max(0, Math.Min(adjustedX1, originalImage.Width));
adjustedY1 = Math.Max(0, Math.Min(adjustedY1, originalImage.Height));
adjustedX2 = Math.Max(0, Math.Min(adjustedX2, originalImage.Width));
adjustedY2 = Math.Max(0, Math.Min(adjustedY2, originalImage.Height));
// 选择颜色
Scalar color = _colors[label % _colors.Length];
// 绘制边界框
Cv2.Rectangle(resultImage,
new Point((int)adjustedX1, (int)adjustedY1),
new Point((int)adjustedX2, (int)adjustedY2),
color, 2);
// 绘制标签文本
string labelText = $"Class: {label} ({score:F2})";
var textSize = Cv2.GetTextSize(labelText, HersheyFonts.HersheySimplex, 0.5, 1, out int baseline);
// 绘制文本背景
Cv2.Rectangle(resultImage,
new Point((int)adjustedX1, (int)adjustedY1 - textSize.Height - 5),
new Point((int)adjustedX1 + textSize.Width, (int)adjustedY1),
color, -1);
// 绘制文本
Cv2.PutText(resultImage, labelText,
new Point((int)adjustedX1, (int)adjustedY1 - 5),
HersheyFonts.HersheySimplex, 0.5, Scalar.White, 1);
}
return resultImage;
}
public void Dispose()
{
_inferRequest?.Dispose();
_compiledModel?.Dispose();
_core?.Dispose();
}
}
}
使用方法示例:
using OpenVinoSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using Point = OpenCvSharp.Point;
using Size = OpenCvSharp.Size;
using System.Diagnostics;
namespace yolo_world_opencvsharp_net4._8
{
public partial class DEIMv2Form : Form
{
public DEIMv2Form()
{
InitializeComponent();
}
DEIMv2Detector detector;
private void button1_Click(object sender, EventArgs e)
{
Infer();
}
private void Infer()
{
try
{
// 运行推理
string inputPath = "D:\\ultralytics-main\\test_det_01.jpg";
if (IsImageFile(inputPath))
{
Stopwatch sw = Stopwatch.StartNew();
var resultImage=detector.ProcessImage(inputPath);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds+"ms");
if (resultImage != null)
{
Cv2.NamedWindow("result",WindowFlags.Normal);
Cv2.ImShow("result", resultImage);
resultImage.Dispose();
}
}
else
{
return;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static bool IsImageFile(string path)
{
string[] imageExtensions = { ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif" };
return imageExtensions.Any(ext => path.ToLower().EndsWith(ext));
}
private void DEIMv2Form_Load(object sender, EventArgs e)
{
//"Model sizes: atto, femto, pico, n, s, m, l, x";
string modelPath = "D:\\deimv2_hgnetv2_n_coco.onnx";
string modelSize = "n";
//初始化
detector = new DEIMv2Detector(modelPath, modelSize);
}
}
}
