OpencvSharp4实现各种图形ROI的提取

OpencvSharp4实现各种图形ROI的提取_第1张图片

OpencvSharp4实现各种图形ROI的提取_第2张图片

OpencvSharp4实现各种图形ROI的提取_第3张图片

实现效果如上图。

我将我之前的方法进行重构了,优化后方便后续的拓展。

下面就介绍下具体的实现吧。

我是在winform内实现的,当我们在界面上用鼠标选取ROI时,可以在picturebox的mousedown事件内记录下第一点,再在mousemove事件中获取实时的鼠标位置当做第二点,根据这两点我们就能得到一个矩形,即OpencvSharp内的Rect ,然后我们就可以在这个区域内进行我们的绘制,当然我还需要进行ROI的旋转 所以就用的 RotatedRect。两者其实差别并不大。

  因为目前这些形状都能通过一个矩形 和其他参数来确定唯一的,所以 我定义了 以RotatedRect 为基础的抽象类 ,通过后续不同的继承来实现不同的形状,主要原理我在代码的注释中都写上了

直接放代码:

这是我的基类 后面所有的形状都是继承这个类的

    /// 
    /// 父类ROI 可有不同形状 绘制方式根据一个矩形 可以在内部绘制不同的曲线 从而实现不同的ROI
    /// 
    abstract class ROI
    {
        public RotatedRect rotatedRect;

        /// 
        /// 包围ROI的最小正矩形
        /// 
        public Rect BoundingRect => Cv2.BoundingRect(GetPoints());

        /// 
        /// ROI的中心点 即所有顶点之和的平均值 
        /// 一般用于当做获取ROI掩膜图像进行水漫过程的种子点 因为该点绝大部分在ROI内部
        /// 
        public OpenCvSharp.Point Center
        {
            get
            {
                OpenCvSharp.Point point = new Point();
                foreach (Point pp in GetPoints())
                {
                    point += pp;
                }
                point = point * (1.0 / GetPoints().Length);
                return point;
            }
        }

        /// 
        /// 在图像上绘制出ROI
        /// 
        /// 输入图像
        /// 画笔颜色
        /// 画笔粗细
        /// 绘制好的图像
        public abstract Mat DrawROI(Mat src, Scalar scalar, int thinckness = 1);

        /// 
        /// 获取ROI顶点
        /// 
        /// 
        public abstract Point[] GetPoints();

        /// 
        /// 提取ROI图像 
        /// 大概步骤为 
        /// 1、得到掩膜图像 即 只有ROI部分不为0 其余地方均为0的图像
        /// 2、确定ROI范围 即 得到包围ROI的最小正矩形 分别从输入图像和掩膜中提取对应的矩形部分
        /// 3、利用图像自身的与运算结合掩膜就能提取出掩膜部分图像了
        /// 
        /// 
        /// 
        public Mat ExtractROI(Mat src)
        {
            Mat mask = GetMaskFloodFill(src.Size());
            Rect rect = BoundingRect;
            src = new Mat(src, rect);
            Mat maskRoI = new Mat(mask, rect);
            Cv2.CvtColor(maskRoI, maskRoI, ColorConversionCodes.BGR2GRAY);
            Mat dstImg = new Mat();
            Cv2.BitwiseAnd(src, src, dstImg, maskRoI);
            return dstImg;
        }

        /// 
        /// 对ROI部分进行Inpaint 原理同提取ROI图像 只是将最后的与运算改为 Inpaint 方法
        /// 
        /// 
        /// 
        public Mat InPaintROI(Mat src)
        {
            Mat mask = GetMaskFloodFill(src.Size());
            Cv2.CvtColor(mask, mask, ColorConversionCodes.BGR2GRAY);
            Mat dstImg = new Mat();
            Cv2.Inpaint(src, mask, dstImg, 1, InpaintMethod.NS);
            return dstImg;
        }

        /// 
        /// 获取指定大小的ROI部分的掩膜单通道图像
        /// 
        /// 最小大小为 BoundingRect.Size 
        /// 
        public Mat GetMaskFloodFill(OpenCvSharp.Size size)
        {
            if (size.Width < BoundingRect.Size.Width || size.Height < BoundingRect.Size.Height)
                size = BoundingRect.Size;
            Mat mask = Mat.Zeros(size, MatType.CV_8UC3);
            mask = DrawROI(mask, Scalar.Red);
            //水漫的种子点 要求在ROI区域内部
            OpenCvSharp.Point pt = new OpenCvSharp.Point(Center.X, Center.Y);
            Cv2.FloodFill(mask, pt, Scalar.Red);
            mask.ConvertTo(mask, MatType.CV_8UC1);
            return mask;
        }

        /// 
        /// 重置 清零
        /// 
        public void Reset()
        {
            rotatedRect = new RotatedRect();
        }

        /// 
        /// 判断ROI是否超限图像
        /// 
        /// 
        /// 
        /// 
        public bool OverRun(Mat src, int gap = 3)
        {
            bool en = true;
            Rect rect = rotatedRect.BoundingRect();

            en = en && rect.X >= gap;
            en = en && rect.Y >= gap;
            en = en && rect.Right <= src.Width - gap;
            en = en && rect.Bottom <= src.Height - gap;
            return !en;
        }

        /// 
        /// 判断ROI是否为空 下面各种ROI都是在RotatedRect的里面进行绘制的 即只需要判断RotatedRect是否是空的
        /// 
        public bool IsNull => rotatedRect.Size == new Size2f(0, 0);

        /// 
        /// ROI的移动 得到ROI在指定图像内移动后的新图像
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public Mat MoveROI(Mat src, yVars.eMovingDirections directions, int step, int gap = 3)
        {
            Mat dstImg = src.Clone();
            OpenCvSharp.Point2f[] pts = rotatedRect.Points();
            switch (directions)
            {
                case yVars.eMovingDirections.上:
                    PointsArrRank(ref pts, false, true);
                    if (pts[0].Y - step <= gap)
                    {
                        rotatedRect.Center.Y -= (int)(pts[0].Y - gap);
                    }
                    else
                    {
                        rotatedRect.Center.Y -= (int)(step);
                    }
                    break;
                case yVars.eMovingDirections.下:
                    PointsArrRank(ref pts, false, false);
                    if (pts[0].Y + step >= src.Height - gap)
                    {
                        rotatedRect.Center.Y += (int)(src.Height - pts[0].Y - gap);
                    }
                    else
                    {
                        rotatedRect.Center.Y += step;
                    }
                    break;
                case yVars.eMovingDirections.左:
                    PointsArrRank(ref pts, true, true);
                    if (pts[0].X - step <= gap)
                    {
                        rotatedRect.Center.X -= (int)(pts[0].X - gap);
                    }
                    else
                    {
                        rotatedRect.Center.X -= step;
                    }
                    break;
                case yVars.eMovingDirections.右:
                    PointsArrRank(ref pts, true, false);
                    if (pts[0].X + step >= src.Width - gap)
                    {
                        rotatedRect.Center.X += (int)(src.Width - pts[0].X - gap);
                    }
                    else
                    {
                        rotatedRect.Center.X += step;
                    }
                    break;
                default:
                    break;
            }
            dstImg = DrawROI(dstImg, Scalar.Red, 1);
            return dstImg;
        }

        /// 
        /// ROI调整大小 ROI在图像内调整大小后的图像
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public Mat ResizeROI(Mat src, yVars.eResizeModes modes, bool IsAdd, int step)
        {
            Mat dstImg = src.Clone();
            switch (modes)
            {
                case yVars.eResizeModes.All:
                    rotatedRect.Size.Width += (IsAdd == true ? 1 : -1) * step;
                    rotatedRect.Size.Height += (IsAdd == true ? 1 : -1) * step;
                    break;
                case yVars.eResizeModes.Width:
                    rotatedRect.Size.Width += (IsAdd == true ? 1 : -1) * step;
                    break;
                case yVars.eResizeModes.Height:
                    rotatedRect.Size.Height += (IsAdd == true ? 1 : -1) * step;
                    break;
            }

            Point2f[] points = rotatedRect.Points(); // 获得矩形四个顶点坐标 
            //大小改变后的坐标需要判断是否超限了 
            for (int i = 0; i < points.Length; i++)
            {
                // 有一个超限后 大小需要更改回原来的  结束
                if (points[i].X <= 0 || points[i].Y <= 0 || points[i].X >= src.Width || points[i].Y >= src.Height)
                {
                    switch (modes)
                    {
                        //矩形的长宽同时变
                        case yVars.eResizeModes.All:
                            rotatedRect.Size.Width -= (IsAdd == true ? 1 : -1) * step;
                            rotatedRect.Size.Height -= (IsAdd == true ? 1 : -1) * step;
                            break;
                        // 只变矩形长 即宽度
                        case yVars.eResizeModes.Width:
                            rotatedRect.Size.Width -= (IsAdd == true ? 1 : -1) * step;
                            break;
                        // 只变矩形的宽 即高度
                        case yVars.eResizeModes.Height:
                            rotatedRect.Size.Height -= (IsAdd == true ? 1 : -1) * step;
                            break;
                    }
                }
            }
            dstImg = DrawROI(dstImg, Scalar.Red, 1);
            return dstImg;
        }

        /// 
        /// ROI的旋转 ROI在图像内旋转后的图像
        /// 
        /// 
        /// 
        /// 
        /// 
        public Mat RotatedROI(Mat src, yVars.eRotatedDirections directions, int step)
        {
            Mat dstImg = src.Clone();
            switch (directions)
            {
                case yVars.eRotatedDirections.顺时针:
                    rotatedRect.Angle += step;
                    break;
                case yVars.eRotatedDirections.逆时针:
                    rotatedRect.Angle -= step;
                    break;
            }
            Point2f[] points = rotatedRect.Points(); // 获得矩形四个顶点坐标 
            //旋转后的坐标需要判断是否超限了 
            for (int i = 0; i < points.Length; i++)
            {
                // 超限后旋转角度需要更改回原来的
                if (points[i].X <= 0 || points[i].Y <= 0 || points[i].X >= src.Width || points[i].Y >= src.Height)
                {
                    switch (directions)
                    {
                        case yVars.eRotatedDirections.顺时针:
                            rotatedRect.Angle -= step;
                            break;
                        case yVars.eRotatedDirections.逆时针:
                            rotatedRect.Angle += step;
                            break;
                    }
                }
            }
            dstImg = DrawROI(dstImg, Scalar.Red, 1);
            return dstImg;
        }

        /// 
        /// 根据移动方向对移动的所有点位进行排序  
        /// 例 往左运动 ,则按照升序和所有点位X坐标进行排序 
        ///  保证第一个点是X最小的那个点 即 运动方向最前面的那个点
        ///  那么 在判断ROI的移动过程中 只需要判断运动方向最前面的那个点有无超限即可 就不需要判断所有的点了
        /// 
        /// 
        /// 
        /// 
        private void PointsArrRank(ref OpenCvSharp.Point2f[] pts, bool IsX, bool IsAc = true)
        {
            OpenCvSharp.Point2f p0 = new Point2f();
            // X
            if (IsX == true)
            {
                if (IsAc == true)
                {
                    for (int i = 0; i < pts.Length - 1; i++)
                    {
                        for (int j = 0; j < pts.Length - 1 - i; j++)
                        {
                            if (pts[j].X >= pts[j + 1].X)
                            {
                                p0 = pts[j];
                                pts[j] = pts[j + 1];
                                pts[j + 1] = p0;
                            }
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < pts.Length - 1; i++)
                    {
                        for (int j = 0; j < pts.Length - 1 - i; j++)
                        {
                            if (pts[j].X <= pts[j + 1].X)
                            {
                                p0 = pts[j];
                                pts[j] = pts[j + 1];
                                pts[j + 1] = p0;
                            }
                        }
                    }
                }
            }
            else //Y
            {
                if (IsAc == true)
                {
                    for (int i = 0; i < pts.Length - 1; i++)
                    {
                        for (int j = 0; j < pts.Length - 1 - i; j++)
                        {
                            if (pts[j].Y >= pts[j + 1].Y)
                            {
                                p0 = pts[j];
                                pts[j] = pts[j + 1];
                                pts[j + 1] = p0;
                            }
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < pts.Length - 1; i++)
                    {
                        for (int j = 0; j < pts.Length - 1 - i; j++)
                        {
                            if (pts[j].Y <= pts[j + 1].Y)
                            {
                                p0 = pts[j];
                                pts[j] = pts[j + 1];
                                pts[j + 1] = p0;
                            }
                        }
                    }
                }
            }
        }
    }



       public enum eMovingDirections  // 移动方向
        {
            上 = 1,
            下 = 2,
            左 = 3,
            右 = 4
        }
        public enum eRotatedDirections //旋转方向
        {
            顺时针 = 0,
            逆时针 = 1,
        }
        public enum eResizeModes // 大小调整方式
        {
            All = 0, // 长宽同时增加
            Width = 1, // 只变宽度  长
            Height = 2, // 只变高度  宽
        }

      后续不同的形状只需要继承上面父类 再对其父类中的几个抽象方法进行不同的重载即可,主要是DrawROI()和GetPoints()这两个方法,进行ROI的提取只需要通过子类对象调用父类里面的ExtractROI()就行 其他方法也是类似的。

下面是我写的部分形状的ROI吧

椭圆

    /// 
    /// 椭圆ROI 即在矩形内绘制一个椭圆
    /// 
    class ROIEllipse : ROI
    {
        public ROIEllipse()
        {
            rotatedRect = new RotatedRect();
        }
        public ROIEllipse(RotatedRect rect)
        {
            rotatedRect = rect;
        }

        public override Mat DrawROI(Mat src, Scalar scalar, int thickness = 1)
        {
            Mat dstImg = src.Clone();
            try
            {
                Cv2.Ellipse(dstImg, rotatedRect, scalar, thickness, LineTypes.AntiAlias);
            }
            catch
            {
                dstImg = src.Clone();
            }

            return dstImg;
        }

        /// 
        ///  椭圆的顶点就返回rotatedRect的四个顶点 不能为空
        /// 
        /// 
        public override Point[] GetPoints()
        {
            return (from pp in rotatedRect.Points() select new OpenCvSharp.Point(pp.X, pp.Y)).ToArray();
        }
    }

五边形:

    /// 
    /// 五边形ROI 即在矩形内绘制一个指定方向的五边形 五个顶点都在边上
    ///  
    class ROIPentagon : ROI
    {
        //五边形顶点方向; 0 1 2 3  对应 上 右 下 左
        public int Flag { set; get; }

        public ROIPentagon()
        {
            rotatedRect = new RotatedRect();
            Flag = 0;
        }
        /// 
        ///  0 1 2 3  对应 上 右 下 左
        /// 
        /// 
        /// 五边形一个顶点所在位置方向 
        public ROIPentagon(RotatedRect rect, int flag = 0)
        {
            rotatedRect = rect;
            Flag = flag;
        }

        public override OpenCvSharp.Mat DrawROI(Mat src, Scalar scalar, int thickness = 1)
        {
            Mat dstImg = src.Clone();
            OpenCvSharp.Point[] points1 = GetPoints();
            try
            {
                for (int i = 0; i <= 4; i++)
                {
                    if (i != 4)
                    {
                        Cv2.Line(dstImg, points1[i], points1[i + 1], scalar, thickness, LineTypes.AntiAlias);
                    }
                    else
                    {
                        Cv2.Line(dstImg, points1[4], points1[0], scalar, thickness, LineTypes.AntiAlias);
                    }
                }
            }
            catch
            {
                dstImg = src.Clone();
            }
            return dstImg;
        }

        public override Point[] GetPoints()
        {
            OpenCvSharp.Point[] points = (from pp in rotatedRect.Points() select new OpenCvSharp.Point(pp.X, pp.Y)
                  ).ToArray();
            OpenCvSharp.Point[] resultPoints = new Point[5];
            switch (Flag)
            {
                default:
                case 0:
                    resultPoints[0] = points[1] * 0.5f + points[2] * 0.5f;
                    resultPoints[1] = points[2] * 0.625f + points[3] * 0.375f;
                    resultPoints[2] = points[3] * 0.833f + points[0] * 0.167f;
                    resultPoints[3] = points[3] * 0.167f + points[0] * 0.833f;
                    resultPoints[4] = points[0] * 0.375f + points[1] * 0.625f;
                    break;
                case 1:
                    resultPoints[0] = points[2] * 0.5f + points[3] * 0.5f;
                    resultPoints[1] = points[3] * 0.625f + points[0] * 0.375f;
                    resultPoints[2] = points[0] * 0.833f + points[1] * 0.167f;
                    resultPoints[3] = points[0] * 0.167f + points[1] * 0.833f;
                    resultPoints[4] = points[1] * 0.375f + points[2] * 0.625f;
                    break;
                case 2:
                    resultPoints[0] = points[3] * 0.5f + points[0] * 0.5f;
                    resultPoints[1] = points[0] * 0.625f + points[1] * 0.375f;
                    resultPoints[2] = points[1] * 0.833f + points[2] * 0.167f;
                    resultPoints[3] = points[1] * 0.167f + points[2] * 0.833f;
                    resultPoints[4] = points[2] * 0.375f + points[3] * 0.625f;
                    break;
                case 3:
                    resultPoints[0] = points[0] * 0.5f + points[1] * 0.5f;
                    resultPoints[1] = points[1] * 0.625f + points[2] * 0.375f;
                    resultPoints[2] = points[2] * 0.833f + points[3] * 0.167f;
                    resultPoints[3] = points[2] * 0.167f + points[3] * 0.833f;
                    resultPoints[4] = points[3] * 0.375f + points[0] * 0.625f;
                    break;
            }
            return resultPoints;
        }

        public new void Reset()
        {
            base.Reset();
            Flag = 0;
        }
    }

四角星

  // 四角星
    class ROIShuriken : ROI
    {
        public float Ratio { set; get; } = 0.75f;

        public ROIShuriken()
        {
            rotatedRect = new RotatedRect();
        }
        /// 
        /// 通过一个 RotatedRect 和比例来确定唯一的四角星
        /// 
        ///  
        /// 比例
        public ROIShuriken(RotatedRect rect, float ratio = 0.75f)
        {
            rotatedRect = rect;
            if (ratio < 0 || ratio > 0.75) ratio = 0.75f;
            Ratio = ratio;
        }

        /// 
        /// 在图像上画出该ROI
        /// 
        /// 输入图像
        /// 画笔颜色
        /// 画笔粗细
        /// 绘制好的图像,若绘制失败则返回原图
        public override OpenCvSharp.Mat DrawROI(Mat src, Scalar scalar, int thickness = 1)
        {
            Mat dstImg = src.Clone();

            float angle = rotatedRect.Angle;

            OpenCvSharp.Point[] points = (from x in rotatedRect.Points()
                                          select new OpenCvSharp.Point(x.X, x.Y)).ToArray();

            OpenCvSharp.Point[] points1 = GetPoints();
            try
            {
                for (int i = 0; i <= points1.Length-1; i++)
                {
                    if (i != points1.Length - 1)
                    {
                        Cv2.Line(dstImg, points1[i], points1[i + 1], scalar, thickness, LineTypes.AntiAlias);
                    }
                    else
                    {
                        Cv2.Line(dstImg, points1[points1.Length - 1], points1[0], scalar, thickness, LineTypes.AntiAlias);
                    }
                }
            }
            catch
            {
                dstImg = src.Clone();
            }
            return dstImg;
        }

        /// 
        /// 四角星的八个顶点 按顺时针顺序
        /// 
        /// 
        public override Point[] GetPoints()
        {
            OpenCvSharp.Point[] points = (from pp in rotatedRect.Points() select new OpenCvSharp.Point(pp.X, pp.Y)
                ).ToArray();
            OpenCvSharp.Point[] resultPoints = new Point[8];

            OpenCvSharp.Point cc = new Point(rotatedRect.Center.X, rotatedRect.Center.Y);

            resultPoints[0] = (points[1] + points[2]) * 0.5;
            resultPoints[1] = cc * Ratio + points[2] * (1 - Ratio);
            resultPoints[2] = points[2] * 0.5 + points[3] * 0.5; 
            resultPoints[3] = cc * Ratio + points[3] * (1 - Ratio);
            resultPoints[4] = points[0] * 0.5 + points[3] * 0.5;
            resultPoints[5] = cc * Ratio + points[0] * (1 - Ratio);
            resultPoints[6] = points[0] * 0.5 + points[1] * 0.5;
            resultPoints[7] = cc * Ratio + points[1] * (1 - Ratio);
            return resultPoints;
        }
    }

 其他形状的ROI原理都跟上面类似 只需要根据矩形的四个顶点来确定好ROI所有顶点的位置即可,在一一画线连起来即可。

你可能感兴趣的:(图像处理,c#,winform,opencv)