[C#][winform]segment-anything分割万物部署onnx模型一键抠图演示

【效果演示】

【测试环境】

vs2019

net framework4.8.0

opencvsharp==4.13.0

onnxruntime==1.24.3

【界面代码】

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.IO;
using System.Drawing.Imaging;

namespace FIRC
{
    public partial class Form1 : Form
    {
        private Mat originalImage;
        private bool isDragging;
        private System.Drawing.Point startPoint;
        private System.Drawing.Rectangle selectionRect;
        private Bitmap originalBitmap;
        private OpenCvSharp.Rect roi;
        private SamInferenceSession samSession;
        private Mat samMask;
        private bool isSamInitialized = false;
      
        Mat displayImage = new Mat();
        public Form1()
        {
            InitializeComponent();
            isDragging = false;
        }

        private void btnLoadImage_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Image files (*.jpg, *.jpeg, *.png)|*.jpg;*.jpeg;*.png";
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                string imagePath = openFileDialog.FileName;
                originalImage = Cv2.ImRead(imagePath);
                if (originalImage.Empty())
                {
                    lblStatus.Text = "状态:图片加载失败";
                    return;
                }

                // 转换为Bitmap并显示
                originalBitmap = BitmapConverter.ToBitmap(originalImage);
                picOriginal.Image = originalBitmap;
                picRectangle.Image = null;
                picResult.Image = null;
                lblStatus.Text = "状态:图片加载成功,请在图片上绘制矩形";

               
            }
        }


        private void picOriginal_MouseDown(object sender, MouseEventArgs e)
        {
            if (originalImage == null || originalBitmap == null)
                return;

            isDragging = true;
            startPoint = e.Location;
            selectionRect = new System.Drawing.Rectangle(startPoint, new System.Drawing.Size(0, 0));
            UpdateSelection();
        }

        private void picOriginal_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isDragging || originalImage == null || originalBitmap == null)
                return;

            selectionRect = new System.Drawing.Rectangle(
                Math.Min(startPoint.X, e.X),
                Math.Min(startPoint.Y, e.Y),
                Math.Abs(e.X - startPoint.X),
                Math.Abs(e.Y - startPoint.Y)
            );
            UpdateSelection();
        }

        private void picOriginal_MouseUp(object sender, MouseEventArgs e)
        {
            if (!isDragging || originalImage == null || originalBitmap == null)
                return;

            isDragging = false;
            UpdateSelection();

            // 确保矩形有一定大小
            if (selectionRect.Width > 10 && selectionRect.Height > 10)
            {
                // 计算实际图像上的矩形坐标
                // 考虑图片在PictureBox中的实际显示位置和大小
                RectangleF imageRect = GetImageDisplayRect();
                float scaleX = (float)originalImage.Cols / imageRect.Width;
                float scaleY = (float)originalImage.Rows / imageRect.Height;

                // 计算相对于图片显示区域的坐标
                int x = (int)((selectionRect.X - imageRect.X) * scaleX);
                int y = (int)((selectionRect.Y - imageRect.Y) * scaleY);
                int width = (int)(selectionRect.Width * scaleX);
                int height = (int)(selectionRect.Height * scaleY);

                // 确保坐标在图像范围内
                x = Math.Max(0, x);
                y = Math.Max(0, y);
                width = Math.Min(width, originalImage.Cols - x);
                height = Math.Min(height, originalImage.Rows - y);

                // 设置ROI
                roi = new OpenCvSharp.Rect(x, y, width, height);

                // 显示矩形区域
                // 截取矩形区域
                Mat roiMat = new Mat(originalImage, roi);
                Bitmap roiBitmap = BitmapConverter.ToBitmap(roiMat);
                picRectangle.Image = roiBitmap;
                
                // 运行SAM分割
                RunSamSegmentation();

            }
        }

        private RectangleF GetImageDisplayRect()
        {
            if (originalImage == null || originalBitmap == null)
                return RectangleF.Empty;

            // 计算图片在PictureBox中的实际显示位置和大小
            float imageAspectRatio = (float)originalImage.Width / originalImage.Height;
            float pictureBoxAspectRatio = (float)picOriginal.Width / picOriginal.Height;

            float displayWidth, displayHeight, x, y;

            if (imageAspectRatio > pictureBoxAspectRatio)
            {
                // 图片比PictureBox更宽,按宽度缩放
                displayWidth = picOriginal.Width;
                displayHeight = displayWidth / imageAspectRatio;
                x = 0;
                y = (picOriginal.Height - displayHeight) / 2;
            }
            else
            {
                // 图片比PictureBox更高,按高度缩放
                displayHeight = picOriginal.Height;
                displayWidth = displayHeight * imageAspectRatio;
                x = (picOriginal.Width - displayWidth) / 2;
                y = 0;
            }

            return new RectangleF(x, y, displayWidth, displayHeight);
        }

        private void UpdateSelection()
        {
            if (originalBitmap == null)
                return;

            // 创建一个新的位图,避免修改原始位图
            Bitmap temp = new Bitmap(picOriginal.Width, picOriginal.Height);
            using (Graphics g = Graphics.FromImage(temp))
            {
                // 绘制原始图像,考虑缩放和居中
                RectangleF imageRect = GetImageDisplayRect();
                g.DrawImage(originalBitmap, imageRect);
                // 绘制选择矩形
                using (Pen pen = new Pen(Color.Red, 2))
                {
                    g.DrawRectangle(pen, selectionRect);
                }
            }
            picOriginal.Image = temp;
        }

        private void ShowRectangleArea()
        {
            if (originalImage == null || roi.Width <= 0 || roi.Height <= 0)
                return;

            // 截取矩形区域
            Mat roiMat = new Mat(originalImage, roi);
            Bitmap roiBitmap = BitmapConverter.ToBitmap(roiMat);
            picRectangle.Image = roiBitmap;
        }

        private void RunSamSegmentation()
        {
            if (!isSamInitialized || originalImage == null || samSession == null)
            {
                lblStatus.Text = "状态:SAM模型未初始化或图像未加载";
                return;
            }

            try
            {
                lblStatus.Text = "状态:正在进行SAM分割...";
                Application.DoEvents();

                // 获取SAM输入尺寸
                OpenCvSharp.Size inputSize = samSession.GetInputSize();
                
                // 调整图像大小以匹配SAM输入
                Mat resizedImage = new Mat();
                Cv2.Resize(originalImage, resizedImage, inputSize);

                // 加载图像到SAM会话
                samSession.LoadImage(resizedImage);

                // 将ROI坐标缩放到SAM输入尺寸
                float scaleX = (float)inputSize.Width / originalImage.Cols;
                float scaleY = (float)inputSize.Height / originalImage.Rows;
                
                int samRoiX = (int)(roi.X * scaleX);
                int samRoiY = (int)(roi.Y * scaleY);
                int samRoiWidth = (int)(roi.Width * scaleX);
                int samRoiHeight = (int)(roi.Height * scaleY);
                
                // 确保ROI在有效范围内
                samRoiX = Math.Max(0, Math.Min(samRoiX, inputSize.Width - 1));
                samRoiY = Math.Max(0, Math.Min(samRoiY, inputSize.Height - 1));
                samRoiWidth = Math.Min(samRoiWidth, inputSize.Width - samRoiX);
                samRoiHeight = Math.Min(samRoiHeight, inputSize.Height - samRoiY);
                
                // 创建SAM输入尺寸的ROI
                OpenCvSharp.Rect samRoi = new OpenCvSharp.Rect(samRoiX, samRoiY, samRoiWidth, samRoiHeight);

                // 运行推理获取掩码(使用ROI模式)
                double iou;
                samMask = samSession.GetMask(new List<OpenCvSharp.Point>(), new List<OpenCvSharp.Point>(), samRoi, out iou);

                // 将掩码调整回原始图像尺寸
                Mat resizedMask = new Mat();
                Cv2.Resize(samMask, resizedMask, new OpenCvSharp.Size(originalImage.Cols, originalImage.Rows));

                // 创建结果显示(原图+掩码叠加)
                //displayImage = originalImage.Clone();
                
                // 创建彩色掩码
                Mat colorMask = new Mat(originalImage.Size(), MatType.CV_8UC3, new Scalar(0, 0, 255));
                Mat maskedColor = new Mat();
                colorMask.CopyTo(maskedColor, resizedMask);

                //// 叠加掩码到原图
                //Cv2.AddWeighted(displayImage, 1.0, maskedColor, 0.5, 0, displayImage);

                //// 绘制ROI矩形
                //Cv2.Rectangle(displayImage, roi, new Scalar(0, 255, 0), 2);

                // 显示结果
                picResult.Image = BitmapConverter.ToBitmap(maskedColor);
                lblStatus.Text = string.Format("状态:SAM分割完成 (IOU: {0:F3})", iou);
            }
            catch (Exception ex)
            {
                lblStatus.Text = "状态:SAM分割失败:" + ex.Message;
                MessageBox.Show(ex.ToString(), "分割错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// 保存分割结果
        /// </summary>
        public void SaveSegmentationResult(string outputPath)
        {
            if (samMask == null || samMask.Empty())
            {
                throw new Exception("没有可保存的分割结果");
            }

            // 将掩码调整回原始图像尺寸
            Mat resizedMask = new Mat();
            Cv2.Resize(samMask, resizedMask, new OpenCvSharp.Size(originalImage.Cols, originalImage.Rows));

            // 保存掩码图像
            Cv2.ImWrite(outputPath, resizedMask);
        }

        /// <summary>
        /// 获取分割后的对象(带透明背景)
        /// </summary>
        public Mat GetSegmentedObject()
        {
            if (samMask == null || samMask.Empty() || originalImage == null)
            {
                return null;
            }

            // 将掩码调整回原始图像尺寸
            Mat resizedMask = new Mat();
            Cv2.Resize(samMask, resizedMask, new OpenCvSharp.Size(originalImage.Cols, originalImage.Rows));

            // 创建RGBA输出图像
            Mat result = new Mat(originalImage.Size(), MatType.CV_8UC4);
            
            // 将原图转换为RGBA
            Mat originalRgba = new Mat();
            Cv2.CvtColor(originalImage, originalRgba, ColorConversionCodes.BGR2BGRA);

            // 应用掩码作为alpha通道
            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Cols; j++)
                {
                    Vec4b pixel = originalRgba.At<Vec4b>(i, j);
                    byte maskValue = resizedMask.At<byte>(i, j);
                    
                    result.At<Vec4b>(i, j) = new Vec4b(pixel.Item0, pixel.Item1, pixel.Item2, maskValue);
                }
            }

            return result;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                // 加载模型
                string encoderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "models", "mobile_sam_preprocess.onnx");
                string decoderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "models", "mobile_sam.onnx");

                lblStatus.Text = "状态:正在初始化模型...";
                
                // 创建SAM推理会话
                samSession = new SamInferenceSession(encoderPath, decoderPath, 4);
                isSamInitialized = true;
                
                lblStatus.Text = "状态:模型加载成功";
            }
            catch (Exception ex)
            {
                lblStatus.Text = "状态:模型加载失败:" + ex.Message;
                // 显示详细错误信息
                MessageBox.Show(ex.ToString(), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                isSamInitialized = false;
            }
        }
    }
}

