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

界面设计:

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

效果展示:

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

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

主要部分代码:

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

    public class ROI{ // 四个点的顺序关系//  1---2//  |   |//  3---4public 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还是第二个ROIpublic 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]);}}

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

主要实现的过程大致就这样了,如有问题,欢迎各位指正和讨论。

c#WinForm使用OpencvSharp4实现简易抓边相关推荐

  1. wireshark简易抓包分析——ping指定大小包长多28Byte

    wireshark简易抓包分析 测试ping时会发现一个现象: 在指定ping包长度后,实际发出的包总长=指定ping包长度+28 为什么ping -s指定大小n后,发出的包长为n+28呢? 先在不涉 ...

  2. android抓包 dns,利用 dns 实现 app简易抓包

    场景:对H5打包的 app实现抓包 环境:centos 8 follow the guide: https://www.linuxtechi.com/setup-bind-server-centos- ...

  3. Java实现抓娃娃_简易抓娃娃机H5的代码实现

    本文的实现主要用PixiJs实现.PixiJS是比较著名的2D渲染引擎.可以参照教程做事前学习了解. 准备工作 第一步是图片资源.这些都是从现有的抓娃娃h5中找到的一些png资源.具体可以查看资源目录 ...

  4. Winform Windows Media Player 简易播放器

    新手上路,高手勿进! 窗体设计: 实现效果: 实现代码: using System; using System.Collections.Generic; using System.ComponentM ...

  5. C#Opencvsharp4实现几种图像特效

    环境配置:VS2019. Winform.Opencvsharp4 4.5.5.20211231 . .Net Framework 4.8 实验原图: 1.毛玻璃特效 . 原理: 用该像素点领域内的随 ...

  6. 爬虫类Chrome去除前端无限debugger反调试(轻松分析算法)

    实际问题与需求 想对网站进行爬虫操作或分析算法时,打开F12和往常不同的是,浏览器自动断点,导致无法正常分析js,如图可知,浏览debugger处于暂停状态,这是前端浏对非授权调试者在debug时造成 ...

  7. VC POST表单——登录验证新浪邮箱

    1.本机环境: Windows XP SP3.ADSL 2.开发工具: WildPackets OmniPeek V5.1.4 Visual C++ 6.0 IE6.0 FlexEdit V2.3.1 ...

  8. Jsoup抓取网页数据完成一个简易的Android新闻APP

    前言:作为一个篮球迷,每天必刷NBA新闻.用了那么多新闻APP,就想自己能不能也做个简易的新闻APP.于是便使用Jsoup抓取了虎扑NBA新闻的数据,完成了一个简易的新闻APP.虽然没什么技术含量,但 ...

  9. 动态加载子节点_简易数据分析 10 | Web Scraper 翻页—抓取「滚动加载」类型网页...

    这是简易数据分析系列的第 10 篇文章. 原文首发于博客园:简易数据分析 10. 友情提示:这一篇文章的内容较多,信息量比较大,希望大家学习的时候多看几遍. 我们在刷朋友圈刷微博的时候,总会强调一个『 ...

最新文章

  1. 中山大学提出SimAM:无参Attention!助力分类/检测/分割涨点!
  2. 软考自查:数据库设计
  3. Python随笔-切片
  4. HBase原理和安装
  5. java 外观模式_Java设计模式11:外观模式
  6. 二维树状数组的区间加减及查询 tyvj 1716 上帝造题的七分钟
  7. cartographer探秘第四章之代码解析(一) --- SLAM处理过程 --- 文章索引
  8. C++学习笔记——opencv2模块(图像处理)
  9. 盘点国内最具实力的双足仿人机器人研发团队有哪些?
  10. php 微信 other,PHP——仿造微信OpenId
  11. android 正则表达式6-16位字母或数字,一个匹配8-16位数字和字母密码的正则表达式...
  12. 删除文件夹时,报错“错误ox80070091:目录不是空的”,该如何解决?
  13. iOS 开发中的争议(二)--唐巧
  14. 计算机毕业设计JAVA项目实训管理系统设计与实现mybatis+源码+调试部署+系统+数据库+lw
  15. 如何系统化学Python?
  16. php显示出温度最好的城市,PHP微信开发之查询城市天气
  17. 正确安装AMD X2双核驱动及补丁的方法
  18. 各国国旗知识学习,你认识多少,快来看看吧
  19. 2021教育信息化新基建
  20. JVM日历:Java 2018大事回顾

热门文章

  1. 【文文殿下】ExBSGS
  2. python爬虫-爬取股票贴吧帖子
  3. Fumark支持SLI双卡测试设置
  4. 【自制】我造了一台 钢 铁 侠 的 机 械 臂 !【硬核】
  5. D - Six Degrees of Cowvin Bacon(最小路径)
  6. Django快速入门教程
  7. 陀螺仪、加速计和磁力计
  8. 认证授权那点事儿 —— OAuth 2.0
  9. 华为笔记本能装手机App了,华为移动应用引擎(公测版)
  10. U盘写保护,终极解决办法,