c#WinForm uses OpencvSharp4 to achieve simple edge grabbing

Environment: VS2019, OpencvSharp4 4.5.5.20211231, .NET Framework 4.8

interface design:

The image display uses the picturebox control, which is the basic control of windows

Show results:

 

      The image is drawn by myself, so the effect of grasping is better. For other pictures, you may need to adjust the relevant parameters, and the effect may not be so good

Implementation principle: Select the ROI in the image, deduct the image corresponding to the ROI part from the original image, and then perform edge processing and other operations on the deducted image to obtain the edge and fitting line, and finally combine the edge and fitting line on the original image Just draw the line. Note that the obtained edges are coordinates relative to the ROI area, which need to be converted into coordinates relative to the original image, just add the coordinates of the ROI.

Main part code:

The defined ROI class pays attention to the relative positions of the four points

    public class ROI
    { 
        // 四个点的顺序关系
        //  1---2
        //  |   |
        //  3---4
        public OpenCvSharp.Point FirstPoint { get; set; } = new OpenCvSharp.Point(0, 0);
        public OpenCvSharp.Point SecondPoint { get; set; } = new OpenCvSharp.Point(0, 0);
        public OpenCvSharp.Point ThirdPoint { get; set; } = new OpenCvSharp.Point(0, 0);
        public OpenCvSharp.Point FourthPoint { get; set; } = new OpenCvSharp.Point(0, 0);

        public OpenCvSharp.Point2f Center
        {
            get
            {
                OpenCvSharp.Point2f center = new OpenCvSharp.Point2f();
                center.X = (float)((FirstPoint.X + SecondPoint.X + ThirdPoint.X + FourthPoint.X) / 4.0);
                center.Y = (float)((FirstPoint.Y + SecondPoint.Y + ThirdPoint.Y + FourthPoint.Y) / 4.0);
                return center;
            }
        }

        public OpenCvSharp.Size2f Size
        {
            get
            {
                return new OpenCvSharp.Size2f(Width, Height);
            }
        }
        public int XLeft
        {
            get { return FirstPoint.X; }
        }
        public int YTop
        {
            get { return FirstPoint.Y; }
        }
        public int XRight
        {
            get { return FourthPoint.X; }
        }
        public int YBottom
        {
            get { return FourthPoint.Y; }
        }
 
        public double Width
        {
            get { return FourthPoint.X - FirstPoint.X; } 
        }
        public double Height
        {
            get { return FourthPoint.Y - FirstPoint.Y; } 
        }

        public void Reset()
        {
            FirstPoint = new OpenCvSharp.Point(0, 0);
            SecondPoint = new OpenCvSharp.Point(0, 0);
            ThirdPoint = new OpenCvSharp.Point(0, 0);
            FourthPoint = new OpenCvSharp.Point(0, 0);
        }

        // 四个点全为0 则判断为空
        public bool IsNull()
        {
            bool en = true;
            en = en && FirstPoint == new OpenCvSharp.Point(0, 0);
            en = en && SecondPoint == new OpenCvSharp.Point(0, 0);
            en = en && ThirdPoint == new OpenCvSharp.Point(0, 0);
            en = en && FourthPoint == new OpenCvSharp.Point(0, 0);
            return en;
        }

        public OpenCvSharp.Point2f[] GetCoutonrs2f()
        {
            OpenCvSharp.Point2f[] coutonrs = new OpenCvSharp.Point2f[4];
            coutonrs[0] = FirstPoint;
            coutonrs[1] = SecondPoint;
            coutonrs[2] = FourthPoint;
            coutonrs[3] = ThirdPoint;
            return coutonrs;
        }

        public OpenCvSharp.Point[] GetCoutonrs()
        {
            OpenCvSharp.Point[] coutonrs = new OpenCvSharp.Point[4];
            coutonrs[0] = FirstPoint;
            coutonrs[1] = SecondPoint;
            coutonrs[2] = FourthPoint;
            coutonrs[3] = ThirdPoint;
            return coutonrs;
        } 
    }

