在加载好的图像上可以用鼠标绘制多段线,多边形围成的不规则的ROI区域,结束绘制后可以将“抠”出来的区域呈现在另外一个控件上。开始绘制时判断是否有图像,因为是在图像上绘制;启用编辑模式和绘画状态下才能绘画;右键结束绘制。
#region 绘制动作的具体实现
private List<Point> points = new List<Point>(); // 组成多段线的点
private Mat canvas = new Mat(); // 绘图层
private bool isEditMode = false; // 是否是编辑模式
private bool isDrawing = false; // 是否正在绘制
// 开始绘制按钮
private void btnDrawing_Click(object sender, EventArgs e)
{
if (originalImage.Empty())
{
MessageBox.Show("请先选择图像文件!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
btnDrawing.Enabled = false;
pictureBox1.Invalidate(); // 刷新以触发重绘
MessageBox.Show("请开始绘制,右键结束绘制!");
isEditMode = true; // 编辑模式打开
btnDrawing.Enabled = false;// 绘制按钮置为不可用
isDrawing = false; // 绘画状态置为false
}
catch (Exception ex)
{
string msg = ex.Message;
}
}
// 鼠标事件
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (!isEditMode) return;
// 将鼠标坐标转换为图像坐标
Point tempPoint = new Point(e.Location.X, e.Location.Y); // 系统鼠标坐标点转换为OpenCvSharp.Point类型的点
var currentPoint = TranslateToImageCoordinates(tempPoint);
if (e.Button == MouseButtons.Left) // 左键绘制
{
if (!isDrawing)
{
isDrawing = true; // 开始新绘制
points.Clear(); // 清空点列表
points.Add(currentPoint); // 添加第一个点
}
else
{
// 判断是否接近起始点
if (points.Count > 1 && Distance(currentPoint, points[0]) < pictureBox1.Width / 10)
{
// 封闭多边形
points.Add(points[0]); // 连接到起始点
isDrawing = false; // 结束绘制
}
else
{
points.Add(currentPoint); // 添加当前点
}
}
pictureBox1.Invalidate(); // 触发重绘
}
else if (e.Button == MouseButtons.Right && points.Count > 2)
{
// 获取最小外接矩形
Rect boundingBox = Cv2.BoundingRect(points.ToArray());
// 创建一个与外接矩形大小相同的掩膜(全黑)
Mat mask = Mat.Zeros(boundingBox.Height, boundingBox.Width, MatType.CV_8UC1); // 单通道掩膜
// 计算多边形的坐标,转换为相对于最小外接矩形的位置
List<Point> shiftedPoint = points.Select(p => new Point(p.X - boundingBox.X, p.Y - boundingBox.Y)).ToList();
// 在掩膜中绘制多边形区域为白色(255),其他区域仍为黑色(0)
Cv2.FillPoly(mask, new[] { shiftedPoint.ToArray() }, new Scalar(240)); // 用255填充多边形区域
// 从原始图像中复制最小外接矩形区域
Mat roi = new Mat(originalImage, boundingBox);
// 使用掩膜从roi中复制多边形区域
//Mat result = new Mat(roi.Size(), roi.Type());
Mat result = Mat.Zeros(roi.Size(), roi.Type()); // 明确初始化为黑色(就不会有噪点了)
roi.CopyTo(result, mask); // 保留多边形区域内容,其他区域为黑色
isEditMode = false; // 禁用编辑模式
btnDrawing.Enabled = true; // 将按钮置为可用
isDrawing = false; // 正在绘制置为false
pictureBox2.Image = result.ToBitmap();
MessageBox.Show("绘制模式已关闭");
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (!isEditMode) return;
// 复制背景图片作为绘图层(不用图层的话,会直接画在原图上破坏了原图)
Mat tempcanvas = originalImage.Clone();
Cv2.CvtColor(tempcanvas, canvas,ColorConversionCodes.GRAY2RGB);// 绘图层转化为彩色
if (points.Count > 1)
{
// 绘制多边形或线条
Cv2.Polylines(canvas, new[] { points.ToArray() }, isClosed: !isDrawing, Scalar.Red, thickness: 2);
}
// 绘制所有点击的点为红色
foreach (var point in points)
{
// 定义矩形的大小
int rectSize = pictureBox1.Width/10;
Rect rect = new Rect(
point.X - rectSize / 2, // 矩形左上角的 X 坐标
point.Y - rectSize / 2, // 矩形左上角的 Y 坐标
rectSize, // 矩形的宽度
rectSize // 矩形的高度
);
// 在原始图像上绘制中空的小矩形
Cv2.Rectangle(canvas, rect, new Scalar(0, 0, 255), thickness: 3); // 红色
}
// 将绘制结果显示在 PictureBox 上
pictureBox1.Image = BitmapConverter.ToBitmap(canvas);
}
#endregion
#region 绘制中引用到的方法
// 两点距离
private double Distance(Point p1, Point p2)
{
return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
}
// 将PictureBox坐标转换为图像实际坐标
private Point TranslateToImageCoordinates(Point pbCoordinates)
{
var imgWidth = originalImage.Width;
var imgHeight = originalImage.Height;
var pbWidth = pictureBox1.ClientSize.Width;
var pbHeight = pictureBox1.ClientSize.Height;
// 计算缩放比例
float scale = Math.Min((float)pbWidth / imgWidth, (float)pbHeight / imgHeight);
// 计算图片在PictureBox居中时的偏移量
int offsetX = (int)((pbWidth - imgWidth * scale) / 2);
int offsetY = (int)((pbHeight - imgHeight * scale) / 2);
// 将PictureBox坐标转换为图像坐标
int imgX = (int)((pbCoordinates.X - offsetX) / scale);
int imgY = (int)((pbCoordinates.Y - offsetY) / scale);
// 限制坐标范围
imgX = Math.Max(0, Math.Min(imgX, imgWidth - 1));
imgY = Math.Max(0, Math.Min(imgY, imgHeight - 1));
return new Point(imgX, imgY);
}
#endregion
自我总结点滴,欢迎批评指正。