C# OnnxRuntime 部署 SAM3 实现可提示概念分割

0 阅读3分钟

效果

图片
图片
图片

项目

图片

模型信息

sam3_grounding_decoder.onnx

Model Properties
-------------------------
---------------------------------------------------------------

Inputs
-------------------------
name:feat0
tensor:Float[-1, 256, -1, -1]
name:feat1
tensor:Float[1, 256, 144, 144]
name:feat2
tensor:Float[1, 256, 72, 72]
name:vpe2
tensor:Float[1, 256, 72, 72]
name:lang_mask
tensor:Bool[-1, 32]
name:lang_feat
tensor:Float[32, -1, 256]
name:box_coords
tensor:Float[-1, -1, 4]
name:box_labels
tensor:Int64[-1, -1]
name:box_masks
tensor:Bool[-1, -1]
---------------------------------------------------------------

Outputs
-------------------------
name:boxes
tensor:Float[1, 200, 4]
name:scores
tensor:Float[-1, -1, 1]
name:masks
tensor:Float[1, 200, -1, -1]
name:presence
tensor:Float[1, 1]
---------------------------------------------------------------

sam3_grounding_encoder.onnx

Model Properties
-------------------------
---------------------------------------------------------------

Inputs
-------------------------
name:images
tensor:Float[1, 3, 1008, 1008]
---------------------------------------------------------------

Outputs
-------------------------
name:feat0
tensor:Float[1, 256, 288, 288]
name:feat1
tensor:Float[1, 256, 144, 144]
name:feat2
tensor:Float[1, 256, 72, 72]
name:vpe0
tensor:Float[1, 256, 288, 288]
name:vpe1
tensor:Float[1, 256, 144, 144]
name:vpe2
tensor:Float[1, 256, 72, 72]
---------------------------------------------------------------

sam3_language_encoder.onnx

Model Properties
-------------------------
---------------------------------------------------------------

Inputs
-------------------------
name:tokens
tensor:Int64[-1, 32]
---------------------------------------------------------------

Outputs
-------------------------
name:text_attention_mask
tensor:Bool[-1, 32]
name:text_memory
tensor:Float[32, -1, 256]
name:text_embeds
tensor:Float[32, -1, 1024]
---------------------------------------------------------------

代码

using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;