Related variables:

    public enum eDirections  // ROI移动方向
    {
        NULL = 0,
        上 = 1,
        下 = 2,
        左 = 3,
        右 = 4
    }

    //ROI大小调整方式
    public enum eResizeMode
    {
        All = 0, // 长宽一起调整
        Width = 1, // 只变宽度 即 矩形的长
        Height = 2, //  只变高度 即 矩形的宽
    }


    public class yVars
    {
        public static string OriImg; // 原图

        public static bool IsDrawEdgeOK = false; 
        public static bool pbxMouseDown = false;
        public static bool IsMouseMove = false;
        public static bool IsSelectROIOK = false;
        public static bool IsMouseUp = false;

        public static int step; //ROI区域移动步长
        public static eDirections direct = eDirections.NULL;

        public static int ROINum = 1; // 操作第一个ROI还是第二个ROI
        public static bool IsSelectingROI = false;
        //  
        public static bool IsSelectROI_1 = false;
        public static bool IsSelectROI_1_OK = false;

        public static bool IsSelectROI_2 = false;
        public static bool IsSelectROI_2_OK = false;

        public static ROI myROI_1 = new ROI();
        public static ROI myROI_2 = new ROI();
    }

Drawing of ROI:

 For a rectangular ROI, we only need two points to define a rectangle.

The position we obtained is the position of the mouse relative to the picturebox, which needs to be converted into coordinates relative to the image. The sizemode of my picturebox is stretchImage, so it can be transformed proportionally.

 In the mousedown event of the picturebox, the first position that the mouse is pressed is recorded as the first point of the ROI.

I write the process of drawing ROI in the mousemove event, so that the ROI area can always be displayed when the mouse moves after the first point is determined

        private void pbxImgShow_MouseMove(object sender, MouseEventArgs e)
        {
            if (yVars.IsSelectROI_1 == false && yVars.IsSelectROI_2 == false)
                return;
            if (yVars.pbxMouseDown == false)
                return;
            if (yVars.IsMouseUp == true)
                return;
            int mx = 0, my = 0;
            Mat mm = new Mat(yVars.OriImg);
            // 鼠标相对于picturebox的位置
            mx = Frm_Main.Instance.pbxImgShow.PointToClient(Control.MousePosition).X;
            my = Frm_Main.Instance.pbxImgShow.PointToClient(Control.MousePosition).Y;

            // 鼠标移动时 位置在 picturebox 中就画出对应的ROI形状
            if (mx < pbxImgShow.Width && my < pbxImgShow.Height)
            {
                //转成在图片上的位置
                double xx = mx * mm.Width * 1.0 / Frm_Main.Instance.pbxImgShow.Width;
                double yy = my * mm.Height * 1.0 / Frm_Main.Instance.pbxImgShow.Height;

                if (yVars.IsSelectROI_1 == true)
                {
                    yVars.myROI_1.FourthPoint = new OpenCvSharp.Point(xx, yy);
                    yVars.myROI_1.SecondPoint = new OpenCvSharp.Point(xx, yVars.myROI_1.FirstPoint.Y);
                    yVars.myROI_1.ThirdPoint = new OpenCvSharp.Point(yVars.myROI_1.FirstPoint.X, yy);

                    mm = yActions.DrawROIMat(mm, yVars.myROI_1);
                    yVars.IsSelectROI_1_OK = true;
                }
                else if (yVars.IsSelectROI_2 == true)
                {
                    yVars.myROI_2.FourthPoint = new OpenCvSharp.Point(xx, yy);
                    yVars.myROI_2.SecondPoint = new OpenCvSharp.Point(xx, yVars.myROI_2.FirstPoint.Y);
                    yVars.myROI_2.ThirdPoint = new OpenCvSharp.Point(yVars.myROI_2.FirstPoint.X, yy);
                    mm = yActions.DrawROIMat(mm, yVars.myROI_2);
                    yVars.IsSelectROI_2_OK = true;
                }
                yVars.IsMouseMove = true;
            }
            else // 释放鼠标时的点位不在picturebox中 将相关变量值清空 
            {
                if (yVars.IsSelectROI_1 == true)
                {
                    yVars.myROI_1.Reset();

                    yVars.IsSelectROI_1_OK = false;
                }
                else if (yVars.IsSelectROI_2 == true)
                {
                    yVars.myROI_2.Reset();
                    yVars.IsSelectROI_2_OK = false;
                }
            }
            pbxImgShow.Image = yImgConvert.MatToBitmap(mm);
            mm.Release();
        }

The mat defined in the process of threads or loops should be released in time.

In the mouseup event, the drawing is completed. Note that the first point and the second point selected are the FirstPoint and FourthPoint of the ROI respectively. The relative positions of the two points must be determined. Make sure that the FirstPoint is the point in the upper left corner and the FourthPoint is the point in the lower right corner. , if not, re-assign FirstPoint and FourthPoint, FirstPoint is the point with the smallest x and y of the two points, and FourthPoint is the point with the largest x and y of the two points.

