本文将详细介绍如何在 WinForms 应用中实现图片的圆形区域选择和截取功能,使用 OpenCvSharp 进行图像处理。
C#using System;
using System.Drawing;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;
C#private Bitmap sourceBitmap; // 源图片
private Point startPoint; // 圆心位置
private Point currentPoint; // 当前鼠标位置
private Point lastMousePosition; // 上次鼠标位置
private bool isDrawing = false; // 是否正在绘制
private bool isMoving = false; // 是否正在移动
private int radius = 0; // 圆形半径
private const int HitTestTolerance = 1; // 点击判定容差
C#private void SetupEventHandlers()
{
pictureBox1.MouseDown += PictureBox1_MouseDown;
pictureBox1.MouseMove += PictureBox1_MouseMove;
pictureBox1.MouseUp += PictureBox1_MouseUp;
pictureBox1.Paint += PictureBox1_Paint;
btnCapture.Click += CaptureButton_Click;
// 设置双缓冲减少闪烁
typeof(PictureBox).InvokeMember("DoubleBuffered",
BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, pictureBox1, new object[] { true });
}
C#private bool IsPointNearCircle(Point point)
{
if (radius == 0) return false;
double distance = Math.Sqrt(
Math.Pow(point.X - startPoint.X, 2) +
Math.Pow(point.Y - startPoint.Y, 2));
return Math.Abs(distance - radius) < HitTestTolerance;
}
private bool IsPointInsideCircle(Point point)
{
if (radius == 0) return false;
double distance = Math.Sqrt(
Math.Pow(point.X - startPoint.X, 2) +
Math.Pow(point.Y - startPoint.Y, 2));
return distance < radius;
}
C#private (float scaleX, float scaleY, float offsetX, float offsetY) GetImageScaleAndOffset()
{
if (sourceBitmap == null || pictureBox1.Image == null)
return (1, 1, 0, 0);
float containerRatio = (float)pictureBox1.Width / pictureBox1.Height;
float imageRatio = (float)sourceBitmap.Width / sourceBitmap.Height;
float scaleX, scaleY, offsetX = 0, offsetY = 0;
if (containerRatio > imageRatio)
{
scaleY = (float)sourceBitmap.Height / pictureBox1.Height;
scaleX = scaleY;
offsetX = (pictureBox1.Width - (sourceBitmap.Width / scaleX)) / 2;
}
else
{
scaleX = (float)sourceBitmap.Width / pictureBox1.Width;
scaleY = scaleX;
offsetY = (pictureBox1.Height - (sourceBitmap.Height / scaleY)) / 2;
}
return (scaleX, scaleY, offsetX, offsetY);
}
C#private void CaptureButton_Click(object sender, EventArgs e)
{
if (sourceBitmap == null || radius == 0) return;
try
{
using (Mat sourceMat = BitmapConverter.ToMat(sourceBitmap))
using (Mat mask = new Mat(sourceMat.Size(), MatType.CV_8UC1, Scalar.Black))
using (Mat result = new Mat(sourceMat.Size(), sourceMat.Type(), Scalar.Black))
{
var (scaleX, scaleY, offsetX, offsetY) = GetImageScaleAndOffset();
int actualX = (int)((startPoint.X - offsetX) * scaleX);
int actualY = (int)((startPoint.Y - offsetY) * scaleY);
int actualRadius = (int)(radius * scaleX);
Cv2.Circle(mask, new OpenCvSharp.Point(actualX, actualY),
actualRadius, Scalar.White, -1);
sourceMat.CopyTo(result, mask);
OpenCvSharp.Rect roi = new OpenCvSharp.Rect(
actualX - actualRadius,
actualY - actualRadius,
actualRadius * 2,
actualRadius * 2);
// 确保ROI在图像范围内
roi.X = Math.Max(0, roi.X);
roi.Y = Math.Max(0, roi.Y);
roi.Width = Math.Min(result.Width - roi.X, roi.Width);
roi.Height = Math.Min(result.Height - roi.Y, roi.Height);
if (roi.Width > 0 && roi.Height > 0)
{
using (Mat cropped = new Mat(result, roi))
{
pictureBox2.Image?.Dispose();
pictureBox2.Image = BitmapConverter.ToBitmap(cropped);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"截取图片时出错:{ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using Point = System.Drawing.Point;
using Timer = System.Windows.Forms.Timer;
namespace WinFormsApp1
{
public partial class Form6 : Form
{
private Bitmap sourceBitmap;
private Point startPoint;
private Point currentPoint;
private Point lastMousePosition;
private bool isDrawing = false;
private bool isMoving = false;
private int radius = 0;
// 用于判断是否点击在圆上的容差值(像素)
private const int HitTestTolerance = 1;
public Form6()
{
InitializeComponent();
SetupEventHandlers();
}
private void SetupEventHandlers()
{
pictureBox1.MouseDown += PictureBox1_MouseDown;
pictureBox1.MouseMove += PictureBox1_MouseMove;
pictureBox1.MouseUp += PictureBox1_MouseUp;
pictureBox1.Paint += PictureBox1_Paint;
btnCapture.Click += CaptureButton_Click;
// 设置双缓冲,减少闪烁
typeof(PictureBox).InvokeMember("DoubleBuffered",
BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, pictureBox1, new object[] { true });
}
private bool IsPointNearCircle(Point point)
{
if (radius == 0) return false;
// 计算点击位置到圆心的距离
double distance = Math.Sqrt(
Math.Pow(point.X - startPoint.X, 2) +
Math.Pow(point.Y - startPoint.Y, 2));
// 判断点击位置是否在圆的边界附近
return Math.Abs(distance - radius) < HitTestTolerance;
}
private bool IsPointInsideCircle(Point point)
{
if (radius == 0) return false;
// 计算点击位置到圆心的距离
double distance = Math.Sqrt(
Math.Pow(point.X - startPoint.X, 2) +
Math.Pow(point.Y - startPoint.Y, 2));
// 判断点击位置是否在圆内
return distance < radius;
}
private void PictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (sourceBitmap == null) return;
if (e.Button == MouseButtons.Left)
{
// 检查是否点击在已有的圆上或圆内
if (radius > 0 && (IsPointNearCircle(e.Location) || IsPointInsideCircle(e.Location)))
{
isMoving = true;
lastMousePosition = e.Location;
pictureBox1.Cursor = Cursors.SizeAll;
}
else
{
isDrawing = true;
startPoint = e.Location;
currentPoint = e.Location;
radius = 0;
}
pictureBox1.Invalidate();
}
}
private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (sourceBitmap == null) return;
if (isDrawing)
{
currentPoint = e.Location;
radius = (int)Math.Sqrt(
Math.Pow(currentPoint.X - startPoint.X, 2) +
Math.Pow(currentPoint.Y - startPoint.Y, 2));
pictureBox1.Invalidate();
}
else if (isMoving)
{
// 计算移动的距离
int deltaX = e.X - lastMousePosition.X;
int deltaY = e.Y - lastMousePosition.Y;
// 更新圆心位置
startPoint = new Point(startPoint.X + deltaX, startPoint.Y + deltaY);
// 更新上一次鼠标位置
lastMousePosition = e.Location;
pictureBox1.Invalidate();
}
else
{
// 更新鼠标样式
if (radius > 0 && (IsPointNearCircle(e.Location) || IsPointInsideCircle(e.Location)))
{
pictureBox1.Cursor = Cursors.SizeAll;
}
else
{
pictureBox1.Cursor = Cursors.Default;
}
}
}
private void PictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
isDrawing = false;
isMoving = false;
pictureBox1.Cursor = Cursors.Default;
pictureBox1.Invalidate();
}
}
private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
if (sourceBitmap != null && radius > 0)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
using (Pen pen = new Pen(Color.Red, 2))
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
// 绘制圆形
e.Graphics.DrawEllipse(pen,
startPoint.X - radius,
startPoint.Y - radius,
radius * 2,
radius * 2);
// 绘制圆心标记
if (isMoving)
{
e.Graphics.DrawLine(pen,
startPoint.X - 5, startPoint.Y,
startPoint.X + 5, startPoint.Y);
e.Graphics.DrawLine(pen,
startPoint.X, startPoint.Y - 5,
startPoint.X, startPoint.Y + 5);
}
}
}
}
private (float scaleX, float scaleY, float offsetX, float offsetY) GetImageScaleAndOffset()
{
if (sourceBitmap == null || pictureBox1.Image == null) return (1, 1, 0, 0);
// 计算图片在 PictureBox 中的实际显示尺寸
float containerRatio = (float)pictureBox1.Width / pictureBox1.Height;
float imageRatio = (float)sourceBitmap.Width / sourceBitmap.Height;
float scaleX, scaleY, offsetX = 0, offsetY = 0;
if (containerRatio > imageRatio)
{
// 图片高度适应 PictureBox,宽度居中
scaleY = (float)sourceBitmap.Height / pictureBox1.Height;
scaleX = scaleY;
offsetX = (pictureBox1.Width - (sourceBitmap.Width / scaleX)) / 2;
}
else
{
// 图片宽度适应 PictureBox,高度居中
scaleX = (float)sourceBitmap.Width / pictureBox1.Width;
scaleY = scaleX;
offsetY = (pictureBox1.Height - (sourceBitmap.Height / scaleY)) / 2;
}
return (scaleX, scaleY, offsetX, offsetY);
}
private void CaptureButton_Click(object sender, EventArgs e)
{
if (sourceBitmap != null && radius > 0)
{
try
{
using (Mat sourceMat = BitmapConverter.ToMat(sourceBitmap))
using (Mat mask = new Mat(sourceMat.Size(), MatType.CV_8UC1, Scalar.Black))
using (Mat result = new Mat(sourceMat.Size(), sourceMat.Type(), Scalar.Black))
{
var (scaleX, scaleY, offsetX, offsetY) = GetImageScaleAndOffset();
// 计算实际图片中的坐标
int actualX = (int)((startPoint.X - offsetX) * scaleX);
int actualY = (int)((startPoint.Y - offsetY) * scaleY);
int actualRadius = (int)(radius * scaleX); // 使用相同的缩放比例
// 在掩码上绘制白色圆形
Cv2.Circle(
mask,
new OpenCvSharp.Point(actualX, actualY),
actualRadius,
Scalar.White,
-1);
// 应用掩码
sourceMat.CopyTo(result, mask);
// 裁剪圆形区域
OpenCvSharp.Rect roi = new OpenCvSharp.Rect(
actualX - actualRadius,
actualY - actualRadius,
actualRadius * 2,
actualRadius * 2);
// 确保ROI在图像范围内
roi.X = Math.Max(0, roi.X);
roi.Y = Math.Max(0, roi.Y);
roi.Width = Math.Min(result.Width - roi.X, roi.Width);
roi.Height = Math.Min(result.Height - roi.Y, roi.Height);
if (roi.Width > 0 && roi.Height > 0)
{
using (Mat cropped = new Mat(result, roi))
{
pictureBox2.Image?.Dispose();
pictureBox2.Image = BitmapConverter.ToBitmap(cropped);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"截取图片时出错:{ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
sourceBitmap?.Dispose();
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
}
private void btnLoadImage_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif";
openFileDialog.Title = "选择图片";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
try
{
// 释放之前的图片资源
sourceBitmap?.Dispose();
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
// 加载新图片
sourceBitmap = new Bitmap(openFileDialog.FileName);
pictureBox1.Image = new Bitmap(sourceBitmap);
// 启用截取按钮
btnCapture.Enabled = true;
// 重置绘图状态
isDrawing = false;
pictureBox1.Invalidate();
}
catch (Exception ex)
{
MessageBox.Show($"加载图片时出错:{ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
}
}
这个实现提供了一个基础的圆形区域选择和截取功能,可以根据实际需求进行扩展和优化。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!