当前位置: 首页 > news >正文

OpencvSharp 算子学习教案之 - Cv2.Erode

OpencvSharp 算子学习教案之 - Cv2.Erode

大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。

Cv2.Erode

  • 教案版本:V1.0
  • 面向对象:OpenCvSharp 初学者
  • 所属模块:imgproc
  • 源码位置:OpenCvSharp/Cv2/Cv2_imgproc.cs

摘要:Cv2.Erode 会让二值图或灰度图中的前景向内收缩,是理解形态学“腐蚀”概念的起点。本文用两组结构元素和迭代次数对比,帮助初学者观察白色区域如何变小,以及它和 Dilate 的相反关系。

1. 函数名称(带参数签名)

publicstaticvoidErode(InputArraysrc,OutputArraydst,InputArray?element,Point?anchor=null,intiterations=1,BorderTypesborderType=BorderTypes.Constant,Scalar?borderValue=null)

2. 函数用途

Cv2.Erode的作用,是让前景区域向内收缩。

它常见的用途有:

  1. 去掉孤立白点。
  2. 收紧目标轮廓。
  3. 断开非常细的连接。
  4. Dilate配合组成更复杂的形态学流程。

如果你想先建立“白色会变小”的直觉,先看 Erode 最合适。

3. 函数公式

对二值或灰度图来说,腐蚀可以理解成局部最小值操作:

dst(x,y)=min⁡(i,j)∈Bsrc(x+i,y+j) dst(x, y) = \min_{(i, j) \in B} src(x + i, y + j)dst(x,y)=(i,j)Bminsrc(x+i,y+j)

其中BBB是结构元素。直观地说,只要结构元素覆盖范围里有黑色背景,结果就更容易变黑。

4. 函数原理说明

Erode 的教学理解可以分成四步:

  1. 选择一个结构元素。
  2. 把结构元素放到图像上的每个位置。
  3. 只要覆盖范围里有背景,结果就可能被“压暗”。
  4. 重复iterations次后,收缩效果会更明显。

它的视觉效果通常是:白色区域变小,细小突起会被削掉,细线也更容易断开。

5. 参数含义解析

参数名类型必填含义
srcInputArray输入图像,通常是二值图或灰度图
dstOutputArray输出图像,大小和类型与输入一致
elementInputArray?结构元素,决定腐蚀的形状和范围
anchorPoint?锚点位置,默认是结构元素中心
iterationsint腐蚀次数
borderTypeBorderTypes边界外推方式
borderValueScalar?常量边界模式下的边界值

补充说明:

  1. element越大,腐蚀越明显。
  2. iterations越多,结果越“瘦”。
  3. 教学时常用MorphShapes.RectMorphShapes.EllipseMorphShapes.Cross
  4. 常量边界模式下,通常配合Cv2.MorphologyDefaultBorderValue()使用。

6. 应用场景列表

场景名场景说明典型用途
场景A:前景收缩观察白色区域如何变小教学入门
场景B:噪点清理去掉孤立白点二值修复
场景C:轮廓收紧让目标边界更干净预处理
场景D:与膨胀对照对比Dilate的相反效果理论学习

7. 函数使用示例

下面的 Console 程序演示Cv2.Erode。示例会对同一张二值图使用两组不同的结构元素和迭代次数,让你直接看到前景收缩的差异。