After the ROI is drawn, its position and size can be adjusted accordingly.

        public static Mat DrawROIMat(Mat src, ROI rOI, Scalar? scalar = null, int thickness = 1, LineTypes lineTypes = LineTypes.AntiAlias)
        {
            Scalar sc = scalar ?? Scalar.Red;
            Cv2.Line(src, rOI.FirstPoint, rOI.SecondPoint, sc, thickness, lineTypes);
            Cv2.Line(src, rOI.SecondPoint, rOI.FourthPoint, sc, thickness, lineTypes);
            Cv2.Line(src, rOI.FourthPoint, rOI.ThirdPoint, sc, thickness, lineTypes);
            Cv2.Line(src, rOI.ThirdPoint, rOI.FirstPoint, sc, thickness, lineTypes);
            return src;
        }

Adjust the position: The main idea is to add and subtract the corresponding directions of the coordinates of the four points of the ROI, and the main overrun problem is enough.

        public static void ImgROIMove(Mat src, out Mat dstImg, ref ROI rOI, eDirections eDirections, double step, int gap = 3)
        {
            dstImg = new Mat();

            switch (eDirections)
            {
                case eDirections.NULL:
                    break;
                case eDirections.上:
                    if (rOI.YTop - step <= gap)
                    {
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, rOI.ThirdPoint.Y - rOI.YTop + gap);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, rOI.FourthPoint.Y - rOI.YTop + gap);
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, gap);
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, gap);
                    }
                    else
                    {
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, rOI.FirstPoint.Y - step);
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, rOI.SecondPoint.Y - step);
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, rOI.ThirdPoint.Y - step);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, rOI.FourthPoint.Y - step);
                    }
                    break;
                case eDirections.下:
                    if (rOI.YBottom + step >= src.Height - gap)
                    {
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, rOI.FirstPoint.Y + src.Height - rOI.YBottom - gap);
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, rOI.SecondPoint.Y + src.Height - rOI.YBottom - gap);
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, src.Height - gap);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, src.Height - gap);
                    }
                    else
                    {
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, rOI.FirstPoint.Y + step);
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, rOI.SecondPoint.Y + step);
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, rOI.ThirdPoint.Y + step);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, rOI.FourthPoint.Y + step);
                    }
                    break;
                case eDirections.左:
                    if (rOI.XLeft - step <= gap)
                    {
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X - rOI.XLeft + gap, rOI.SecondPoint.Y);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X - rOI.XLeft + gap, rOI.FourthPoint.Y);
                        rOI.ThirdPoint = new OpenCvSharp.Point(gap, rOI.ThirdPoint.Y);
                        rOI.FirstPoint = new OpenCvSharp.Point(gap, rOI.FirstPoint.Y);
                    }
                    else
                    {
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X - step, rOI.FirstPoint.Y);
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X - step, rOI.SecondPoint.Y);
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X - step, rOI.ThirdPoint.Y);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X - step, rOI.FourthPoint.Y);
                    }
                    break;
                case eDirections.右:
                    if (rOI.XRight + step >= src.Width - gap)
                    {
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X + src.Width - rOI.XRight - gap, rOI.FirstPoint.Y);
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X + src.Width - rOI.XRight - gap, rOI.ThirdPoint.Y);
                        rOI.FourthPoint = new OpenCvSharp.Point(src.Width - gap, rOI.FourthPoint.Y);
                        rOI.SecondPoint = new OpenCvSharp.Point(src.Width - gap, rOI.SecondPoint.Y);
                    }
                    else
                    {
                        rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X + step, rOI.FirstPoint.Y);
                        rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X + step, rOI.SecondPoint.Y);
                        rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X + step, rOI.ThirdPoint.Y);
                        rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X + step, rOI.FourthPoint.Y);
                    }
                    break;
                default:
                    break;
            }
            dstImg = yActions.DrawROIMat(src, rOI);
        }

