c#中WinForm使用OpencvSharp4实现简易抓边

作者:Iawfy_ 时间:2023-04-15 15:54:39 

环境: VS2019 , OpencvSharp4 4.5.5.20211231 , .NET Framework 4.8

界面设计:

c#中WinForm使用OpencvSharp4实现简易抓边

图像显示用的是picturebox 控件都是windows基本控件

效果展示:

c#中WinForm使用OpencvSharp4实现简易抓边

c#中WinForm使用OpencvSharp4实现简易抓边

图像是自己画图画的 所以抓的效果比较好 。其他图片的话可能需要调整一下相关参数,效果可能达不到这么好

实现原理: 在图像中选择ROI,从原图上把对应ROI部分的图像扣下来,然后对扣下来的图像进行边缘处理等操作,得到边缘和拟合线,最后在原图上将边缘和拟合线画出来即可。注意,得到的边缘是相对于ROI区域的坐标,需要转化成相对于原图的坐标才行,只需加上ROI的坐标即可。

主要部分代码:

定义的ROI类 注意一下四个点的相对位置

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;
       }
   }

相关变量:

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();
   }

ROI的绘制:

 矩形的ROI ,我们只需要两个点就能确定一个矩形。

我们获取到的位置是鼠标相对于picturebox的位置,需要转化成相对于图像的坐标,我的 picturebox 的 sizemode 是 stretchImage ,所以按比例转化过去就行。

 在 picturebox 的 mousedown 事件中 记录鼠标按下的第一个位置 为ROI的第一个点。

我把绘制ROI的过程写在 mousemove 事件里面,这样就能实现在确定第一个点后鼠标移动时ROI区域一直显示出来

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();
       }

在线程或者循环等过程中定义的 mat 要及时 Release 掉。

在 mouseup 事件中就绘制完成了 注意选择的第一点和第二点,分别是ROI的 FirstPoint 和 FourthPoint  ,两点的相对位置要确定好,要保证 FirstPoint 为左上角的点 FourthPoint 为右下角的点,不是的话 就对 FirstPoint 和 FourthPoint 重新赋值, FirstPoint 为两点的 x , y 最小的点 ,FourthPoint  为两点的 x , y 最大的点。

绘制完ROI后可以对其位置和大小进行相应的调整。

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;
       }

对位置进行调整: 主要思想就是对ROI的四个点的坐标相应方向进行加减即可,主要超限问题即可。

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);
       }

对大小进行调整: 主要思路是 ROI 大小调整前后,其中心点坐标不变,相应的长度和宽度变了。我们就可以采用 OpenCvSharp.RotatedRect 这个类,根据 中心点坐标,相应size,和倾斜角度(正矩形为0). 最后再把 RotatedRect 的四个顶点重新赋值给 ROI的四个顶点就好,注意一下点的相对位置关系。

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);
       }

绘制并调整好ROI后,从原图上将对应的ROI图像扣下来,

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;
       }

然后对每张扣下来的mat进行边缘检测 抓边拟合等操作

部分代码

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]);
               }
           }

操作完成后再根据想要在界面上显示的进行相应的绘制即可。

来源:https://blog.csdn.net/Iawfy_/article/details/124289809

标签:c#,OpencvSharp4,抓边
0
投稿

猜你喜欢

  • 配置SpringBoot方便的切换jar和war的方法示例

    2023-11-22 11:57:54
  • Java实现在Word指定位置插入分页符

    2021-06-29 03:24:44
  • Java Bean 作用域及它的几种类型介绍

    2022-12-02 20:39:42
  • C#操作XML方法详解

    2022-11-10 21:21:14
  • idea中如何去掉不想commit的文件

    2021-11-09 15:51:30
  • java中timer的schedule和scheduleAtFixedRate方法区别详解

    2023-05-25 10:47:45
  • springboot:接收date类型的参数方式

    2023-03-19 12:48:31
  • Android studio实现画板功能

    2022-08-04 21:30:39
  • Java实现json数据处理的常用脚本分享

    2022-07-27 15:21:39
  • 三道java新手入门面试题,通往自由的道路--锁+Volatile

    2023-09-04 20:33:42
  • 解决response.setHeader设置下载文件名无效的问题

    2021-08-15 20:43:54
  • 详解JDK自带javap命令反编译class文件和Jad反编译class文件(推荐使用jad)

    2021-12-24 00:29:29
  • mybatis @Alias注解在类上的使用方式(推荐)

    2023-11-20 00:30:03
  • 使用HandlerMethodArgumentResolver用于统一获取当前登录用户

    2023-04-20 12:09:16
  • 实例讲解Java读取一般文本文件和word文档的方法

    2023-11-13 05:09:53
  • Java多线程之同步工具类Exchanger

    2022-07-05 03:50:54
  • Android 弹出Dialog时隐藏状态栏和底部导航栏的方法

    2021-06-22 16:54:53
  • Jmeter环境搭建及安装步骤

    2021-11-03 21:06:10
  • java字符串格式化输出实例讲解

    2022-09-12 10:25:24
  • C#微信公众号开发 微信事件交互

    2023-04-22 21:18:31
  • asp之家 软件编程 m.aspxhome.com