C#,Winform,多段线,多边形绘制区域

在加载好的图像上可以用鼠标绘制多段线,多边形围成的不规则的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

自我总结点滴,欢迎批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值