Adjust the size: The main idea is that before and after the ROI size is adjusted, the coordinates of its center point remain unchanged, and the corresponding length and width change. We can use the OpenCvSharp.RotatedRect class, according to the coordinates of the center point, the corresponding size, and the tilt angle (a positive rectangle is 0). Finally, reassign the four vertices of RotatedRect to the four vertices of ROI. Pay attention relative positional relationship.

        public static void ImgROIResize(Mat src, out Mat dstImg, ref ROI rOI, bool IsAdd, double step, eResizeMode eResizeMode)
        {
            dstImg = new Mat();
            double height = rOI.Height, width = rOI.Width; 
            if (IsAdd == true)
            {
                switch (eResizeMode)
                {
                    case eResizeMode.All:
                        height = rOI.Height + step;
                        width = rOI.Width + step;
                        break;
                    case eResizeMode.Width:
                        width = rOI.Width + step;
                        break;
                    case eResizeMode.Height:
                        height = rOI.Height + step;
                        break;
                }
            }
            else
            {
                switch (eResizeMode)
                {
                    case eResizeMode.All:
                        height = rOI.Height - step;
                        width = rOI.Width - step;
                        break;
                    case eResizeMode.Width:
                        width = rOI.Width - step;
                        break;
                    case eResizeMode.Height:
                        height = rOI.Height - step;
                        break;
                }
            }

            OpenCvSharp.Size2f size = new Size2f(width, height);
            OpenCvSharp.RotatedRect rotateRect = new RotatedRect(rOI.Center, size, 0);
            Point2f[] points = rotateRect.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)
                {
                    return;
                }
            }
            rOI.FirstPoint = new OpenCvSharp.Point(points[1].X, points[1].Y);
            rOI.SecondPoint = new OpenCvSharp.Point(points[2].X, points[2].Y);
            rOI.ThirdPoint = new OpenCvSharp.Point(points[0].X, points[0].Y);
            rOI.FourthPoint = new OpenCvSharp.Point(points[3].X, points[3].Y);
            dstImg = yActions.DrawROIMat(src, rOI); 
        }

After drawing and adjusting the ROI, buckle the corresponding ROI image from the original image,

        public static Mat GetROIMat(Mat mm, ROI rOI)
        {
            Mat mask = Mat.Zeros(mm.Size(), MatType.CV_8UC1);
            List<List<OpenCvSharp.Point>> pp = new List<List<OpenCvSharp.Point>>() {
               rOI.GetCoutonrs().ToList()
            }; 
            Cv2.FillPoly(mask, pp, new Scalar(255, 255, 255));
            OpenCvSharp.Rect rect = Cv2.BoundingRect(rOI.GetCoutonrs2f());
            if (rect.X <= 0) rect.X = 1;
            if (rect.Y <= 0) rect.Y = 0;
            if (rect.X + rect.Width > mm.Width)
                rect.Width = mm.Width - rect.X - 1;
            if (rect.Y + rect.Height > mm.Height)
                rect.Height = mm.Height - rect.Y - 1;
            Mat src = new Mat(mm, rect);
            Mat maskROI = new Mat(mask, rect);
            Mat dstImg = new Mat();
            Cv2.BitwiseAnd(src, src, dstImg, maskROI);
            return dstImg;
        }

Then perform edge detection, edge fitting and other operations on each buckled mat

part of the code

            coutonrs = yVars.myROI_1.GetCoutonrs2f();
            srcROIImg = yActions.GetROIMat(src, yVars.myROI_1);
            Cv2.CvtColor(srcROIImg, grayImg, ColorConversionCodes.RGB2GRAY);
            Cv2.Blur(grayImg, grayImg, new OpenCvSharp.Size(3, 3));
            Cv2.Canny(grayImg, cannyImg, param1, param2, param3, true);
            //获得轮廓 
            Cv2.FindContours(cannyImg, out contoursROI1, out hierarchly, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(0, 0));

            if (contoursROI1.Length == 0)
            {
                YXH._01.yMessagebox.ShowDialogCN("ROI_1未抓到边,请调整迟滞参数,或重新选择ROI区域");
                return;
            }
            // 获取轮廓后需要将点的坐标转换到原图上 此时的坐标是相对于ROI区域的坐标 
            // 即每个坐标需要加上ROI区域的左上角坐标 再将转化后的坐标添加进拟合集合内
            for (int i = 0; i < contoursROI1.Length; i++)
            {
                for (int j = 0; j < contoursROI1[i].Length; j++)
                {
                    contoursROI1[i][j] += yVars.myROI_1.FirstPoint;
                    ROI_1_Points.Add(contoursROI1[i][j]);
                    AllPoints.Add(contoursROI1[i][j]);
                }
            }

After the operation is completed, draw according to what you want to display on the interface.

The main implementation process is roughly like this. If you have any questions, you are welcome to correct and discuss.

Guess you like

Origin blog.csdn.net/Iawfy_/article/details/124289809