【效果演示】


【测试环境】
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显卡,大大降低电脑配置