namespace Onnx_Demo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";
        string image_path = "";
        DateTime dt1 = DateTime.Now;
        DateTime dt2 = DateTime.Now;
        Mat image;
        Sam3GroundingSession groundingSession;

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = fileFilter;
            if (ofd.ShowDialog() != DialogResult.OK) return;
            pictureBox1.Image = null;
            image_path = ofd.FileName;
            pictureBox1.Image = new Bitmap(image_path);
            textBox1.Text = "";
            image = new Mat(image_path);
            pictureBox3.Image = null;

            image = new Mat(image_path);
            pictureBox1.Image = new Bitmap(image_path);

            textBox1.Text = "图片编码中……";
            Application.DoEvents();

            // 记录开始时间
            DateTime startTime = DateTime.Now;

            groundingSession.EncodeImg(image);

            // 计算耗时(秒)
            TimeSpan elapsed = DateTime.Now - startTime;
            double seconds = elapsed.TotalSeconds;

            textBox1.Text = $"图片编码完成,耗时 {seconds:F2} 秒";
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string modelDir = "model";  // 存放所有 onnx 和 vocab/merges 文件的目录
            groundingSession = new Sam3GroundingSession(modelDir, useGpu: false);

            textBox1.Text = "图片编码中……";
            Application.DoEvents();

            image_path = "test_img/i1.png";

            image = new Mat(image_path);
            pictureBox1.Image = new Bitmap(image_path);

            // 记录开始时间
            DateTime startTime = DateTime.Now;

            groundingSession.EncodeImg(image);

            // 计算耗时(秒)
            TimeSpan elapsed = DateTime.Now - startTime;
            double seconds = elapsed.TotalSeconds;

            textBox1.Text = $"图片编码完成,耗时 {seconds:F2} 秒";
        }

        SaveFileDialog sdf = new SaveFileDialog();
        private void button3_Click(object sender, EventArgs e)
        {
            if (pictureBox3.Image == null)
            {
                return;
            }
            Bitmap output = new Bitmap(pictureBox3.Image);
            sdf.Title = "保存";
            sdf.Filter = "Images (*.jpg)|*.jpg|Images (*.png)|*.png|Images (*.bmp)|*.bmp|Images (*.emf)|*.emf|Images (*.exif)|*.exif|Images (*.gif)|*.gif|Images (*.ico)|*.ico|Images (*.tiff)|*.tiff|Images (*.wmf)|*.wmf";
            if (sdf.ShowDialog() == DialogResult.OK)
            {
                switch (sdf.FilterIndex)
                {
                    case 1:
                        {
                            output.Save(sdf.FileName, ImageFormat.Jpeg);
                            break;
                        }
                    case 2:
                        {
                            output.Save(sdf.FileName, ImageFormat.Png);
                            break;
                        }
                    case 3:
                        {
                            output.Save(sdf.FileName, ImageFormat.Bmp);
                            break;
                        }
                    case 4:
                        {
                            output.Save(sdf.FileName, ImageFormat.Emf);
                            break;
                        }
                    case 5:
                        {
                            output.Save(sdf.FileName, ImageFormat.Exif);
                            break;
                        }
                    case 6:
                        {
                            output.Save(sdf.FileName, ImageFormat.Gif);
                            break;
                        }
                    case 7:
                        {
                            output.Save(sdf.FileName, ImageFormat.Icon);
                            break;
                        }

                    case 8:
                        {
                            output.Save(sdf.FileName, ImageFormat.Tiff);
                            break;
                        }
                    case 9:
                        {
                            output.Save(sdf.FileName, ImageFormat.Wmf);
                            break;
                        }
                }
                MessageBox.Show("保存成功,位置:" + sdf.FileName);
            }
        }

        /// <summary>
        /// 在原图上半透明叠加掩码(红色,透明度 0.5)
        /// </summary>
        private Mat OverlayMask(Mat originalBgr, Mat binaryMask)
        {
            // 创建彩色覆盖层(红色)
            Mat overlay = Mat.Zeros(originalBgr.Size(), MatType.CV_8UC3);
            overlay.SetTo(new Scalar(0, 0, 255), binaryMask); // 红色

            // 半透明混合
            Mat result = new Mat();
            Cv2.AddWeighted(originalBgr, 0.6, overlay, 0.4, 0, result);
            return result;
        }

        /// <summary>
        /// 将多个分割掩码用随机颜色填充并叠加到原图上
        /// </summary>
        /// <param name="original">原始 BGR 图像</param>
        /// <param name="results">文本预测结果列表</param>
        /// <returns>叠加后的图像(Mat 格式)</returns>
        private Mat OverlayMasksWithColors(Mat original, List<GroundingResult> results)
        {
            // 创建彩色叠加层(初始全黑,CV_8UC3)
            Mat colorOverlay = Mat.Zeros(original.Size(), MatType.CV_8UC3);
            Random rand = new Random();

            foreach (var res in results)
            {
                // 为每个实例生成随机颜色 (BGR)
                Scalar color = new Scalar(rand.Next(0, 255), rand.Next(0, 255), rand.Next(0, 255));
                // 将掩码区域填充为随机颜色(SetTo 高效)
                colorOverlay.SetTo(color, res.Mask);
            }

            // 半透明混合:原图 60% + 彩色叠加层 40%
            Mat blended = new Mat();
            Cv2.AddWeighted(original, 0.6, colorOverlay, 0.4, 0, blended);
            return blended;
        }

        private void button5_Click(object sender, EventArgs e)
        {
            if (image == nullreturn;
            string prompt = textBoxPrompt.Text.Trim();  // 假设存在 textBoxPrompt 输入提示词
            if (string.IsNullOrEmpty(prompt))
            {
                MessageBox.Show("请输入文本提示");
                return;
            }

            button5.Enabled = false;
            pictureBox3.Image = null;
            textBox1.Text = "";
            Application.DoEvents();

            // 执行文本预测
            // 记录开始时间
            DateTime startTime = DateTime.Now;
            var results = groundingSession.PredictText(prompt, threshold: 0.5f, maxDetections: 10);
            // 计算耗时(秒)
            TimeSpan elapsed = DateTime.Now - startTime;
            double seconds = elapsed.TotalSeconds;
            if (results.Count > 0)
            {
                // 1. 保存所有掩码到 mask 文件夹
                for (int i = 0; i < results.Count; i++)
                {
                    SaveMask(results[i].Mask, $"text_mask_{i}");
                }

                // 2. 生成彩色叠加效果
                using (Mat displayImage = OverlayMasksWithColors(image, results))
                {
                    pictureBox3.Image = BitmapConverter.ToBitmap(displayImage);
                }

                // 3. 更新状态栏信息
                textBox1.Text = $"检测到 {results.Count} 个实例,最高得分: {results.Max(r => r.Score):F2}";

                textBox1.Text += $"\r\n图片编码完成,耗时 {seconds:F2} 秒"; ;

            }
            else
            {
                textBox1.Text = "未检测到目标";
            }
            button5.Enabled = true;
        }

        /// <summary>
        /// 保存掩码到文件(自动创建 mask 文件夹)
        /// </summary>
        private void SaveMask(Mat mask, string prefix)
        {
            string maskDir = Path.Combine(Application.StartupPath, "mask");
            if (!Directory.Exists(maskDir))
                Directory.CreateDirectory(maskDir);

            string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
            string fileName = $"{prefix}_{timestamp}.png";
            string fullPath = Path.Combine(maskDir, fileName);
            Cv2.ImWrite(fullPath, mask);
        }
    }
}