usingSystem;usingSystem.Globalization;usingSystem.Linq;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{/// <summary>/// 程序入口。/// </summary>privatestaticvoidMain(){// 先让控制台可以正确显示中文说明。Console.OutputEncoding=Encoding.UTF8;usingvarsource=CreateMorphologySourceImage();usingvarrectKernel=Cv2.GetStructuringElement(MorphShapes.Rect,newSize(3,3));usingvarellipseKernel=Cv2.GetStructuringElement(MorphShapes.Ellipse,newSize(7,7));usingvarrectEroded=newMat();usingvarellipseEroded=newMat();// 腐蚀会把白色前景往里缩,所以我们用两组参数做对比。Cv2.Erode(source,rectEroded,rectKernel,iterations:1,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());Cv2.Erode(source,ellipseEroded,ellipseKernel,iterations:2,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());Cv2.ImWrite("erode-source.png",source);Cv2.ImWrite("erode-rect.png",rectEroded);Cv2.ImWrite("erode-ellipse.png",ellipseEroded);Cv2.ImWrite("erode-ellipse-kernel.png",CreateKernelPreview(ellipseKernel));Console.WriteLine("场景A:Erode(InputArray src, OutputArray dst, InputArray? element, Point? anchor = null, int iterations = 1, BorderTypes borderType = BorderTypes.Constant, Scalar? borderValue = null)");Console.WriteLine("Erode 会让白色前景向内收缩。\n");Console.WriteLine($"源图:{DescribeBinaryMat(source)}");Console.WriteLine($"3x3 Rect 核:{DescribeKernel(rectKernel)}");Console.WriteLine($"7x7 Ellipse 核:{DescribeKernel(ellipseKernel)}");Console.WriteLine();AppendCaseReport("结果A:3x3 Rect + 1 次",source,rectEroded,"这组参数会轻微收缩前景,适合先观察边缘如何往内退。");Console.WriteLine();AppendCaseReport("结果B:7x7 Ellipse + 2 次",source,ellipseEroded,"这组参数更强,细线条和小白块更容易被吞掉。");}/// <summary>/// 创建一张适合形态学演示的二值图。/// </summary>privatestaticMatCreateMorphologySourceImage(){varcanvas=newMat(480,640,MatType.CV_8UC1,newScalar(0));// 这张图里包含大块前景、孔洞、细线和孤立区域,适合观察腐蚀的变化。Cv2.Rectangle(canvas,newRect(44,44,184,132),newScalar(255),-1,LineTypes.Link8);Cv2.Rectangle(canvas,newRect(84,82,34,34),newScalar(0),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(178,132),14,newScalar(0),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(348,118),58,newScalar(255),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(348,118),18,newScalar(0),-1,LineTypes.Link8);Cv2.Line(canvas,newPoint(62,282),newPoint(568,282),newScalar(255),6,LineTypes.Link8);Cv2.Line(canvas,newPoint(390,194),newPoint(514,92),newScalar(255),4,LineTypes.Link8);Cv2.Rectangle(canvas,newRect(68,248,124,58),newScalar(255),-1,LineTypes.Link8);Cv2.Rectangle(canvas,newRect(102,264,28,18),newScalar(0),-1,LineTypes.Link8);Cv2.Ellipse(canvas,newPoint(218,320),newSize(92,60),18,0,360,newScalar(255),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(488,314),44,newScalar(255),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(488,314),10,newScalar(0),-1,LineTypes.Link8);returnAddSaltAndPepperNoise(canvas,0.008,2026);}/// <summary>/// 给二值图叠加可重复的椒盐噪声。/// </summary>privatestaticMatAddSaltAndPepperNoise(Matsource,doublenoiseRatio,intseed){varrng=newRandom(seed);varnoisy=source.Clone();varflipCount=Math.Max(1,(int)Math.Round(noisy.Rows*noisy.Cols*noiseRatio));// 随机翻转像素值,会同时产生白点和黑洞。for(varindex=0;index<flipCount;index++){varrow=rng.Next(noisy.Rows);varcol=rng.Next(noisy.Cols);noisy.At<byte>(row,col)=noisy.At<byte>(row,col)==0?(byte)255:(byte)0;}returnnoisy;}/// <summary>/// 把结构元素缩放成便于观察的预览图。/// </summary>privatestaticMatCreateKernelPreview(Matkernel,intscale=28){usingvarkernel64=newMat();usingvarnormalized=newMat();usingvarenlarged=newMat();kernel.ConvertTo(kernel64,MatType.CV_64FC1);Cv2.Normalize(kernel64,normalized,0,255,NormTypes.MinMax,(int)MatType.CV_8UC1);Cv2.Resize(normalized,enlarged,newSize(kernel.Cols*scale,kernel.Rows*scale),0,0,InterpolationFlags.Nearest);returnenlarged;}/// <summary>/// 创建带标签的预览图。/// </summary>privatestaticMatCreateLabeledPreview(Matimage,stringlabel){varpreview=newMat();if(image.Channels()==1){Cv2.CvtColor(image,preview,ColorConversionCodes.GRAY2BGR);}else{preview=image.Clone();}Cv2.PutText(preview,label,newPoint(18,34),HersheyFonts.HersheySimplex,0.9,newScalar(255,255,255),3,LineTypes.AntiAlias);Cv2.PutText(preview,label,newPoint(18,34),HersheyFonts.HersheySimplex,0.9,newScalar(35,35,35),1,LineTypes.AntiAlias);returnpreview;}/// <summary>/// 把二值图格式化成便于教学阅读的摘要。/// </summary>privatestaticstringDescribeBinaryMat(Matimage){Cv2.MinMaxLoc(image,outvarminVal,outvarmaxVal);varactivePixels=Cv2.CountNonZero(image);varratio=activePixels*100.0/(image.Rows*image.Cols);return$"Size={image.Width}x{image.Height}, ForegroundPixels={activePixels}, ForegroundRatio={ratio.ToString("F2",CultureInfo.InvariantCulture)}%, Min={minVal.ToString("F0",CultureInfo.InvariantCulture)}, Max={maxVal.ToString("F0",CultureInfo.InvariantCulture)}";}/// <summary>/// 描述结构元素的核心信息。/// </summary>privatestaticstringDescribeKernel(Matkernel){Cv2.MinMaxLoc(kernel,outvarminVal,outvarmaxVal);varactiveCells=Cv2.CountNonZero(kernel);return$"Size={kernel.Width}x{kernel.Height}, ActiveCells={activeCells}, Min={minVal.ToString("F0",CultureInfo.InvariantCulture)}, Max={maxVal.ToString("F0",CultureInfo.InvariantCulture)}";}/// <summary>/// 追加一个完整的案例报告块。/// </summary>privatestaticvoidAppendCaseReport(stringtitle,Matsource,Matresult,stringcomment){Console.WriteLine(title);Console.WriteLine(comment);Console.WriteLine($"源图摘要:{DescribeBinaryMat(source)}");Console.WriteLine($"结果摘要:{DescribeBinaryMat(result)}");Console.WriteLine($"前景像素变化:{Cv2.CountNonZero(result)-Cv2.CountNonZero(source):+0;-0;0}");usingvardiff=newMat();Cv2.Absdiff(source,result,diff);Console.WriteLine($"实际被改动的像素数:{Cv2.CountNonZero(diff)}");}}

8. 注意事项

  1. 腐蚀会让前景变小,不要把它和膨胀的效果混淆。
  2. element的形状会直接影响腐蚀后的轮廓。
  3. iterations越多,结果越容易被“吃掉”。
  4. 如果你不理解边界行为,先默认使用Cv2.MorphologyDefaultBorderValue()

9. 调优建议

  1. 教学时先从小结构元素开始,比如3x3 Rect
  2. 如果只是想轻微收缩,不要把iterations设得太大。
  3. 如果希望保留更多圆角,可以试试Ellipse
  4. 如果前景已经很细,先观察腐蚀后是否还保留了你真正需要的结构。

10. 运行说明

  1. 如果你在控制台工程里运行本文示例,直接把代码放到Program.cs即可。
  2. 如果你在本仓库里学习,请直接打开 WPF 控件Cv2.Erode,点击“运行场景A”。
  3. WPF 示例会同时展示源图、结果图和结构元素预览,方便你直观看到腐蚀效果。

11. 常见错误排查

  1. 输入图不是二值图,却期待它像二值图那样腐蚀。
  2. 结构元素太大,导致目标细节直接被吞掉。
  3. 只盯着结果大小变化,却忘了观察轮廓形状变化。
  4. 没有把腐蚀和膨胀成对理解。
http://www.cnnetsun.cn/news/2020370.html

相关文章:

  • 从GDC论文到UE5蓝图:手把手实现‘惯性化’动画过渡,让你的角色动作更物理
  • QtDataVisualization实战:用C++快速打造一个可交互的3D图表演示器(附完整源码)
  • GLM-4.1V-9B-Base应用场景:在线教育题图自动解析与知识点标注
  • 从收音机到5G:三极管频率特性模型演变史,以及它为什么今天依然重要
  • 手把手复现:用Python和OpenCV一步步理解Marr视觉计算理论的“要素图”
  • 别再手动仿真了!手把手教你配置Vivado 2018.3与ModelSim SE的联合仿真环境
  • 网络‘活地图’实战:用PyHPEcw7库+D3.js打造可点击的拓扑仪表盘
  • 首文双拼的衍生版:首嵋双拼
  • Halcon灰度投影实战:用‘简单’模式搞定二维码定位,告别Blob分析的烦恼
  • 高校大学生论文查重工具全面测评
  • 英雄联盟智能工具包:League Akari 终极使用指南与实战技巧
  • 别再被CORS报错搞懵了!手把手教你用Nginx反向代理5分钟搞定前端跨域
  • DJI Osmo Nano 4/5评测:小巧便携功能强,成冒险家与vlogger新宠!
  • Nebula 1.10 版本发布:支持 IPv6 覆盖网络,解锁多项新特性!
  • 别再手动轮询了!STM32 HAL库串口DMA空闲中断接收不定长数据,一个函数就搞定
  • RK3568开发板实战:如何将定制好的Ubuntu系统打包成可烧写的rootfs镜像
  • Debian 11上Qt程序中文输入失效?手把手教你编译fcitx5-qt插件(Qt6/Qt5通用)
  • 给芯片设计新人的保姆级面积估算指南:从IO、Standard Cell到Macro Block怎么算?
  • 别再只用RGB图做分割了!手把手教你用Python融合深度图(RGB-D)提升分割精度
  • 使用Nemotron构建文档处理RAG管道
  • Docker 27资源监控增强配置:3分钟定位CPU爆表、内存泄漏与网络抖动的7个隐藏参数
  • Docker 27监控能力跃迁实录(27项增强配置权威白皮书)
  • 从PyTorch到海光DCU:手把手教你用MIGraphX部署ResNet50推理服务(附完整C++/Python代码)
  • 手把手带你用Python实现TimSort算法(附完整代码与调试技巧)
  • 从ELMo到BERT:手把手教你理解NLP预训练模型的进化史(附代码示例)
  • CVX工具箱避坑指南:从norm()到log_det(),这些内置函数你用对了吗?
  • Linux RT 调度器的限流机制:rt_throttled 标志的触发与解除
  • 告别网格撕裂!用Fluent动网格Smoothing Spring搞定三角形/四面体变形(附完整UDF与避坑指南)
  • 从无人机照片到Web三维地图:我是如何用Cesium+3D高斯泼溅(3DGS)搭建一个轻量级实景应用的
  • 告别环境配置烦恼:用Docker一键搞定TensorRT trtexec和ONNX模型转换