【支持模型】

edage_sam模型:

edge_sam_3x_decoder.onnx

edge_sam_3x_encoder.onnx

mobile_sam模型:

mobile_sam.onnx

mobile_sam_preprocess.onnx

sam模型:

sam_hq_preprocess.onnx

sam_hq_vit_h.onnx

【下载地址】

https://download.csdn.net/download/FL1623863129/92740173

【参考】

gthub仓库:dinglufe/segment-anything-cpp-wrapper

【注意事项】

使用的mobile_sam框架进行分割,使用CPU推理,速度比较快,无需安装cuda+cudnn和带nvidia显卡,大大降低电脑配置

相关推荐
百锦再2 小时前
Java 并发编程进阶,从线程池、锁、AQS 到并发容器与性能调优全解析
java·开发语言·jvm·spring·kafka·tomcat·maven
条tiao条2 小时前
KMP 算法详解:告别暴力匹配,让字符串匹配 “永不回头”
开发语言·算法
干啥啥不行,秃头第一名2 小时前
C++20概念(Concepts)入门指南
开发语言·c++·算法
2301_807367192 小时前
C++中的解释器模式变体
开发语言·c++·算法
always_TT3 小时前
C语言中的字符与字符串(char数组)
c语言·开发语言
forAllforMe3 小时前
LAN9252 从机寄存器配置--C语言举例
c语言·开发语言
weixin_537590453 小时前
《C程序设计语言》练习答案(练习1-4)
c语言·开发语言
love530love4 小时前
OpenClaw 手机直连配置全流程
人工智能·windows·python·智能手机·c#·agent·openclaw
chushiyunen4 小时前
python中的内置属性 todo
开发语言·javascript·python