当前位置: 首页 > 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.jsqmd.com/news/678949/

相关文章:

  • WindowResizer:如何轻松解决Windows顽固窗口无法调整大小的终极指南
  • DownKyi免费下载工具:3步轻松获取B站高清视频的完整指南
  • Neovim插件管理进阶:除了PlugInstall,vim-plug的这些技巧让你的配置更专业
  • 联想电脑必备!Lenovo Quick Fix工具包全功能实测(附下载链接)
  • Docker 27量子计算适配案例分析(2024全球仅7家机构通过CNCF量子SIG认证)
  • 健身房管理系统中的UML建模与编程实现
  • 告别Keil,在Windows上用VSCode + arm-none-eabi-gcc + Makefile搭建国产MCU开发环境(附JLink配置避坑)
  • GLM-4.1V-9B-Base应用场景:在线教育题图自动解析与知识点标注
  • 别再死记硬背了!用TwinCAT 3和Wireshark抓包,5分钟搞懂EtherCAT的4种寻址模式
  • 水稻基因组注释太乱?手把手教你用RAP-DB和RGAP数据生成完整GFF/GTF文件
  • 如何高效实现跨平台视频资源解析:VideoDownloadHelper专业指南
  • 从GDC论文到UE5蓝图:手把手实现‘惯性化’动画过渡,让你的角色动作更物理
  • 构建高性能Vue3+TS移动端Table组件:从卡顿优化到流畅交互
  • 从Ext4迁移到Btrfs实战:我的个人服务器数据无损转换全记录与避坑指南
  • AngularJS XMLHttpRequest
  • 目前验证码识别遇到的问题
  • 避开这些坑!调试MS41xx系列镜头驱动芯片时,VD_FZ信号与电机‘丢步’问题的深度解析
  • 别再死记硬背了!用Python+NetworkX快速上手ER、BA、WS、NW四大经典网络模型
  • OpencvSharp 算子学习教案之 - Cv2.MorphologyEx
  • nli-MiniLM2-L6-H768参数详解:Cross-Encoder vs Bi-Encoder在NLI任务中的选型建议
  • 高并发系统重构迫在眉睫?Java 25虚拟线程上线72小时:GC停顿降86%,连接池告警归零,》
  • 2026年厕所隔断服务机构top5排行:卫生间隔断板材/厕所隔断/洗手间隔断/卫生间隔断/选择指南 - 优质品牌商家
  • RWKV7-1.5B-g1a部署案例:CSDN平台外网服务(7860端口)完整调试与日志排障指南
  • Prompt工程进阶2026:从基础提示到企业级提示系统设计
  • C语言新手必看:用代码实现人民币大写转换,搞定这道经典编程题
  • 别再死记硬背模型了!用SUMO的Krauss跟驰模型,手把手教你复现一次真实堵车
  • FPGA间高速数据搬运工:SRIO NWRITE协议在图像处理系统中的实战优化
  • GNU Radio之「模块」—— QT GUI Time Sink
  • ESP32-C3 SPI避坑指南:从模式选择到时钟配置,新手必看的5个常见错误
  • 推荐几款内存占用小的监控Agent:2026年企业级智能体与轻量化监控选型全景盘点