C# OpenvinoSharp使用DINOv2模型进行图像相似度计算

效果

ONNX模型信息

facebook/dinov2-small

C#部署代码

cs 复制代码
using System.Diagnostics;
using MathNet.Numerics.LinearAlgebra.Single;
using OpenCvSharp;
using OpenCvSharp.Dnn;
using OpenVinoSharp;
using Size = OpenCvSharp.Size;

namespace ImageSimilarityApp
{
    public partial class Form1 : Form
    {
        private string imgPath1, imgPath2;

        // OpenVINO 推理相关
        private Core ieCore;
        private Model model;
        private CompiledModel compiledModel;
        private InferRequest inferRequest;

        // 预处理常量
        private const int InputSize = 518;
        
        public Form1()
        {
            InitializeComponent();
            LoadModel();
        }

        private void LoadModel()
        {
            try
            {
                ieCore = new Core();
                model = ieCore.read_model("dinov2_cls_norm.onnx");
                compiledModel = ieCore.compile_model(model, "GPU.0");
                inferRequest = compiledModel.create_infer_request();
                lblResult.Text = "模型加载成功!";
            }
            catch (Exception ex)
            {
                MessageBox.Show($"模型加载失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Environment.Exit(1);
            }
        }

        private void BtnSelectImg1_Click(object sender, EventArgs e)
        {
            using OpenFileDialog dlg = new OpenFileDialog();
            dlg.Filter = "图像文件|*.jpg;*.jpeg;*.png;*.bmp";
            if (dlg.ShowDialog() == DialogResult.OK)
            {
                imgPath1 = dlg.FileName;
                picBox1.Image = Image.FromFile(imgPath1);
            }
        }

        private void BtnSelectImg2_Click(object sender, EventArgs e)
        {
            using OpenFileDialog dlg = new OpenFileDialog();
            dlg.Filter = "图像文件|*.jpg;*.jpeg;*.png;*.bmp";
            if (dlg.ShowDialog() == DialogResult.OK)
            {
                imgPath2 = dlg.FileName;
                picBox2.Image = Image.FromFile(imgPath2);
            }
        }

        private void BtnCompute_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(imgPath1) || string.IsNullOrEmpty(imgPath2))
            {
                MessageBox.Show("请先选择两张图片", "提示");
                return;
            }

            try
            {
                Stopwatch sw = Stopwatch.StartNew();
                float[] feat1 = ExtractFeature(imgPath1);
                float[] feat2 = ExtractFeature(imgPath2);
                sw.Stop();
                long time1= sw.ElapsedMilliseconds;
                sw.Restart();
                double similarity = CosineSimilarity(feat1, feat2);
                sw.Stop();
                long time2 = sw.ElapsedMilliseconds;
                lblResult.Text = $"相似度: {similarity:F6} 推理耗时: {time1} ms 相似度计算耗时: {time2} ms ";
            }
            catch (Exception ex)
            {
                MessageBox.Show($"计算失败: {ex.Message}", "错误");
            }
        }

        private float[] ExtractFeature(string imagePath)
        {
            // 1. 读取并预处理图像
            using Mat img = Cv2.ImRead(imagePath);
            if (img.Empty())
                throw new Exception("图像读取失败");

            // 转为 RGB
            Mat rgbImg = new Mat();
            Cv2.CvtColor(img, rgbImg, ColorConversionCodes.BGR2RGB);

            // resize
            Mat resized = new Mat();
            Cv2.Resize(rgbImg, resized, new Size(InputSize, InputSize), 0, 0, InterpolationFlags.Linear);

            // 转 float32 并归一化到 [0,1]
            resized.ConvertTo(resized, MatType.CV_32FC3, 1.0 / 255.0);

            // 标准化 (mean, std)
            Mat normImg = new Mat();
            Cv2.Subtract(resized, new Scalar(0.485f, 0.456f, 0.406f), normImg);
            Cv2.Divide(normImg,new Scalar(0.229f, 0.224f, 0.225f),normImg);
           
            // CHW 布局 (OpenVINO 默认 NCHW)
            Mat chwImg = CvDnn.BlobFromImage(normImg);  // 返回 1x3xHxW

            // 获取输入张量
            Tensor inputTensor = inferRequest.get_input_tensor();
            inputTensor.shape = new Shape([1, 3, 518, 518]);
            // 将数据拷贝到输入张量
            float[] inputData = new float[1 * 3 * InputSize * InputSize];
            System.Runtime.InteropServices.Marshal.Copy(chwImg.Data, inputData, 0, inputData.Length);
            inputTensor.set_data(inputData);

            // 推理
            inferRequest.infer();

            // 获取输出张量 (归一化特征)
            Tensor outputTensor = inferRequest.get_output_tensor();
            float[] feature = outputTensor.get_data<float>((int)outputTensor.size);
            // feature 已经是 L2 归一化 (shape: 1x384)
            float[] feat = new float[384];
            Array.Copy(feature, 0, feat, 0, 384);
            return feat;
        }

        private float CosineSimilarity(float[] a, float[] b)
        {
            // 因为向量的 L2 范数已经是 1 (模型输出已归一化),所以直接返回点积
            float dot = (float)System.Numerics.Tensors.TensorPrimitives.CosineSimilarity(a,b);
            return dot;
        }

        private float CosineSimilarity2(float[] a, float[] b)
        {
            var vector1 = new DenseVector(a);
            var vector2 = new DenseVector(b);

            float dotProduct = vector1.DotProduct(vector2);
            //float magnitude = (float)(vector1.L2Norm() * vector2.L2Norm());
            //float similarity = magnitude == 0 ? 0 : dotProduct / magnitude;
            float similarity = dotProduct;
            return similarity;
        }

        protected override void OnFormClosed(FormClosedEventArgs e)
        {
            inferRequest?.Dispose();
            compiledModel?.Dispose();
            model?.Dispose();
            ieCore?.Dispose();
            base.OnFormClosed(e);
        }
    }
}
相关推荐
玖玥拾1 小时前
C# 语言进阶(十五)C# 游戏服务端 MySQL 数据库
服务器·开发语言·网络·数据库·mysql·c#
铅笔侠_小龙虾2 小时前
Rust 学习目录
开发语言·学习·rust
云泽8082 小时前
从零吃透 C++ 异常:抛出捕获、栈展开、异常重抛与编码规范详解
开发语言·c++·代码规范
灯澜忆梦2 小时前
GO---可见性规则
开发语言·golang
逝水无殇2 小时前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#
这是个栗子3 小时前
前端开发中的常用工具函数(八)
开发语言·前端·javascript
凤凰院凶涛QAQ3 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
研☆香4 小时前
为什么变量声明在赋值前也能使用?—— 深入理解 JavaScript 变量提升与作用域
开发语言·前端·javascript
右耳朵猫AI4 小时前
PHP周刊2026W28 | 安全更新、Laravel 13.18、Twig 3.28
开发语言·php·laravel
second604 小时前
第一部分:快速上手 —— 建立 C++ 基本语法与编程范式
开发语言·c++