环境: VS2019 , OpencvSharp4 4.5.5.20211231 , .NET Framework 4.8
界面设计:
图像显示用的是picturebox 控件都是windows基本控件
效果展示:
图像是自己画图画的 所以抓的效果比较好 。其他图片的话可能需要调整一下相关参数,效果可能达不到这么好
实现原理: 在图像中选择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> pp = new List
>() { 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]); } }
操作完成后再根据想要在界面上显示的进行相应的绘制即可。
到此这篇关于c#中WinForm使用OpencvSharp4实现简易抓边的文章就介绍到这了,更多相关c# OpencvSharp4 抓边内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!